f08e18d0ce3f2ae3c55844f4aac9c1e39a1a9241
[sfa.git] / sfa / managers / registry_manager_pl.py
1 import types
2 import time 
3 from sfa.server.registry import Registries
4 from sfa.util.prefixTree import prefixTree
5 from sfa.util.record import GeniRecord
6 from sfa.util.genitable import GeniTable
7 from sfa.util.record import GeniRecord
8 from sfa.util.genitable import GeniTable
9 from sfa.trust.gid import GID 
10 from sfa.util.namespace import *
11 from sfa.trust.credential import *
12 from sfa.trust.certificate import *
13 from sfa.util.faults import *
14
15 def get_credential(api, hrn, type, is_self=False):    
16     # Is this a root or sub authority
17     auth_hrn = api.auth.get_authority(hrn)
18     if not auth_hrn or hrn == api.config.SFA_INTERFACE_HRN:
19         auth_hrn = hrn
20     # get record info
21     auth_info = api.auth.get_auth_info(auth_hrn)
22     table = GeniTable()
23     records = table.findObjects({'type': type, 'hrn': hrn})
24     if not records:
25         raise RecordNotFound(hrn)
26     record = records[0]
27
28     # verify_cancreate_credential requires that the member lists
29     # (researchers, pis, etc) be filled in
30     api.fill_record_info(record)
31
32     # get the callers gid
33     # if this is a self cred the record's gid is the caller's gid
34     if is_self:
35         caller_hrn = hrn
36         caller_gid = record.get_gid_object()
37     else:
38         caller_gid = api.auth.client_cred.get_gid_caller() 
39         caller_hrn = caller_gid.get_hrn()
40     
41     object_hrn = record.get_gid_object().get_hrn()
42     rights = api.auth.determine_user_rights(caller_hrn, record)
43     # make sure caller has rights to this object
44     if rights.is_empty():
45         raise PermissionError(caller_hrn + " has no rights to " + record['name'])
46
47     object_gid = GID(string=record['gid'])
48     new_cred = Credential(subject = object_gid.get_subject())
49     new_cred.set_gid_caller(caller_gid)
50     new_cred.set_gid_object(object_gid)
51     new_cred.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn)
52     new_cred.set_pubkey(object_gid.get_pubkey())
53     new_cred.set_privileges(rights)
54     new_cred.set_delegate(True)
55     auth_kind = "authority,ma,sa"
56     new_cred.set_parent(api.auth.hierarchy.get_auth_cred(auth_hrn, kind=auth_kind))
57     new_cred.encode()
58     new_cred.sign()
59
60     return new_cred.save_to_string(save_parents=True)
61
62 def resolve(api, hrns, type=None, origin_hrn=None):
63
64     # load all know registry names into a prefix tree and attempt to find
65     # the longest matching prefix
66     if not isinstance(hrns, types.ListType):
67         hrns = [hrns]
68     
69     # create a dict whre key is an registry hrn and its value is a
70     # hrns at that registry (determined by the known prefix tree).  
71     hrn_dict = {}
72     registries = Registries(api)
73     tree = prefixTree()
74     registry_hrns = registries.keys()
75     tree.load(registry_hrns)
76     for hrn in hrns:
77         registry_hrn = tree.best_match(hrn)
78         if registry_hrn not in hrn_dict:
79             hrn_dict[registry_hrn] = []
80         hrn_dict[registry_hrn].append(hrn)
81         
82     records = [] 
83     for registry_hrn in hrn_dict:
84         # skip the hrn without a registry hrn
85         # XX should we let the user know the authority is unknown?       
86         if not registry_hrn:
87             continue
88
89         # if the best match (longest matching hrn) is not the local registry,
90         # forward the request
91         hrns = hrn_dict[registry_hrn]
92         if registry_hrn != api.hrn:
93             credential = api.getCredential()
94             peer_records = registries[registry_hrn].resolve(credential, hrn, origin_hrn)
95             records.extend([GeniRecord(dict=record).as_dict() for record in peer_records])
96
97     # try resolving the remaining unfound records at the local registry
98     remaining_hrns = set(hrns).difference([record['hrn'] for record in records])
99     remaining_hrns = [hrn for hrn in remaining_hrns] 
100     table = GeniTable()
101     local_records = table.findObjects({'hrn': remaining_hrns})
102     for record in local_records:
103         try:
104             api.fill_record_info(record)
105             records.append(dict(record))
106         except PlanetLabRecordDoesNotExist:
107             # silently drop the ones that are missing in PL
108             print >> log, "ignoring geni record ", record['hrn'], \
109                               " because pl record does not exist"    
110             table.remove(record)
111
112     if not records:
113         raise RecordNotFound(str(hrns))
114
115     if type:
116         records = filter(lambda rec: rec['type'] in [type], records)
117
118     return records
119
120 def list(api, hrn):
121     # load all know registry names into a prefix tree and attempt to find
122     # the longest matching prefix
123     records = []
124     registries = Registries(api)
125     hrns = registries.keys()
126     tree = prefixTree()
127     tree.load(hrns)
128     registry_hrn = tree.best_match(hrn)
129     
130     #if there was no match then this record belongs to an unknow registry
131     if not registry_hrn:
132         raise MissingAuthority(hrn)
133     
134     # if the best match (longest matching hrn) is not the local registry,
135     # forward the request
136     records = []    
137     if registry_hrn != api.hrn:
138         credential = api.getCredential()
139         record_list = registries[registry_hrn].list(credential, hrn, origin_hrn)
140         records = [GeniRecord(dict=record).as_dict() for record in record_list]
141     
142     # if we still havnt found the record yet, try the local registry
143     if not records:
144         if not api.auth.hierarchy.auth_exists(hrn):
145             raise MissingAuthority(hrn)
146
147         table = GeniTable()
148         records = table.find({'authority': hrn})
149
150     return records
151
152
153 def register(api, record):
154
155     hrn, type = record['hrn'], record['type']
156
157     # validate the type
158     if type not in ['authority', 'slice', 'node', 'user']:
159         raise UnknownGeniType(type) 
160     
161     # check if record already exists
162     table = GeniTable()
163     existing_records = table.find({'type': type, 'hrn': hrn})
164     if existing_records:
165         raise ExistingRecord(hrn)
166        
167     record = GeniRecord(dict = record)
168     record['authority'] = get_authority(record['hrn'])
169     type = record['type']
170     hrn = record['hrn']
171     api.auth.verify_object_permission(hrn)
172     auth_info = api.auth.get_auth_info(record['authority'])
173     pub_key = None
174     # make sure record has a gid
175     if 'gid' not in record:
176         uuid = create_uuid()
177         pkey = Keypair(create=True)
178         if 'key' in record and record['key']:
179             if isinstance(record['key'], types.ListType):
180                 pub_key = record['key'][0]
181             else:
182                 pub_key = record['key']
183             pkey = convert_public_key(pub_key)
184
185         gid_object = api.auth.hierarchy.create_gid(hrn, uuid, pkey)
186         gid = gid_object.save_to_string(save_parents=True)
187         record['gid'] = gid
188         record.set_gid(gid)
189
190     if type in ["authority"]:
191         # update the tree
192         if not api.auth.hierarchy.auth_exists(hrn):
193             api.auth.hierarchy.create_auth(hrn)
194
195         # get the GID from the newly created authority
196         gid = auth_info.get_gid_object()
197         record.set_gid(gid.save_to_string(save_parents=True))
198         pl_record = api.geni_fields_to_pl_fields(type, hrn, record)
199         sites = api.plshell.GetSites(api.plauth, [pl_record['login_base']])
200         if not sites:
201             pointer = api.plshell.AddSite(api.plauth, pl_record)
202         else:
203             pointer = sites[0]['site_id']
204
205         record.set_pointer(pointer)
206         record['pointer'] = pointer
207
208     elif (type == "slice"):
209         acceptable_fields=['url', 'instantiation', 'name', 'description']
210         pl_record = api.geni_fields_to_pl_fields(type, hrn, record)
211         for key in pl_record.keys():
212             if key not in acceptable_fields:
213                 pl_record.pop(key)
214             slices = api.plshell.GetSlices(api.plauth, [pl_record['name']])
215             if not slices:
216                 pointer = api.plshell.AddSlice(api.plauth, pl_record)
217             else:
218                 pointer = slices[0]['slice_id']
219             record.set_pointer(pointer)
220             record['pointer'] = pointer
221
222     elif  (type == "user"):
223         persons = api.plshell.GetPersons(api.plauth, [record['email']])
224         if not persons:
225             pointer = api.plshell.AddPerson(api.plauth, dict(record))
226         else:
227             pointer = persons[0]['person_id']
228
229         if 'enabled' in record and record['enabled']:
230             api.plshell.UpdatePerson(api.plauth, pointer, {'enabled': record['enabled']})
231         # add this persons to the site only if he is being added for the first
232         # time by sfa and doesont already exist in plc
233         if not persons or not persons[0]['site_ids']:
234             login_base = get_leaf(record['authority'])
235             api.plshell.AddPersonToSite(api.plauth, pointer, login_base)
236
237         # What roles should this user have?
238         api.plshell.AddRoleToPerson(api.plauth, 'user', pointer)
239         # Add the user's key
240         if pub_key:
241             api.plshell.AddPersonKey(api.plauth, pointer, {'key_type' : 'ssh', 'key' : pub_key})
242
243     elif (type == "node"):
244         pl_record = api.geni_fields_to_pl_fields(type, hrn, record)
245         login_base = hrn_to_pl_login_base(record['authority'])
246         nodes = api.plshell.GetNodes(api.plauth, [pl_record['hostname']])
247         if not nodes:
248             pointer = api.plshell.AddNode(api.plauth, login_base, pl_record)
249         else:
250             pointer = nodes[0]['node_id']
251
252     record['pointer'] = pointer
253     record.set_pointer(pointer)
254     record_id = table.insert(record)
255     record['record_id'] = record_id
256
257     # update membership for researchers, pis, owners, operators
258     api.update_membership(None, record)
259
260     return record.get_gid_object().save_to_string(save_parents=True)
261
262 def update(api, record_dict):
263     new_record = GeniRecord(dict = record_dict)
264     type = new_record['type']
265     hrn = new_record['hrn']
266     api.auth.verify_object_permission(hrn)
267     table = GeniTable()
268     # make sure the record exists
269     records = table.findObjects({'type': type, 'hrn': hrn})
270     if not records:
271         raise RecordNotFound(hrn)
272     record = records[0]
273     record['last_updated'] = time.gmtime()
274
275     # Update_membership needs the membership lists in the existing record
276     # filled in, so it can see if members were added or removed
277     api.fill_record_info(record)
278
279     # Use the pointer from the existing record, not the one that the user
280     # gave us. This prevents the user from inserting a forged pointer
281     pointer = record['pointer']
282     # update the PLC information that was specified with the record
283
284     if (type == "authority"):
285         api.plshell.UpdateSite(api.plauth, pointer, new_record)
286
287     elif type == "slice":
288         pl_record=api.geni_fields_to_pl_fields(type, hrn, new_record)
289         if 'name' in pl_record:
290             pl_record.pop('name')
291             api.plshell.UpdateSlice(api.plauth, pointer, pl_record)
292
293     elif type == "user":
294         # SMBAKER: UpdatePerson only allows a limited set of fields to be
295         #    updated. Ideally we should have a more generic way of doing
296         #    this. I copied the field names from UpdatePerson.py...
297         update_fields = {}
298         all_fields = new_record
299         for key in all_fields.keys():
300             if key in ['first_name', 'last_name', 'title', 'email',
301                        'password', 'phone', 'url', 'bio', 'accepted_aup',
302                        'enabled']:
303                 update_fields[key] = all_fields[key]
304         api.plshell.UpdatePerson(api.plauth, pointer, update_fields)
305
306         if 'key' in new_record and new_record['key']:
307             # must check this key against the previous one if it exists
308             persons = api.plshell.GetPersons(api.plauth, [pointer], ['key_ids'])
309             person = persons[0]
310             keys = person['key_ids']
311             keys = api.plshell.GetKeys(api.plauth, person['key_ids'])
312             key_exists = False
313             if isinstance(new_record['key'], types.ListType):
314                 new_key = new_record['key'][0]
315             else:
316                 new_key = new_record['key']
317             
318             # Delete all stale keys
319             for key in keys:
320                 if new_record['key'] != key['key']:
321                     api.plshell.DeleteKey(api.plauth, key['key_id'])
322                 else:
323                     key_exists = True
324             if not key_exists:
325                 api.plshell.AddPersonKey(api.plauth, pointer, {'key_type': 'ssh', 'key': new_key})
326
327             # update the openssl key and gid
328             pkey = convert_public_key(new_key)
329             uuid = create_uuid()
330             gid_object = api.auth.hierarchy.create_gid(hrn, uuid, pkey)
331             gid = gid_object.save_to_string(save_parents=True)
332             record['gid'] = gid
333             record = GeniRecord(dict=record)
334             table.update(record)
335
336     elif type == "node":
337         api.plshell.UpdateNode(api.plauth, pointer, new_record)
338
339     else:
340         raise UnknownGeniType(type)
341
342     # update membership for researchers, pis, owners, operators
343     api.update_membership(record, new_record)
344     
345     return 1 
346
347 def remove(api, hrn, type, origin_hrn=None):
348     table = GeniTable()
349     filter = {'hrn': hrn}
350     if type not in ['all', '*']:
351         filter['type'] = type
352     records = table.find(filter)
353     if not records:
354         raise RecordNotFound(hrn)
355     record = records[0]
356     type = record['type']
357
358     credential = api.getCredential()
359     registries = Registries(api)
360
361     # Try to remove the object from the PLCDB of federated agg.
362     # This is attempted before removing the object from the local agg's PLCDB and sfa table
363     if hrn.startswith(api.hrn) and type in ['user', 'slice', 'authority']:
364         for registry in registries:
365             if registry not in [api.hrn]:
366                 try:
367                     result=registries[registry].remove_peer_object(credential, record, origin_hrn)
368                 except:
369                     pass
370     if type == "user":
371         persons = api.plshell.GetPersons(api.plauth, record['pointer'])
372         # only delete this person if he has site ids. if he doesnt, it probably means
373         # he was just removed from a site, not actually deleted
374         if persons and persons[0]['site_ids']:
375             api.plshell.DeletePerson(api.plauth, record['pointer'])
376     elif type == "slice":
377         if api.plshell.GetSlices(api.plauth, record['pointer']):
378             api.plshell.DeleteSlice(api.plauth, record['pointer'])
379     elif type == "node":
380         if api.plshell.GetNodes(api.plauth, record['pointer']):
381             api.plshell.DeleteNode(api.plauth, record['pointer'])
382     elif type == "authority":
383         if api.plshell.GetSites(api.plauth, record['pointer']):
384             api.plshell.DeleteSite(api.plauth, record['pointer'])
385     else:
386         raise UnknownGeniType(type)
387
388     table.remove(record)
389
390     return 1
391
392 def remove_peer_object(api, record, origin_hrn=None):
393     pass
394
395 def register_peer_object(api, record, origin_hrn=None):
396     pass