# # 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.Messages import Message, Messages from PLC.Roles import Role, Roles from PLC.Keys import Key, Keys 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['site_ids']).intersection(person['site_ids']): # 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 def add_role(self, role_name, login_base=None): user = self.api.client_shell.keystone.users.find(id=self['keystone_id']) roles = Roles(self.api, {'name': role_name}) if not roles: raise PLCInvalidArgument, "Role %s not found" % role_name role = roles[0] if login_base: # add role at the requested site tenant = self.api.client_shell.keystone.tenants.find(name=login_base) self.api.client_shell.keystone.roles.add_user_role(user, role, tenant) else: from PLC.Sites import Sites # add role to at all of users sites if not self['site_ids']: raise PLCInvalidArgument, "Cannot add role unless user already belongs to a site or a valid site is specified" for site_id in self['site_ids']: sites = Sites(self.api, {'site_id': site_id}) site = sites[0] tenant = self.api.client_shell.keystone.tenants.find(id=site['tenant_id']) self.api.client_shell.keystone.roles.add_user_role(user, role, tenant) def remove_role(self, role_name, login_base=None): user = self.api.client_shell.keystone.users.find(id=self['keystone_id']) roles = Roles(self.api, {'name': role_name}) if not roles: raise PLCInvalidArgument, "Role %s not found" % role_name role = roles[0] if login_base: # add role at the requested site tenant = self.api.client_shell.keystone.tenants.find(name=login_base) self.api.client_shell.keystone.roles.add_user_role(user, role, tenant) else: from PLC.Sites import Sites # add role to at all of users sites if not self['site_ids']: raise PLCInvalidArgument, "Cannot add role unless user already belongs to a site or a valid site is specified" for site_id in self['site_ids']: sites = Sites(self.api, {'site_id': site_id}) site = sites[0] tenant = self.api.client_shell.keystone.tenants.find(id=site['tenant_id']) self.api.client_shell.keystone.roles.remove_user_role(user, role, tenant) #add_key = Row.add_object(Key, 'person_key') #remove_key = Row.remove_object(Key, 'person_key') def sync(self, commit=True, validate=True): AlchemyObj.sync(self, commit=commit, validate=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 '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 assert 'keystone_id' in self # delete keystone record nova_user = self.api.client_shell.keystone.users.find(id=self['keystone_id']) self.api.client_shell.keystone.users.delete(nova_user) # delete relationships SlicePerson().delete.filter({'person_id': self['person_id']}) # delete person AlchemyObj.delete(self, dict(self)) 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)): 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: keystone_user = self.api.client_shell.keystone.persons.find(id=person['keystone_id']) tenant = self.api.client_shell.keystone.tenants.find(id=keystone_user.tenantId) if not columns or 'roles' in columns: roles = self.api.client_shell.keystone.roles.roles_for_user(keystone_user, tenant) 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)