added default option
[plcapi.git] / PLC / Persons.py
1 #
2 # Functions for interacting with the persons table in the database
3 #
4
5 from types import StringTypes
6 try:
7     from hashlib import md5
8 except ImportError:
9     from md5 import md5
10 import time
11 from random import Random
12 import re
13 import crypt
14
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
22
23 class Person(AlchemyObj):
24     """
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().
28     """
29
30     tablename = 'persons'
31
32     fields = {
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),
56         }
57
58     def validate_email(self, email):
59         """
60         Validate email address. Stolen from Mailman.
61         """
62         email = email.lower()
63         invalid_email = PLCInvalidArgument("Invalid e-mail address")
64
65         if not email:
66             raise invalid_email
67
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):
70             raise invalid_email
71
72         return email
73
74     def can_update(self, person):
75         """
76         Returns true if we can update the specified person. We can
77         update a person if:
78
79         1. We are the person.
80         2. We are an admin.
81         3. We are a PI and the person is a user or tech or at
82            one of our sites.
83         """
84
85         assert isinstance(person, Person)
86
87         if self.person_id == person.person_id:
88             return True
89
90         if 'admin' in self['roles']:
91             return True
92
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']))
97
98         return False
99
100     def can_view(self, person):
101         """
102         Returns true if we can view the specified person. We can
103         view a person if:
104
105         1. We are the person.
106         2. We are an admin.
107         3. We are a PI or Tech and the person is at one of our sites.
108         """
109
110         assert isinstance(person, Person)
111
112         if self.can_update(person):
113             return True
114
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']):
118                 return True
119
120         return False
121
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})
125         if not roles:
126             raise PLCInvalidArgument, "Role %s not found" % role_name 
127         role = roles[0]
128       
129         if login_base:
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)
133         else:
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})
140                 site = sites[0]
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)
143     
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})
147         if not roles:
148             raise PLCInvalidArgument, "Role %s not found" % role_name
149         role = roles[0]
150
151         if login_base:
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)
155         else:
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
160  is specified"
161             for site_id in self['site_ids']:
162                 sites = Sites(self.api, {'site_id': site_id})
163                 site = sites[0]
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)
166
167     #add_key = Row.add_object(Key, 'person_key')
168     #remove_key = Row.remove_object(Key, 'person_key')
169
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))
180         else:
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))
183         
184
185     def delete(self):
186         assert 'person_id' in self
187         assert 'keystone_id' in self
188
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)
192
193         # delete relationships
194         SlicePerson().delete.filter({'person_id': self['person_id']})
195
196         # delete person
197         AlchemyObj.delete(self, dict(self))
198
199  
200     def get_tenants_ids(self):
201         tenants = []
202         if self.tenantId:
203             tenants = [self.tenantId]
204         return tenants
205
206     def get_key_ids(self):
207         return []
208
209 class Persons(list):
210     """
211     Representation of row(s) from the persons table in the
212     database.
213     """
214
215     def __init__(self, api, person_filter = None, columns = None):
216         self.api = api
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})
230         else:
231             raise PLCInvalidArgument, "Wrong person filter %r"%person_filter
232
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]
246             self.append(person)