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