# # Functions for interacting with the persons table in the database # from types import StringTypes try: from hashlib import md5 except ImportError: from md5 import md5 import time from random import Random import re import crypt from PLC.Faults import * from PLC.Debug import log from PLC.Parameter import Parameter, Mixed from PLC.Table import Row, Table from PLC.Roles import Role, Roles from PLC.Keys import Key, Keys from PLC.Messages import Message, Messages from PLC.Storage.AlchemyObject import AlchemyObj class Person(AlchemyObj): """ Representation of a row in the persons table. To use, optionally instantiate with a dict of values. Update as you would a dict. Commit to the database with sync(). """ tablename = 'persons' fields = { 'person_id': Parameter(int, "User identifier", primary_key=True), 'keystone_id': Parameter(int, "Keystone User identifier"), 'first_name': Parameter(str, "Given name", max = 128), 'last_name': Parameter(str, "Surname", max = 128), 'title': Parameter(str, "Title", max = 128, nullok = True), 'email': Parameter(str, "Primary e-mail address", max = 254), 'phone': Parameter(str, "Telephone number", max = 64, nullok = True), 'url': Parameter(str, "Home page", max = 254, nullok = True), 'bio': Parameter(str, "Biography", max = 254, nullok = True), 'enabled': Parameter(bool, "Has been enabled"), 'password': Parameter(str, "Account password in crypt() form", max = 254), 'verification_key': Parameter(str, "Reset password key", max = 254, nullok = True), 'verification_expires': Parameter(int, "Date and time when verification_key expires", nullok = True), 'last_updated': Parameter(int, "Date and time of last update", ro = True), 'date_created': Parameter(int, "Date and time when account was created", ro = True), 'role_ids': Parameter([int], "List of role identifiers", joined=True), 'roles': Parameter([str], "List of roles", joined=True), 'site_ids': Parameter([int], "List of site identifiers", joined=True), 'key_ids': Parameter([int], "List of key identifiers", joined=True), 'slice_ids': Parameter([int], "List of slice identifiers", joined=True), 'peer_id': Parameter(int, "Peer to which this user belongs", nullok = True), 'peer_person_id': Parameter(int, "Foreign user identifier at peer", nullok = True), 'person_tag_ids' : Parameter ([int], "List of tags attached to this person", joined=True), } def validate_email(self, email): """ Validate email address. Stolen from Mailman. """ email = email.lower() invalid_email = PLCInvalidArgument("Invalid e-mail address") if not email: raise invalid_email email_re = re.compile('\A[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9._\-]+\.[a-zA-Z]+\Z') if not email_re.match(email): raise invalid_email return email def can_update(self, person): """ Returns true if we can update the specified person. We can update a person if: 1. We are the person. 2. We are an admin. 3. We are a PI and the person is a user or tech or at one of our sites. """ assert isinstance(person, Person) if self.person_id == person.person_id: return True if 'admin' in self['roles']: return True if 'pi' in self['roles']: if set(self['tenants']).intersection(person['tenants']): # non-admin users cannot update a person who is neither a PI or ADMIN return (not set(['pi','admin']).intersection(person['roles'])) return False def can_view(self, person): """ Returns true if we can view the specified person. We can view a person if: 1. We are the person. 2. We are an admin. 3. We are a PI or Tech and the person is at one of our sites. """ assert isinstance(person, Person) if self.can_update(person): return True # pis and techs can see all people on their site if set(['pi','tech']).intersection(self['roles']): if set(self['site_ids']).intersection(person['site_ids']): return True return False #add_role = Row.add_object(Role, 'person_role') #remove_role = Row.remove_object(Role, 'person_role') #add_key = Row.add_object(Key, 'person_key') #remove_key = Row.remove_object(Key, 'person_key') def sync(self, insert=False, validate=True): NovaObject.sync(self, insert, validate) nova_fields = ['enabled', 'email', 'password'] nova_can_update = lambda (field, value): field in nova_fields nova_person = dict(filter(nova_can_update, self.items())) nova_person['name'] = "%s %s" % (self.get('first_name', ''), self.get('last_name', '')) if insert == True or 'person_id' not in self: self.object = self.api.client_shell.keystone.users.create(**self) self['keystone_id'] = self.object.id AlchemyObj.insert(self, dict(self)) else: self.object = self.api.client_shell.keystone.users.update(self['person_id'], nova_person) AlchemyObj.update(self, {'person_id': self['person_id']}, dict(self)) def delete(self): assert 'person_id' in self # delete relationships SlicePerson().delete.filter({'person_id': self['person_id']}) # delete nova object user = self.api.client_shell.keystone.users.find(**self) self.api.client_shell.keystone.users.delete(user) AlchemyObj.delete(self, dict(self)) def get_roles(self): roles = [] if self.tenant: roles = self.api.client_shell.keystone.roles.roles_for_user(self.object, self.tenant) return [role.name for role in roles] def get_tenants_ids(self): tenants = [] if self.tenantId: tenants = [self.tenantId] return tenants def get_key_ids(self): return [] class Persons(list): """ Representation of row(s) from the persons table in the database. """ def __init__(self, api, person_filter = None, columns = None): self.api = api if not person_filter: #persons = self.api.client_shell.keystone.users.findall() persons = Person().select() elif isinstance(person_filter, (list, tuple, set)): #persons = self.api.client_shell.keystone.users.findall() #persons = [person for person in persons if person.id in person_filter] ints = filter(lambda x: isinstance(x, (int, long)), person_filter) strs = filter(lambda x: isinstance(x, StringTypes), person_filter) person_filter = {'person_id': ints, 'email': strs} persons = Person().select(filter=person_filter) elif isinstance(person_filter, dict): persons = Person().select(filter=person_filter) #persons = self.api.client_shell.keystone.users.findall(**person_filter) elif isinstance (person_filter, StringTypes): persons = Person().select(filter={'email': person_filter}) else: raise PLCInvalidArgument, "Wrong person filter %r"%person_filter for person in persons: person = Person(self.api, object = person) person.tenant=None if person.tenantId: person.tenant = self.api.client_shell.keystone.tenants.find(id=person.tenantId) if not columns or 'roles' in columns: person['roles'] = person.get_roles() if not columns or 'tenant_ids' in columns: person['tenant_ids'] = person.get_tenants_ids() if not columns or 'key_ids' in columns: person['key_ids'] = person.get_keys_ids() if not columns or 'slice_ids' in columns: person_slices = SlicePerson().select(filter={'person_id': person.person_id}) person['slice_ids'] = [rec.slice_id for rec in person_slices] self.append(person)