implement add_role, remove_role
[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             tenant = self.api.client_shell.keystone.tenants.find(name=login_base) 
131         else:
132             tenant = self.api.client_shell.keystone.tenants.find(id=self['tenantId'])
133           
134         self.api.client_shell.keystone.roles.add_user_role(user, role, tenant)
135     
136     def remove_role(self, role_name, login_base=None):
137         user = self.api.client_shell.keystone.users.find(id=self['keystone_id'])
138         roles = Roles(self.api, {'name': role_name})
139         if not roles:
140             raise PLCInvalidArgument, "Role %s not found" % role_name
141         role = roles[0]
142
143         if login_base:
144             tenant = self.api.client_shell.keystone.tenants.find(name=login_base)
145         else:
146             tenant = self.api.client_shell.keystone.tenants.find(id=self['tenantId'])
147
148         self.api.client_shell.keystone.roles.remove_user_role(user, role, tenant)        
149
150
151     #add_key = Row.add_object(Key, 'person_key')
152     #remove_key = Row.remove_object(Key, 'person_key')
153
154     def sync(self, commit=True, validate=True):
155         AlchemyObj.sync(self, commit=commit, validate=validate)
156         nova_fields = ['enabled', 'email', 'password']
157         nova_can_update = lambda (field, value): field in nova_fields
158         nova_person = dict(filter(nova_can_update, self.items()))
159         nova_person['name'] = "%s %s" % (self.get('first_name', ''), self.get('last_name', ''))  
160         if 'person_id' not in self:
161             self.object = self.api.client_shell.keystone.users.create(**self)
162             self['keystone_id'] = self.object.id
163             AlchemyObj.insert(self, dict(self))
164         else:
165             self.object = self.api.client_shell.keystone.users.update(self['person_id'], nova_person)
166             AlchemyObj.update(self, {'person_id': self['person_id']}, dict(self))
167         
168
169     def delete(self):
170         assert 'person_id' in self
171         assert 'keystone_id' in self
172
173         # delete keystone record
174         nova_user = self.api.client_shell.keystone.users.find(id=self['keystone_id'])
175         self.api.client_shell.keystone.users.delete(nova_user)
176
177         # delete relationships
178         SlicePerson().delete.filter({'person_id': self['person_id']})
179
180         # delete person
181         AlchemyObj.delete(self, dict(self))
182
183  
184     def get_tenants_ids(self):
185         tenants = []
186         if self.tenantId:
187             tenants = [self.tenantId]
188         return tenants
189
190     def get_key_ids(self):
191         return []
192
193 class Persons(list):
194     """
195     Representation of row(s) from the persons table in the
196     database.
197     """
198
199     def __init__(self, api, person_filter = None, columns = None):
200         self.api = api
201         if not person_filter:
202             #persons = self.api.client_shell.keystone.users.findall()
203             persons = Person().select()
204         elif isinstance(person_filter, (list, tuple, set)):
205             ints = filter(lambda x: isinstance(x, (int, long)), person_filter)
206             strs = filter(lambda x: isinstance(x, StringTypes), person_filter)
207             person_filter = {'person_id': ints, 'email': strs}
208             persons = Person().select(filter=person_filter)
209         elif isinstance(person_filter, dict):
210             persons = Person().select(filter=person_filter)
211             #persons = self.api.client_shell.keystone.users.findall(**person_filter)
212         elif isinstance (person_filter, StringTypes):
213             persons = Person().select(filter={'email': person_filter})
214         else:
215             raise PLCInvalidArgument, "Wrong person filter %r"%person_filter
216
217         for person in persons:
218             keystone_user = self.api.client_shell.keystone.persons.find(id=person['keystone_id'])
219             tenant = self.api.client_shell.keystone.tenants.find(id=keystone_user.tenantId)
220             if not columns or 'roles' in columns:
221                 roles = self.api.client_shell.keystone.roles.roles_for_user(keystone_user, tenant)
222                 person['roles'] = person.get_roles()
223             if not columns or 'tenant_ids' in columns:
224                 person['tenant_ids'] = person.get_tenants_ids() 
225             if not columns or 'key_ids' in columns:
226                 person['key_ids'] = person.get_keys_ids() 
227             if not columns or 'slice_ids' in columns:
228                 person_slices = SlicePerson().select(filter={'person_id': person.person_id})
229                 person['slice_ids'] = [rec.slice_id for rec in person_slices]
230             self.append(person)