2 # Functions for interacting with the persons table in the database
5 from datetime import datetime
6 from types import StringTypes
8 from hashlib import md5
12 from random import Random
16 from PLC.Faults import *
17 from PLC.Debug import log
18 from PLC.Parameter import Parameter, Mixed
19 from PLC.Messages import Message, Messages
20 from PLC.Roles import Role, Roles
21 from PLC.Keys import Key, Keys
22 from PLC.SitePersons import SitePerson, SitePersons
23 from PLC.SlicePersons import SlicePerson, SlicePersons
24 from PLC.Storage.AlchemyObject import AlchemyObj
26 class Person(AlchemyObj):
28 Representation of a row in the persons table. To use, optionally
29 instantiate with a dict of values. Update as you would a
30 dict. Commit to the database with sync().
36 'person_id': Parameter(int, "User identifier", primary_key=True),
37 'keystone_id': Parameter(str, "Keystone User identifier"),
38 'first_name': Parameter(str, "Given name", max = 128),
39 'last_name': Parameter(str, "Surname", max = 128),
40 'title': Parameter(str, "Title", max = 128, nullok = True),
41 'email': Parameter(str, "Primary e-mail address", max = 254),
42 'phone': Parameter(str, "Telephone number", max = 64, nullok = True),
43 'url': Parameter(str, "Home page", max = 254, nullok = True),
44 'bio': Parameter(str, "Biography", max = 254, nullok = True),
45 'enabled': Parameter(bool, "Has been enabled"),
46 'password': Parameter(str, "Account password in crypt() form", max = 254),
47 'verification_key': Parameter(str, "Reset password key", max = 254, nullok = True),
48 'verification_expires': Parameter(datetime, "Date and time when verification_key expires", nullok = True),
49 'last_updated': Parameter(datetime, "Date and time of last update", ro = True, nullok=True),
50 'date_created': Parameter(datetime, "Date and time when account was created", ro = True, default=datetime.now()),
51 'role_ids': Parameter([int], "List of role identifiers", joined=True),
52 'roles': Parameter([str], "List of roles", joined=True),
53 'site_ids': Parameter([int], "List of site identifiers", joined=True),
54 'key_ids': Parameter([int], "List of key identifiers", joined=True),
55 'slice_ids': Parameter([int], "List of slice identifiers", joined=True),
56 'peer_id': Parameter(int, "Peer to which this user belongs", nullok = True),
57 'peer_person_id': Parameter(int, "Foreign user identifier at peer", nullok = True),
58 'person_tag_ids' : Parameter ([int], "List of tags attached to this person", joined=True),
61 def validate_last_updated(self, last_updated):
62 # always return current timestamp
63 last_updated = datetime.now()
66 def validate_email(self, email):
68 Validate email address. Stolen from Mailman.
71 invalid_email = PLCInvalidArgument("Invalid e-mail address")
76 email_re = re.compile('\A[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9._\-]+\.[a-zA-Z]+\Z')
77 if not email_re.match(email):
82 def can_update(self, person):
84 Returns true if we can update the specified person. We can
89 3. We are a PI and the person is a user or tech or at
93 assert isinstance(person, Person)
95 if self.person_id == person.person_id:
98 if 'admin' in self['roles']:
101 if 'pi' in self['roles']:
102 if set(self['site_ids']).intersection(person['site_ids']):
103 # non-admin users cannot update a person who is neither a PI or ADMIN
104 return (not set(['pi','admin']).intersection(person['roles']))
108 def can_view(self, person):
110 Returns true if we can view the specified person. We can
113 1. We are the person.
115 3. We are a PI or Tech and the person is at one of our sites.
118 assert isinstance(person, Person)
120 if self.can_update(person):
123 # pis and techs can see all people on their site
124 if set(['pi','tech']).intersection(self['roles']):
125 if set(self['site_ids']).intersection(person['site_ids']):
130 def add_role(self, role_name, site_filter = {}):
131 assert 'keystone_id' in self
132 from PLC.Sites import Sites
133 user = self.api.client_shell.keystone.users.find(id=self['keystone_id'])
134 roles = Roles(self.api, {'name': role_name})
136 raise PLCInvalidArgument, "Role %s not found" % role_name
140 sites = Sites(self.api, site_filter)
142 # add role at the requested site
143 tenant = self.api.client_shell.keystone.tenants.find(id=site['tenant_id'])
144 self.api.client_shell.keystone.roles.add_user_role(user, tenant, role.object)
146 # add role to at all of users sites
147 if not self['site_ids']:
148 raise PLCInvalidArgument, "Cannot add role unless user already belongs to a site or a valid site is specified"
149 for site_id in self['site_ids']:
150 sites = Sites(self.api, {'site_id': site_id})
152 tenant = self.api.client_shell.keystone.tenants.find(id=site['tenant_id'])
153 self.api.client_shell.keystone.roles.add_user_role(user, tenant, role.object)
155 def remove_role(self, role_name, login_base=None):
156 assert 'keystone_id' in self
157 user = self.api.client_shell.keystone.users.find(id=self['keystone_id'])
158 roles = Roles(self.api, {'name': role_name})
160 raise PLCInvalidArgument, "Role %s not found" % role_name
164 # add role at the requested site
165 tenant = self.api.client_shell.keystone.tenants.find(name=login_base)
166 self.api.client_shell.keystone.roles.add_user_role(user, role.object, tenant)
168 from PLC.Sites import Sites
169 # add role to at all of users sites
170 if not self['site_ids']:
171 raise PLCInvalidArgument, "Must specify a valid site or add user to site first"
172 for site_id in self['site_ids']:
173 sites = Sites(self.api, {'site_id': site_id})
175 tenant = self.api.client_shell.keystone.tenants.find(id=site['tenant_id'])
176 self.api.client_shell.keystone.roles.remove_user_role(user, role.object, tenant)
178 #add_key = Row.add_object(Key, 'person_key')
179 #remove_key = Row.remove_object(Key, 'person_key')
181 def sync(self, commit=True, validate=True):
182 assert 'email' in self
183 AlchemyObj.sync(self, commit=commit, validate=validate)
184 # filter out fields that are not supported in keystone
185 nova_fields = ['enabled', 'email', 'password']
186 nova_can_update = lambda (field, value): field in nova_fields
187 nova_person = dict(filter(nova_can_update, self.items()))
188 nova_person['name'] = "%s %s" % (self.get('first_name', ''), self.get('last_name', ''))
189 if 'person_id' not in self:
190 # check if keystone record exists
191 users = self.api.client_shell.keystone.users.findall(email=self['email'])
193 self.object = self.api.client_shell.keystone.users.create(**nova_person)
195 self.object = users[0]
196 self['keystone_id'] = self.object.id
197 AlchemyObj.insert(self, dict(self))
199 self.object = self.api.client_shell.keystone.users.update(self['person_id'], nova_person)
200 AlchemyObj.update(self, {'person_id': self['person_id']}, dict(self))
204 assert 'person_id' in self
205 assert 'keystone_id' in self
207 # delete keystone record
208 nova_user = self.api.client_shell.keystone.users.find(id=self['keystone_id'])
209 self.api.client_shell.keystone.users.delete(nova_user)
211 # delete relationships
212 SlicePerson().delete.filter({'person_id': self['person_id']})
215 AlchemyObj.delete(self, dict(self))
220 Representation of row(s) from the persons table in the
224 def __init__(self, api, person_filter = None, columns = None):
225 from PLC.Sites import Sites
227 if not person_filter:
228 #persons = self.api.client_shell.keystone.users.findall()
229 persons = Person().select()
230 elif isinstance(person_filter, (list, tuple, set)):
231 ints = filter(lambda x: isinstance(x, (int, long)), person_filter)
232 strs = filter(lambda x: isinstance(x, StringTypes), person_filter)
233 person_filter = {'person_id': ints, 'email': strs}
234 persons = Person().select(filter=person_filter)
235 elif isinstance(person_filter, dict):
236 persons = Person().select(filter=person_filter)
237 #persons = self.api.client_shell.keystone.users.findall(**person_filter)
238 elif isinstance (person_filter, StringTypes):
239 persons = Person().select(filter={'email': person_filter})
241 raise PLCInvalidArgument, "Wrong person filter %r"%person_filter
243 for person in persons:
244 person = Person(self.api, object=person)
245 keystone_user = self.api.client_shell.keystone.users.find(id=person['keystone_id'])
246 if not columns or 'site_ids' in columns:
247 site_persons = SitePerson().select(filter={'person_id': person['person_id']})
248 person['site_ids'] = [rec.site_id for rec in site_persons]
249 if not columns or 'roles' in columns:
251 sites = Sites(self.api, person['site_ids'])
253 tenant = self.api.client_shell.keystone.tenants.find(id=site['tenant_id'])
254 tenant_roles = self.api.client_shell.keystone.roles.roles_for_user(keystone_user, tenant)
255 for tenant_role in tenant_roles:
256 roles.add(tenant_role.name)
257 person['roles'] = list(roles)
258 if not columns or 'key_ids' in columns:
259 person['key_ids'] = []
260 if not columns or 'slice_ids' in columns:
261 person_slices = SlicePerson().select(filter={'person_id': person['person_id']})
262 person['slice_ids'] = [rec.slice_id for rec in person_slices]