2 # Functions for interacting with the persons table in the database
5 from types import StringTypes
7 from hashlib import md5
11 from random import Random
15 from PLC.Faults import *
16 from PLC.Debug import log
17 from PLC.Parameter import Parameter, Mixed
18 from PLC.Messages import Message, Messages
19 from PLC.Roles import Role, Roles
20 from PLC.Keys import Key, Keys
21 from PLC.Storage.AlchemyObject import AlchemyObj
23 class Person(AlchemyObj):
25 Representation of a row in the persons table. To use, optionally
26 instantiate with a dict of values. Update as you would a
27 dict. Commit to the database with sync().
33 'person_id': Parameter(int, "User identifier", primary_key=True),
34 'keystone_id': Parameter(int, "Keystone User identifier"),
35 'first_name': Parameter(str, "Given name", max = 128),
36 'last_name': Parameter(str, "Surname", max = 128),
37 'title': Parameter(str, "Title", max = 128, nullok = True),
38 'email': Parameter(str, "Primary e-mail address", max = 254),
39 'phone': Parameter(str, "Telephone number", max = 64, nullok = True),
40 'url': Parameter(str, "Home page", max = 254, nullok = True),
41 'bio': Parameter(str, "Biography", max = 254, nullok = True),
42 'enabled': Parameter(bool, "Has been enabled"),
43 'password': Parameter(str, "Account password in crypt() form", max = 254),
44 'verification_key': Parameter(str, "Reset password key", max = 254, nullok = True),
45 'verification_expires': Parameter(int, "Date and time when verification_key expires", nullok = True),
46 'last_updated': Parameter(int, "Date and time of last update", ro = True),
47 'date_created': Parameter(int, "Date and time when account was created", ro = True),
48 'role_ids': Parameter([int], "List of role identifiers", joined=True),
49 'roles': Parameter([str], "List of roles", joined=True),
50 'site_ids': Parameter([int], "List of site identifiers", joined=True),
51 'key_ids': Parameter([int], "List of key identifiers", joined=True),
52 'slice_ids': Parameter([int], "List of slice identifiers", joined=True),
53 'peer_id': Parameter(int, "Peer to which this user belongs", nullok = True),
54 'peer_person_id': Parameter(int, "Foreign user identifier at peer", nullok = True),
55 'person_tag_ids' : Parameter ([int], "List of tags attached to this person", joined=True),
58 def validate_email(self, email):
60 Validate email address. Stolen from Mailman.
63 invalid_email = PLCInvalidArgument("Invalid e-mail address")
68 email_re = re.compile('\A[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9._\-]+\.[a-zA-Z]+\Z')
69 if not email_re.match(email):
74 def can_update(self, person):
76 Returns true if we can update the specified person. We can
81 3. We are a PI and the person is a user or tech or at
85 assert isinstance(person, Person)
87 if self.person_id == person.person_id:
90 if 'admin' in self['roles']:
93 if 'pi' in self['roles']:
94 if set(self['site_ids']).intersection(person['site_ids']):
95 # non-admin users cannot update a person who is neither a PI or ADMIN
96 return (not set(['pi','admin']).intersection(person['roles']))
100 def can_view(self, person):
102 Returns true if we can view the specified person. We can
105 1. We are the person.
107 3. We are a PI or Tech and the person is at one of our sites.
110 assert isinstance(person, Person)
112 if self.can_update(person):
115 # pis and techs can see all people on their site
116 if set(['pi','tech']).intersection(self['roles']):
117 if set(self['site_ids']).intersection(person['site_ids']):
122 def add_role(self, role_name, login_base=None):
123 user = self.api.client_shell.keystone.users.find(id=self['keystone_id'])
124 roles = Roles(self.api, {'name': role_name})
126 raise PLCInvalidArgument, "Role %s not found" % role_name
130 # add role at the requested site
131 tenant = self.api.client_shell.keystone.tenants.find(name=login_base)
132 self.api.client_shell.keystone.roles.add_user_role(user, role, tenant)
134 from PLC.Sites import Sites
135 # add role to at all of users sites
136 if not self['site_ids']:
137 raise PLCInvalidArgument, "Cannot add role unless user already belongs to a site or a valid site is specified"
138 for site_id in self['site_ids']:
139 sites = Sites(self.api, {'site_id': site_id})
141 tenant = self.api.client_shell.keystone.tenants.find(id=site['tenant_id'])
142 self.api.client_shell.keystone.roles.add_user_role(user, role, tenant)
144 def remove_role(self, role_name, login_base=None):
145 user = self.api.client_shell.keystone.users.find(id=self['keystone_id'])
146 roles = Roles(self.api, {'name': role_name})
148 raise PLCInvalidArgument, "Role %s not found" % role_name
152 # add role at the requested site
153 tenant = self.api.client_shell.keystone.tenants.find(name=login_base)
154 self.api.client_shell.keystone.roles.add_user_role(user, role, tenant)
156 from PLC.Sites import Sites
157 # add role to at all of users sites
158 if not self['site_ids']:
159 raise PLCInvalidArgument, "Cannot add role unless user already belongs to a site or a valid site
161 for site_id in self['site_ids']:
162 sites = Sites(self.api, {'site_id': site_id})
164 tenant = self.api.client_shell.keystone.tenants.find(id=site['tenant_id'])
165 self.api.client_shell.keystone.roles.remove_user_role(user, role, tenant)
167 #add_key = Row.add_object(Key, 'person_key')
168 #remove_key = Row.remove_object(Key, 'person_key')
170 def sync(self, commit=True, validate=True):
171 AlchemyObj.sync(self, commit=commit, validate=validate)
172 nova_fields = ['enabled', 'email', 'password']
173 nova_can_update = lambda (field, value): field in nova_fields
174 nova_person = dict(filter(nova_can_update, self.items()))
175 nova_person['name'] = "%s %s" % (self.get('first_name', ''), self.get('last_name', ''))
176 if 'person_id' not in self:
177 self.object = self.api.client_shell.keystone.users.create(**self)
178 self['keystone_id'] = self.object.id
179 AlchemyObj.insert(self, dict(self))
181 self.object = self.api.client_shell.keystone.users.update(self['person_id'], nova_person)
182 AlchemyObj.update(self, {'person_id': self['person_id']}, dict(self))
186 assert 'person_id' in self
187 assert 'keystone_id' in self
189 # delete keystone record
190 nova_user = self.api.client_shell.keystone.users.find(id=self['keystone_id'])
191 self.api.client_shell.keystone.users.delete(nova_user)
193 # delete relationships
194 SlicePerson().delete.filter({'person_id': self['person_id']})
197 AlchemyObj.delete(self, dict(self))
200 def get_tenants_ids(self):
203 tenants = [self.tenantId]
206 def get_key_ids(self):
211 Representation of row(s) from the persons table in the
215 def __init__(self, api, person_filter = None, columns = None):
217 if not person_filter:
218 #persons = self.api.client_shell.keystone.users.findall()
219 persons = Person().select()
220 elif isinstance(person_filter, (list, tuple, set)):
221 ints = filter(lambda x: isinstance(x, (int, long)), person_filter)
222 strs = filter(lambda x: isinstance(x, StringTypes), person_filter)
223 person_filter = {'person_id': ints, 'email': strs}
224 persons = Person().select(filter=person_filter)
225 elif isinstance(person_filter, dict):
226 persons = Person().select(filter=person_filter)
227 #persons = self.api.client_shell.keystone.users.findall(**person_filter)
228 elif isinstance (person_filter, StringTypes):
229 persons = Person().select(filter={'email': person_filter})
231 raise PLCInvalidArgument, "Wrong person filter %r"%person_filter
233 for person in persons:
234 keystone_user = self.api.client_shell.keystone.persons.find(id=person['keystone_id'])
235 tenant = self.api.client_shell.keystone.tenants.find(id=keystone_user.tenantId)
236 if not columns or 'roles' in columns:
237 roles = self.api.client_shell.keystone.roles.roles_for_user(keystone_user, tenant)
238 person['roles'] = person.get_roles()
239 if not columns or 'tenant_ids' in columns:
240 person['tenant_ids'] = person.get_tenants_ids()
241 if not columns or 'key_ids' in columns:
242 person['key_ids'] = person.get_keys_ids()
243 if not columns or 'slice_ids' in columns:
244 person_slices = SlicePerson().select(filter={'person_id': person.person_id})
245 person['slice_ids'] = [rec.slice_id for rec in person_slices]