assert primary key exists in record before attempting to delete it
[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.Table import Row, Table
19 from PLC.Roles import Role, Roles
20 from PLC.Keys import Key, Keys
21 from PLC.Messages import Message, Messages
22 from PLC.Storage.AlchemyObject import AlchemyObj
23
24 class Person(AlchemyObj):
25     """
26     Representation of a row in the persons table. To use, optionally
27     instantiate with a dict of values. Update as you would a
28     dict. Commit to the database with sync().
29     """
30
31     tablename = 'persons'
32
33     fields = {
34         'person_id': Parameter(int, "User identifier", primary_key=True),
35         'keystone_id': Parameter(int, "Keystone User identifier"),
36         'first_name': Parameter(str, "Given name", max = 128),
37         'last_name': Parameter(str, "Surname", max = 128),
38         'title': Parameter(str, "Title", max = 128, nullok = True),
39         'email': Parameter(str, "Primary e-mail address", max = 254),
40         'phone': Parameter(str, "Telephone number", max = 64, nullok = True),
41         'url': Parameter(str, "Home page", max = 254, nullok = True),
42         'bio': Parameter(str, "Biography", max = 254, nullok = True),
43         'enabled': Parameter(bool, "Has been enabled"),
44         'password': Parameter(str, "Account password in crypt() form", max = 254),
45         'verification_key': Parameter(str, "Reset password key", max = 254, nullok = True),
46         'verification_expires': Parameter(int, "Date and time when verification_key expires", nullok = True),
47         'last_updated': Parameter(int, "Date and time of last update", ro = True),
48         'date_created': Parameter(int, "Date and time when account was created", ro = True),
49         'role_ids': Parameter([int], "List of role identifiers", joined=True),
50         'roles': Parameter([str], "List of roles", joined=True),
51         'site_ids': Parameter([int], "List of site identifiers", joined=True),
52         'key_ids': Parameter([int], "List of key identifiers", joined=True),
53         'slice_ids': Parameter([int], "List of slice identifiers", joined=True),
54         'peer_id': Parameter(int, "Peer to which this user belongs", nullok = True),
55         'peer_person_id': Parameter(int, "Foreign user identifier at peer", nullok = True),
56         'person_tag_ids' : Parameter ([int], "List of tags attached to this person", joined=True),
57         }
58
59     def validate_email(self, email):
60         """
61         Validate email address. Stolen from Mailman.
62         """
63         email = email.lower()
64         invalid_email = PLCInvalidArgument("Invalid e-mail address")
65
66         if not email:
67             raise invalid_email
68
69         email_re = re.compile('\A[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9._\-]+\.[a-zA-Z]+\Z')
70         if not email_re.match(email):
71             raise invalid_email
72
73         return email
74
75     def can_update(self, person):
76         """
77         Returns true if we can update the specified person. We can
78         update a person if:
79
80         1. We are the person.
81         2. We are an admin.
82         3. We are a PI and the person is a user or tech or at
83            one of our sites.
84         """
85
86         assert isinstance(person, Person)
87
88         if self.person_id == person.person_id:
89             return True
90
91         if 'admin' in self['roles']:
92             return True
93
94         if 'pi' in self['roles']:
95             if set(self['tenants']).intersection(person['tenants']):
96                 # non-admin users cannot update a person who is neither a PI or ADMIN
97                 return (not set(['pi','admin']).intersection(person['roles']))
98
99         return False
100
101     def can_view(self, person):
102         """
103         Returns true if we can view the specified person. We can
104         view a person if:
105
106         1. We are the person.
107         2. We are an admin.
108         3. We are a PI or Tech and the person is at one of our sites.
109         """
110
111         assert isinstance(person, Person)
112
113         if self.can_update(person):
114             return True
115
116         # pis and techs can see all people on their site
117         if set(['pi','tech']).intersection(self['roles']):
118             if set(self['site_ids']).intersection(person['site_ids']):
119                 return True
120
121         return False
122
123     #add_role = Row.add_object(Role, 'person_role')
124     #remove_role = Row.remove_object(Role, 'person_role')
125
126     #add_key = Row.add_object(Key, 'person_key')
127     #remove_key = Row.remove_object(Key, 'person_key')
128
129     def sync(self, insert=False, validate=True):
130         NovaObject.sync(self, insert, validate)
131         nova_fields = ['enabled', 'email', 'password']
132
133         nova_can_update = lambda (field, value): field in nova_fields
134         nova_person = dict(filter(nova_can_update, self.items()))
135         nova_person['name'] = "%s %s" % (self.get('first_name', ''), self.get('last_name', ''))  
136         if insert == True or 'person_id' not in self:
137             self.object = self.api.client_shell.keystone.users.create(**self)
138             self['keystone_id'] = self.object.id
139             AlchemyObj.insert(self, dict(self))
140         else:
141             self.object = self.api.client_shell.keystone.users.update(self['person_id'], nova_person)
142             AlchemyObj.update(self, {'person_id': self['person_id']}, dict(self))
143         
144
145     def delete(self):
146         assert 'person_id' in self
147         # delete relationships
148         SlicePerson().delete.filter({'person_id': self['person_id']})
149
150         # delete nova object
151         user = self.api.client_shell.keystone.users.find(**self)
152         self.api.client_shell.keystone.users.delete(user)
153         AlchemyObj.delete(self, dict(self))
154
155  
156     def get_roles(self):
157         roles = []
158         if self.tenant:
159             roles = self.api.client_shell.keystone.roles.roles_for_user(self.object, self.tenant)
160         return [role.name for role in roles] 
161
162     def get_tenants_ids(self):
163         tenants = []
164         if self.tenantId:
165             tenants = [self.tenantId]
166         return tenants
167
168     def get_key_ids(self):
169         return []
170
171 class Persons(list):
172     """
173     Representation of row(s) from the persons table in the
174     database.
175     """
176
177     def __init__(self, api, person_filter = None, columns = None):
178         self.api = api
179         if not person_filter:
180             #persons = self.api.client_shell.keystone.users.findall()
181             persons = Person().select()
182         elif isinstance(person_filter, (list, tuple, set)):
183             #persons = self.api.client_shell.keystone.users.findall()
184             #persons = [person for person in persons if person.id in person_filter]
185             ints = filter(lambda x: isinstance(x, (int, long)), person_filter)
186             strs = filter(lambda x: isinstance(x, StringTypes), person_filter)
187             person_filter = {'person_id': ints, 'email': strs}
188             persons = Person().select(filter=person_filter)
189         elif isinstance(person_filter, dict):
190             persons = Person().select(filter=person_filter)
191             #persons = self.api.client_shell.keystone.users.findall(**person_filter)
192         elif isinstance (person_filter, StringTypes):
193             persons = Person().select(filter={'email': person_filter})
194         else:
195             raise PLCInvalidArgument, "Wrong person filter %r"%person_filter
196
197         for person in persons:
198             person = Person(self.api, object = person)
199             person.tenant=None
200             if person.tenantId:
201                 person.tenant = self.api.client_shell.keystone.tenants.find(id=person.tenantId)
202             if not columns or 'roles' in columns:
203                 person['roles'] = person.get_roles()
204             if not columns or 'tenant_ids' in columns:
205                 person['tenant_ids'] = person.get_tenants_ids() 
206             if not columns or 'key_ids' in columns:
207                 person['key_ids'] = person.get_keys_ids() 
208             if not columns or 'slice_ids' in columns:
209                 person_slices = SlicePerson().select(filter={'person_id': person.person_id})
210                 person['slice_ids'] = [rec.slice_id for rec in person_slices]
211             self.append(person)