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