the record returned when resolving a user now inculdes the users public keys as strings
[sfa.git] / geni / registry.py
1 ##
2 # Registry is a GeniServer that implements the Registry interface
3
4 import tempfile
5 import os
6 import time
7 import sys
8
9 from geni.util.credential import Credential
10 from geni.util.hierarchy import Hierarchy
11 from geni.util.trustedroot import TrustedRootList
12 from geni.util.cert import Keypair, Certificate
13 from geni.util.gid import GID, create_uuid
14 from geni.util.geniserver import GeniServer
15 from geni.util.geniclient import GeniClient
16 from geni.util.record import GeniRecord
17 from geni.util.rights import RightList
18 from geni.util.genitable import GeniTable
19 from geni.util.geniticket import Ticket
20 from geni.util.excep import *
21 from geni.util.misc import *
22 from geni.util.config import *
23 from geni.util.storage import *
24
25 ##
26 # Convert geni fields to PLC fields for use when registering up updating
27 # registry record in the PLC database
28 #
29 # @param type type of record (user, slice, ...)
30 # @param hrn human readable name
31 # @param geni_fields dictionary of geni fields
32 # @param pl_fields dictionary of PLC fields (output)
33
34 def geni_fields_to_pl_fields(type, hrn, geni_fields, pl_fields):
35     if type == "user":
36         if not "email" in pl_fields:
37             if not "email" in geni_fields:
38                 raise MissingGeniInfo("email")
39             pl_fields["email"] = geni_fields["email"]
40
41         if not "first_name" in pl_fields:
42             pl_fields["first_name"] = "geni"
43
44         if not "last_name" in pl_fields:
45             pl_fields["last_name"] = hrn
46
47     elif type == "slice":
48         if not "instantiation" in pl_fields:
49             pl_fields["instantiation"] = "delegated"  # "plc-instantiated"
50         if not "name" in pl_fields:
51             pl_fields["name"] = hrn_to_pl_slicename(hrn)
52         if not "max_nodes" in pl_fields:
53             pl_fields["max_nodes"] = 10
54
55     elif type == "node":
56         if not "hostname" in pl_fields:
57             if not "dns" in geni_fields:
58                 raise MissingGeniInfo("dns")
59             pl_fields["hostname"] = geni_fields["dns"]
60
61         if not "model" in pl_fields:
62             pl_fields["model"] = "geni"
63
64     elif type == "sa":
65         pl_fields["login_base"] = hrn_to_pl_login_base(hrn)
66
67         if not "name" in pl_fields:
68             pl_fields["name"] = hrn
69
70         if not "abbreviated_name" in pl_fields:
71             pl_fields["abbreviated_name"] = hrn
72
73         if not "enabled" in pl_fields:
74             pl_fields["enabled"] = True
75
76         if not "is_public" in pl_fields:
77             pl_fields["is_public"] = True
78
79 ##
80 # Registry is a GeniServer that serves registry and slice operations at PLC.
81
82 class Registry(GeniServer):
83     ##
84     # Create a new registry object.
85     #
86     # @param ip the ip address to listen on
87     # @param port the port to listen on
88     # @param key_file private key filename of registry
89     # @param cert_file certificate filename containing public key (could be a GID file)
90
91     def __init__(self, ip, port, key_file, cert_file, config = '/usr/share/geniwrapper/geni/util/geni_config'):
92         GeniServer.__init__(self, ip, port, key_file, cert_file)
93
94         # get PL account settings from config module
95         self.pl_auth = get_pl_auth()
96
97         # connect to planetlab
98         if "Url" in self.pl_auth:
99             self.connect_remote_shell()
100         else:
101             self.connect_local_shell()
102
103         self.key_file = key_file
104         self.cert_file = cert_file
105         self.config = Config(config)
106         self.basedir = self.config.GENI_BASE_DIR + os.sep
107         self.server_basedir = self.basedir + os.sep + "geni" + os.sep
108         self.hrn = self.config.GENI_INTERFACE_HRN
109
110         # get peer registry information
111         registries_file = self.server_basedir + os.sep + 'registries.xml'
112         connection_dict = {'hrn': '', 'addr': '', 'port': ''} 
113         self.registry_info = XmlStorage(registries_file, {'registries': {'registry': [connection_dict]}})
114         self.registry_info.load()
115         self.connectRegistry()
116         self.connectRegistries()
117         
118  
119     ##
120     # Connect to a remote shell via XMLRPC
121
122     def connect_remote_shell(self):
123         from geni.util import remoteshell
124         self.shell = remoteshell.RemoteShell()
125
126     ##
127     # Connect to a local shell via local API functions
128
129     def connect_local_shell(self):
130         import PLC.Shell
131         self.shell = PLC.Shell.Shell(globals = globals())
132
133     ##
134     # Register the server RPCs for the registry
135
136     def register_functions(self):
137         GeniServer.register_functions(self)
138         # registry interface
139         self.server.register_function(self.create_gid)
140         self.server.register_function(self.get_self_credential)
141         self.server.register_function(self.get_credential)
142         self.server.register_function(self.get_gid)
143         self.server.register_function(self.get_ticket)
144         self.server.register_function(self.register)
145         self.server.register_function(self.remove)
146         self.server.register_function(self.update)
147         self.server.register_function(self.list)
148         self.server.register_function(self.resolve)
149
150
151     def loadCredential(self):
152         """
153         Attempt to load credential from file if it exists. If it doesnt get
154         credential from registry.
155         """
156
157         # see if this file exists
158         # XX This is really the aggregate's credential. Using this is easier than getting
159         # the registry's credential from iteslf (ssl errors).   
160         ma_cred_filename = self.server_basedir + os.sep + "agg." + self.hrn + ".ma.cred"
161         try:
162             self.credential = Credential(filename = ma_cred_filename)
163         except IOError:
164             self.credential = self.getCredentialFromRegistry()
165
166     def getCredentialFromRegistry(self):
167         """
168         Get our current credential from the registry.
169         """
170         # get self credential
171         self_cred_filename = self.server_basedir + os.sep + "smgr." + self.hrn + ".cred"
172         self_cred = self.registry.get_credential(None, 'ma', self.hrn)
173         self_cred.save_to_file(self_cred_filename, save_parents=True)
174
175         # get ma credential
176         ma_cred_filename = self.server_basedir + os.sep + "smgr." + self.hrn + ".sa.cred"
177         ma_cred = self.registry.get_credential(self_cred, 'sa', self.hrn)
178         ma_cred.save_to_file(ma_cred_filename, save_parents=True)
179         return ma_cred
180
181     def connectRegistry(self):
182         """
183         Connect to the registry
184         """
185         # connect to registry using GeniClient
186         address = self.config.GENI_REGISTRY_HOSTNAME
187         port = self.config.GENI_REGISTRY_PORT
188         url = 'http://%(address)s:%(port)s' % locals()
189         self.registry = GeniClient(url, self.key_file, self.cert_file)
190
191     def connectRegistries(self):
192         """
193         Get connection details for the trusted peer registries from file and 
194         create an GeniClient connection to each. 
195         """
196         self.registries= {}
197         registries = self.registry_info['registries']['registry']
198         if isinstance(registries, dict):
199             registries = [registries]
200         if isinstance(registries, list):
201             for registry in registries:
202                 # create xmlrpc connection using GeniClient
203                 hrn, address, port = registry['hrn'], registry['addr'], registry['port']
204                 url = 'http://%(address)s:%(port)s' % locals()
205                 self.registries[hrn] = GeniClient(url, self.key_file, self.cert_file)
206
207     ##
208     # Given an authority name, return the information for that authority. This
209     # is basically a stub that calls the hierarchy module.
210     #
211     # @param auth_hrn human readable name of authority
212
213     def get_auth_info(self, auth_hrn):
214         return self.hierarchy.get_auth_info(auth_hrn)
215
216     ##
217     # Given an authority name, return the database table for that authority. If
218     # the database table does not exist, then one will be automatically
219     # created.
220     #
221     # @param auth_name human readable name of authority
222
223     def get_auth_table(self, auth_name):
224         auth_info = self.get_auth_info(auth_name)
225
226         table = GeniTable(hrn=auth_name,
227                           cninfo=auth_info.get_dbinfo())
228
229         # if the table doesn't exist, then it means we haven't put any records
230         # into this authority yet.
231
232         if not table.exists():
233             print "Registry: creating table for authority", auth_name
234             table.create()
235
236         return table
237
238     ##
239     # Verify that an authority belongs to this registry. This is basically left
240     # up to the implementation of the hierarchy module. If the specified name
241     # does not belong to this registry, an exception is thrown indicating the
242     # caller should contact someone else.
243     #
244     # @param auth_name human readable name of authority
245
246     def verify_auth_belongs_to_me(self, name):
247         # get_auth_info will throw an exception if the authority does not
248         # exist
249         self.get_auth_info(name)
250
251     ##
252     # Verify that an object belongs to this registry. By extension, this implies
253     # that the authority that owns the object belongs to this registry. If the
254     # object does not belong to this registry, then an exception is thrown.
255     #
256     # @param name human readable name of object
257
258     def verify_object_belongs_to_me(self, name):
259         auth_name = get_authority(name)
260         if not auth_name:
261             # the root authority belongs to the registry by default?
262             # TODO: is this true?
263             return
264         self.verify_auth_belongs_to_me(auth_name)
265
266     ##
267     # Verify that the object_gid that was specified in the credential allows
268     # permission to the object 'name'. This is done by a simple prefix test.
269     # For example, an object_gid for planetlab.us.arizona would match the
270     # objects planetlab.us.arizona.slice1 and planetlab.us.arizona.
271     #
272     # @param name human readable name to test
273
274     def verify_object_permission(self, name):
275         object_hrn = self.object_gid.get_hrn()
276         if object_hrn == name:
277             return
278         if name.startswith(object_hrn + "."):
279             return
280         raise PermissionError(name)
281
282     ##
283     # Fill in the planetlab-specific fields of a Geni record. This involves
284     # calling the appropriate PLC methods to retrieve the database record for
285     # the object.
286     #
287     # PLC data is filled into the pl_info field of the record.
288     #
289     # @param record record to fill in fields (in/out param)
290
291     def fill_record_pl_info(self, record):
292         type = record.get_type()
293         pointer = record.get_pointer()
294
295         # records with pointer==-1 do not have plc info associated with them.
296         # for example, the top level authority records which are
297         # authorities, but not PL "sites"
298         if pointer == -1:
299             record.set_pl_info({})
300             return
301
302         if (type == "sa") or (type == "ma"):
303             pl_res = self.shell.GetSites(self.pl_auth, [pointer])
304         elif (type == "slice"):
305             pl_res = self.shell.GetSlices(self.pl_auth, [pointer])
306         elif (type == "user"):
307             pl_res = self.shell.GetPersons(self.pl_auth, [pointer])
308             key_ids = pl_res[0]['key_ids']
309             keys = self.shell.GetKeys(self.pl_auth, key_ids)
310             pubkeys = []
311             if keys:
312                 pubkeys = [key['key'] for key in keys]
313             pl_res[0]['keys'] = pubkeys
314         elif (type == "node"):
315             pl_res = self.shell.GetNodes(self.pl_auth, [pointer])
316         else:
317             raise UnknownGeniType(type)
318
319         if not pl_res:
320             # the planetlab record no longer exists
321             # TODO: delete the geni record ?
322             raise PlanetLabRecordDoesNotExist(record.get_name())
323
324         record.set_pl_info(pl_res[0])
325
326     ##
327     # Look up user records given PLC user-ids. This is used as part of the
328     # process for reverse-mapping PLC records into Geni records.
329     #
330     # @param auth_table database table for the authority that holds the user records
331     # @param user_id_list list of user ids
332     # @param role either "*" or a string describing the role to look for ("pi", "user", ...)
333     #
334     # TODO: This function currently only searches one authority because it would
335     # be inefficient to brute-force search all authorities for a user id. The
336     # solution would likely be to implement a reverse mapping of user-id to
337     # (type, hrn) pairs.
338
339     def lookup_users(self, auth_table, user_id_list, role="*"):
340         record_list = []
341         for person_id in user_id_list:
342             user_records = auth_table.find("user", person_id, "pointer")
343             for user_record in user_records:
344                 self.fill_record_info(user_record)
345
346                 user_roles = user_record.get_pl_info().get("roles")
347                 if (role=="*") or (role in user_roles):
348                     record_list.append(user_record.get_name())
349         return record_list
350
351     ##
352     # Fill in the geni-specific fields of the record.
353     #
354     # Note: It is assumed the fill_record_pl_info() has already been performed
355     # on the record.
356
357     def fill_record_geni_info(self, record):
358         geni_info = {}
359         type = record.get_type()
360
361         if (type == "slice"):
362             auth_table = self.get_auth_table(get_authority(record.get_name()))
363             person_ids = record.pl_info.get("person_ids", [])
364             researchers = self.lookup_users(auth_table, person_ids)
365             geni_info['researcher'] = researchers
366
367         elif (type == "sa"):
368             auth_table = self.get_auth_table(record.get_name())
369             person_ids = record.pl_info.get("person_ids", [])
370             pis = self.lookup_users(auth_table, person_ids, "pi")
371             geni_info['pi'] = pis
372             # TODO: OrganizationName
373
374         elif (type == "ma"):
375             auth_table = self.get_auth_table(record.get_name())
376             person_ids = record.pl_info.get("person_ids", [])
377             operators = self.lookup_users(auth_table, person_ids, "tech")
378             geni_info['operator'] = operators
379             # TODO: OrganizationName
380
381             auth_table = self.get_auth_table(record.get_name())
382             person_ids = record.pl_info.get("person_ids", [])
383             owners = self.lookup_users(auth_table, person_ids, "admin")
384             geni_info['owner'] = owners
385
386         elif (type == "node"):
387             geni_info['dns'] = record.pl_info.get("hostname", "")
388             # TODO: URI, LatLong, IP, DNS
389
390         elif (type == "user"):
391             geni_info['email'] = record.pl_info.get("email", "")
392             # TODO: PostalAddress, Phone
393
394         record.set_geni_info(geni_info)
395
396     ##
397     # Given a Geni record, fill in the PLC-specific and Geni-specific fields
398     # in the record.
399
400     def fill_record_info(self, record):
401         self.fill_record_pl_info(record)
402         self.fill_record_geni_info(record)
403
404     def update_membership_list(self, oldRecord, record, listName, addFunc, delFunc):
405         # get a list of the HRNs tht are members of the old and new records\r
406         if oldRecord:\r
407             if oldRecord.pl_info == None:\r
408                 oldRecord.pl_info = {}\r
409             oldList = oldRecord.get_geni_info().get(listName, [])\r
410         else:\r
411             oldList = []\r
412         newList = record.get_geni_info().get(listName, [])\r
413 \r
414         # if the lists are the same, then we don't have to update anything\r
415         if (oldList == newList):\r
416             return\r
417 \r
418         # build a list of the new person ids, by looking up each person to get\r
419         # their pointer\r
420         newIdList = []\r
421         for hrn in newList:\r
422             userRecord = self.resolve_raw("user", hrn)[0]\r
423             newIdList.append(userRecord.get_pointer())\r
424 \r
425         # build a list of the old person ids from the person_ids field of the\r
426         # pl_info\r
427         if oldRecord:\r
428             oldIdList = oldRecord.pl_info.get("person_ids", [])\r
429             containerId = oldRecord.get_pointer()\r
430         else:\r
431             # if oldRecord==None, then we are doing a Register, instead of an\r
432             # update.\r
433             oldIdList = []\r
434             containerId = record.get_pointer()\r
435 \r
436         # add people who are in the new list, but not the oldList\r
437         for personId in newIdList:\r
438             if not (personId in oldIdList):\r
439                 print "adding id", personId, "to", record.get_name()\r
440                 addFunc(self.pl_auth, personId, containerId)\r
441 \r
442         # remove people who are in the old list, but not the new list\r
443         for personId in oldIdList:\r
444             if not (personId in newIdList):\r
445                 print "removing id", personId, "from", record.get_name()\r
446                 delFunc(self.pl_auth, personId, containerId)\r
447 \r
448     def update_membership(self, oldRecord, record):\r
449         if record.type == "slice":\r
450             self.update_membership_list(oldRecord, record, 'researcher',\r
451                                         self.shell.AddPersonToSlice,\r
452                                         self.shell.DeletePersonFromSlice)\r
453         elif record.type == "sa":\r
454             # TODO\r
455             pass\r
456         elif record.type == "ma":\r
457             # TODO\r
458             pass
459
460     ##
461     # GENI API: register
462     #
463     # Register an object with the registry. In addition to being stored in the
464     # Geni database, the appropriate records will also be created in the
465     # PLC databases
466     #
467     # @param cred credential string
468     # @param record_dict dictionary containing record fields
469
470     def register(self, cred, record_dict):
471         self.decode_authentication(cred, "register")
472
473         record = GeniRecord(dict = record_dict)
474         type = record.get_type()
475         name = record.get_name()
476
477         auth_name = get_authority(name)
478         self.verify_object_permission(auth_name)
479         auth_info = self.get_auth_info(auth_name)
480         table = self.get_auth_table(auth_name)
481
482         pkey = None
483
484         # check if record already exists
485         existing_records = table.resolve(type, name)
486         if existing_records:
487             raise ExistingRecord(name)
488
489         if (type == "sa") or (type=="ma"):
490             # update the tree
491             if not self.hierarchy.auth_exists(name):
492                 self.hierarchy.create_auth(name)
493
494             # authorities are special since they are managed by the registry
495             # rather than by the caller. We create our own GID for the
496             # authority rather than relying on the caller to supply one.
497
498             # get the GID from the newly created authority
499             child_auth_info = self.get_auth_info(name)
500             gid = auth_info.get_gid_object()
501             record.set_gid(gid.save_to_string(save_parents=True))
502
503             geni_fields = record.get_geni_info()
504             site_fields = record.get_pl_info()
505
506             # if registering a sa, see if a ma already exists
507             # if registering a ma, see if a sa already exists
508             if (type == "sa"):
509                 other_rec = table.resolve("ma", record.get_name())
510             elif (type == "ma"):
511                 other_rec = table.resolve("sa", record.get_name())
512
513             if other_rec:
514                 print "linking ma and sa to the same plc site"
515                 pointer = other_rec[0].get_pointer()
516             else:
517                 geni_fields_to_pl_fields(type, name, geni_fields, site_fields)
518                 print "adding site with fields", site_fields
519                 pointer = self.shell.AddSite(self.pl_auth, site_fields)
520
521             record.set_pointer(pointer)
522
523         elif (type == "slice"):
524             geni_fields = record.get_geni_info()
525             slice_fields = record.get_pl_info()
526
527             geni_fields_to_pl_fields(type, name, geni_fields, slice_fields)
528
529             pointer = self.shell.AddSlice(self.pl_auth, slice_fields)
530             record.set_pointer(pointer)
531
532         elif (type == "user"):
533             geni_fields = record.get_geni_info()
534             user_fields = record.get_pl_info()
535
536             geni_fields_to_pl_fields(type, name, geni_fields, user_fields)
537
538             pointer = self.shell.AddPerson(self.pl_auth, user_fields)
539             record.set_pointer(pointer)
540
541         elif (type == "node"):
542             geni_fields = record.get_geni_info()
543             node_fields = record.get_pl_info()
544
545             geni_fields_to_pl_fields(type, name, geni_fields, node_fields)
546
547             login_base = hrn_to_pl_login_base(auth_name)
548
549             print "calling addnode with", login_base, node_fields
550             pointer = self.shell.AddNode(self.pl_auth, login_base, node_fields)
551             record.set_pointer(pointer)
552
553         else:
554             raise UnknownGeniType(type)
555
556         table.insert(record)
557
558         # update membership for researchers, pis, owners, operators
559         self.update_membership(None, record)
560
561         return record.get_gid_object().save_to_string(save_parents=True)
562
563     ##
564     # GENI API: remove
565     #
566     # Remove an object from the registry. If the object represents a PLC object,
567     # then the PLC records will also be removed.
568     #
569     # @param cred credential string
570     # @param record_dict dictionary containing record fields. The only relevant
571     #     fields of the record are 'name' and 'type', which are used to lookup
572     #     the current copy of the record in the Geni database, to make sure
573     #     that the appopriate record is removed.
574
575     def remove(self, cred, type, hrn):
576         self.decode_authentication(cred, "remove")
577
578         self.verify_object_permission(hrn)
579
580         auth_name = get_authority(hrn)
581         table = self.get_auth_table(auth_name)
582
583         record_list = table.resolve(type, hrn)
584         if not record_list:
585             raise RecordNotFound(hrn)
586         record = record_list[0]
587
588         # TODO: sa, ma
589         if type == "user":
590             self.shell.DeletePerson(self.pl_auth, record.get_pointer())
591         elif type == "slice":
592             self.shell.DeleteSlice(self.pl_auth, record.get_pointer())
593         elif type == "node":
594             self.shell.DeleteNode(self.pl_auth, record.get_pointer())
595         elif (type == "sa") or (type == "ma"):
596             if (type == "sa"):
597                 other_rec = table.resolve("ma", record.get_name())
598             elif (type == "ma"):
599                 other_rec = table.resolve("sa", record.get_name())
600
601             if other_rec:
602                 # sa and ma both map to a site, so if we are deleting one
603                 # but the other still exists, then do not delete the site
604                 print "not removing site", record.get_name(), "because either sa or ma still exists"
605                 pass
606             else:
607                 print "removing site", record.get_name()
608                 self.shell.DeleteSite(self.pl_auth, record.get_pointer())
609         else:
610             raise UnknownGeniType(type)
611
612         table.remove(record)
613
614         return True
615
616     ##
617     # GENI API: Update
618     #
619     # Update an object in the registry. Currently, this only updates the
620     # PLC information associated with the record. The Geni fields (name, type,
621     # GID) are fixed.
622     #
623     # The record is expected to have the pl_info field filled in with the data
624     # that should be updated.
625     #
626     # TODO: The geni_info member of the record should be parsed and the pl_info
627     # adjusted as necessary (add/remove users from a slice, etc)
628     #
629     # @param cred credential string specifying rights of the caller
630     # @param record a record dictionary to be updated
631
632     def update(self, cred, record_dict):
633         self.decode_authentication(cred, "update")
634
635         record = GeniRecord(dict = record_dict)
636         type = record.get_type()
637
638         self.verify_object_permission(record.get_name())
639
640         auth_name = get_authority(record.get_name())
641         if not auth_name:
642             auth_name = record.get_name()
643         table = self.get_auth_table(auth_name)
644
645         # make sure the record exists
646         existing_record_list = table.resolve(type, record.get_name())
647         if not existing_record_list:
648             raise RecordNotFound(record.get_name())
649         existing_record = existing_record_list[0]
650
651         # Update_membership needs the membership lists in the existing record
652         # filled in, so it can see if members were added or removed
653         self.fill_record_info(existing_record)
654
655         # Use the pointer from the existing record, not the one that the user
656         # gave us. This prevents the user from inserting a forged pointer
657         pointer = existing_record.get_pointer()
658
659         # update the PLC information that was specified with the record
660
661         if (type == "sa") or (type == "ma"):
662             self.shell.UpdateSite(self.pl_auth, pointer, record.get_pl_info())
663
664         elif type == "slice":
665             self.shell.UpdateSlice(self.pl_auth, pointer, record.get_pl_info())
666
667         elif type == "user":
668             # SMBAKER: UpdatePerson only allows a limited set of fields to be
669             #    updated. Ideally we should have a more generic way of doing
670             #    this. I copied the field names from UpdatePerson.py...
671             update_fields = {}
672             all_fields = record.get_pl_info()
673             for key in all_fields.keys():
674                 if key in ['first_name', 'last_name', 'title', 'email',
675                            'password', 'phone', 'url', 'bio', 'accepted_aup',\r
676                            'enabled']:
677                     update_fields[key] = all_fields[key]
678             self.shell.UpdatePerson(self.pl_auth, pointer, update_fields)
679
680         elif type == "node":
681             self.shell.UpdateNode(self.pl_auth, pointer, record.get_pl_info())
682
683         else:
684             raise UnknownGeniType(type)
685
686         # update membership for researchers, pis, owners, operators\r
687         self.update_membership(existing_record, record)
688
689     ##
690     # List the records in an authority. The objectGID in the supplied credential
691     # should name the authority that will be listed.
692     #
693     # TODO: List doesn't take an hrn and uses the hrn contained in the
694     #    objectGid of the credential. Does this mean the only way to list an
695     #    authority is by having a credential for that authority?
696     #
697     # @param cred credential string specifying rights of the caller
698     #
699     # @return list of record dictionaries
700     def list(self, cred, auth_hrn):
701         self.decode_authentication(cred, "list")
702
703         if not self.hierarchy.auth_exists(auth_hrn):
704             raise MissingAuthority(auth_hrn)
705
706         table = self.get_auth_table(auth_hrn)
707
708         records = table.list()
709
710         good_records = []
711         for record in records:
712             try:
713                 self.fill_record_info(record)
714                 good_records.append(record)
715             except PlanetLabRecordDoesNotExist:
716                 # silently drop the ones that are missing in PL.
717                 # is this the right thing to do?
718                 print "ignoring geni record ", record.get_name(), " because pl record does not exist"
719                 table.remove(record)
720
721         dicts = []
722         for record in good_records:
723             dicts.append(record.as_dict())
724
725         return dicts
726
727         return dict_list
728
729     ##
730     # Resolve a record. This is an internal version of the Resolve API call
731     # and returns records in record object format rather than dictionaries
732     # that may be sent over XMLRPC.
733     #
734     # @param type type of record to resolve (user | sa | ma | slice | node)
735     # @param name human readable name of object
736     # @param must_exist if True, throw an exception if no records are found
737     #
738     # @return a list of record objects, or an empty list []
739
740     def resolve_raw(self, type, name, must_exist=True):
741         auth_name = get_authority(name)
742         if not auth_name:
743             auth_name = name
744         table = self.get_auth_table(auth_name)
745         records = table.resolve(type, name)
746         if (not records) and must_exist:
747             raise RecordNotFound(name)
748
749         good_records = []
750         for record in records:
751             try:
752                 self.fill_record_info(record)
753                 good_records.append(record)
754             except PlanetLabRecordDoesNotExist:
755                 # silently drop the ones that are missing in PL.
756                 # is this the right thing to do?
757                 print "ignoring geni record ", record.get_name(), "because pl record does not exist"
758                 table.remove(record)
759
760         return good_records
761
762     ##
763     # GENI API: Resolve
764     #
765     # This is a wrapper around resolve_raw that converts records objects into
766     # dictionaries before returning them to the user.
767     #
768     # @param cred credential string authorizing the caller
769     # @param name human readable name to resolve
770     #
771     # @return a list of record dictionaries, or an empty list
772
773     def resolve(self, cred, name):
774         self.decode_authentication(cred, "resolve")
775         
776         try:
777             records = self.resolve_raw("*", name)
778         except:
779             records = []
780             for registry in self.registries:
781                 if name.startswith(registry):
782                     records = self.registries[registry].resolve(self.credential, name)
783                 
784
785         dicts = []
786         for record in records:
787             dicts.append(record.as_dict())
788
789         return dicts
790
791     ##
792     # GENI API: get_gid
793     #
794     # Retrieve the GID for an object. This function looks up a record in the
795     # registry and returns the GID of the record if it exists.
796     # TODO: Is this function needed? It's a shortcut for Resolve()
797     #
798     # @param name hrn to look up
799     #
800     # @return the string representation of a GID object
801
802     def get_gid(self, name):
803         self.verify_object_belongs_to_me(name)
804         records = self.resolve_raw("*", name)
805         gid_string_list = []
806         for record in records:
807             gid = record.get_gid_object()
808             gid_string_list.append(gid.save_to_string(save_parents=True))
809         return gid_string_list
810
811     ##
812     # Determine tje rights that an object should have. The rights are entirely
813     # dependent on the type of the object. For example, users automatically
814     # get "refresh", "resolve", and "info".
815     #
816     # @param type the type of the object (user | sa | ma | slice | node)
817     # @param name human readable name of the object (not used at this time)
818     #
819     # @return RightList object containing rights
820
821     def determine_rights(self, type, name):
822         rl = RightList()
823
824         # rights seem to be somewhat redundant with the type of the credential.
825         # For example, a "sa" credential implies the authority right, because
826         # a sa credential cannot be issued to a user who is not an owner of
827         # the authority
828
829         if type == "user":
830             rl.add("refresh")
831             rl.add("resolve")
832             rl.add("info")
833         elif type == "sa":
834             rl.add("authority,sa")
835         elif type == "ma":
836             rl.add("authority,ma")
837         elif type == "slice":
838             rl.add("refresh")
839             rl.add("embed")
840             rl.add("bind")
841             rl.add("control")
842             rl.add("info")
843         elif type == "component":
844             rl.add("operator")
845
846         return rl
847
848     ##
849     # GENI API: Get_self_credential
850     #
851     # Get_self_credential a degenerate version of get_credential used by a
852     # client to get his initial credential when he doesn't have one. This is
853     # the same as get_credential(..., cred=None,...).
854     #
855     # The registry ensures that the client is the principal that is named by
856     # (type, name) by comparing the public key in the record's GID to the
857     # private key used to encrypt the client-side of the HTTPS connection. Thus
858     # it is impossible for one principal to retrieve another principal's
859     # credential without having the appropriate private key.
860     #
861     # @param type type of object (user | slice | sa | ma | node
862     # @param name human readable name of object
863     #
864     # @return the string representation of a credential object
865
866     def get_self_credential(self, type, name):
867         self.verify_object_belongs_to_me(name)
868
869         auth_hrn = get_authority(name)
870         if not auth_hrn:
871             auth_hrn = name
872         auth_info = self.get_auth_info(auth_hrn)
873         # find a record that matches
874         records = self.resolve_raw(type, name, must_exist=True)
875         record = records[0]
876
877         gid = record.get_gid_object()
878         peer_cert = self.server.peer_cert
879         if not peer_cert.is_pubkey(gid.get_pubkey()):
880            raise ConnectionKeyGIDMismatch(gid.get_subject())
881
882         # create the credential
883         gid = record.get_gid_object()
884         cred = Credential(subject = gid.get_subject())
885         cred.set_gid_caller(gid)
886         cred.set_gid_object(gid)
887         cred.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn)
888         cred.set_pubkey(gid.get_pubkey())
889
890         rl = self.determine_rights(type, name)
891         cred.set_privileges(rl)
892
893         # determine the type of credential that we want to use as a parent for
894         # this credential.
895
896         if (type == "ma") or (type == "node"):
897             auth_kind = "authority,ma"
898         else: # user, slice, sa
899             auth_kind = "authority,sa"
900
901         cred.set_parent(self.hierarchy.get_auth_cred(auth_hrn, kind=auth_kind))
902
903         cred.encode()
904         cred.sign()
905
906         return cred.save_to_string(save_parents=True)
907
908     ##
909     # verify_cancreate_credential
910     #
911     # Verify that a user can retrieve a particular type of credential. For
912     # slices, the user must be on the researcher list. For SA and MA the user
913     # must be on the pi and operator lists respectively.
914
915     def verify_cancreate_credential(self, src_cred, record):
916         type = record.get_type()
917         cred_object_hrn = src_cred.get_gid_object().get_hrn()
918         config = Config()
919         if cred_object_hrn in [config.GENI_REGISTRY_ROOT_AUTH]:
920             return
921         if type=="slice":
922             researchers = record.get_geni_info().get("researcher", [])
923             if not (cred_object_hrn in researchers):
924                 raise PermissionError(cred_object_hrn + " is not in researcher list for " + record.get_name())
925         elif type == "sa":
926             pis = record.get_geni_info().get("pi", [])
927             if not (cred_object_hrn in pis):
928                 raise PermissionError(cred_object_hrn + " is not in pi list for " + record.get_name())
929         elif type == "ma":
930             operators = record.get_geni_info().get("operator", [])
931             if not (cred_object_hrn in operators):
932                 raise PermissionError(cred_object_hrn + " is not in operator list for " + record.get_name())
933
934     ##
935     # GENI API: Get_credential
936     #
937     # Retrieve a credential for an object.
938     #
939     # If cred==None, then the behavior reverts to get_self_credential()
940     #
941     # @param cred credential object specifying rights of the caller
942     # @param type type of object (user | slice | sa | ma | node)
943     # @param name human readable name of object
944     #
945     # @return the string representation of a credental object
946
947     def get_credential(self, cred, type, name):
948         if not cred:
949             return self.get_self_credential(type, name)
950
951         self.decode_authentication(cred, "getcredential")
952
953         self.verify_object_belongs_to_me(name)
954
955         auth_hrn = get_authority(name)
956         if not auth_hrn:
957             auth_hrn = name
958         auth_info = self.get_auth_info(auth_hrn)
959
960         records = self.resolve_raw(type, name, must_exist=True)
961         record = records[0]
962
963         # verify_cancreate_credential requires that the member lists
964         # (researchers, pis, etc) be filled in
965         self.fill_record_info(record)
966
967         self.verify_cancreate_credential(self.client_cred, record)
968
969         # TODO: Check permission that self.client_cred can access the object
970
971         object_gid = record.get_gid_object()
972         new_cred = Credential(subject = object_gid.get_subject())
973         new_cred.set_gid_caller(self.client_gid)
974         new_cred.set_gid_object(object_gid)
975         new_cred.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn)
976         new_cred.set_pubkey(object_gid.get_pubkey())
977
978         rl = self.determine_rights(type, name)
979         new_cred.set_privileges(rl)
980
981         # determine the type of credential that we want to use as a parent for
982         # this credential.
983
984         if (type == "ma") or (type == "node"):
985             auth_kind = "authority,ma"
986         else: # user, slice, sa
987             auth_kind = "authority,sa"
988
989         new_cred.set_parent(self.hierarchy.get_auth_cred(auth_hrn, kind=auth_kind))
990
991         new_cred.encode()
992         new_cred.sign()
993
994         return new_cred.save_to_string(save_parents=True)
995
996     ##
997     # GENI API: get_ticket
998     #
999     # Retrieve a ticket. This operation is currently implemented on PLC
1000     # only (see SFA, engineering decisions); it is not implemented on
1001     # components.
1002     #
1003     # The ticket is filled in with information from the PLC database. This
1004     # information includes resources, and attributes such as user keys and
1005     # initscripts.
1006     #
1007     # @param cred credential string
1008     # @param name name of the slice to retrieve a ticket for
1009     # @param rspec resource specification dictionary
1010     #
1011     # @return the string representation of a ticket object
1012
1013     def get_ticket(self, cred, name, rspec):
1014         self.decode_authentication(cred, "getticket")
1015
1016         self.verify_object_belongs_to_me(name)
1017
1018         self.verify_object_permission(name)
1019
1020         # XXX much of this code looks like get_credential... are they so similar
1021         # that they should be combined?
1022
1023         auth_hrn = get_authority(name)
1024         if not auth_hrn:
1025             auth_hrn = name
1026         auth_info = self.get_auth_info(auth_hrn)
1027
1028         records = self.resolve_raw("slice", name, must_exist=True)
1029         record = records[0]
1030
1031         object_gid = record.get_gid_object()
1032         new_ticket = Ticket(subject = object_gid.get_subject())
1033         new_ticket.set_gid_caller(self.client_gid)
1034         new_ticket.set_gid_object(object_gid)
1035         new_ticket.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn)
1036         new_ticket.set_pubkey(object_gid.get_pubkey())
1037
1038         self.fill_record_info(record)
1039
1040         (attributes, rspec) = self.record_to_slice_info(record)
1041
1042         new_ticket.set_attributes(attributes)
1043         new_ticket.set_rspec(rspec)
1044
1045         new_ticket.set_parent(AuthHierarchy.get_auth_ticket(auth_hrn))
1046
1047         new_ticket.encode()
1048         new_ticket.sign()
1049
1050         return new_ticket.save_to_string(save_parents=True)
1051
1052     ##
1053     # GENI_API: Create_gid
1054     #
1055     # Create a new GID. For MAs and SAs that are physically located on the
1056     # registry, this allows a owner/operator/PI to create a new GID and have it
1057     # signed by his respective authority.
1058     #
1059     # @param cred credential of caller
1060     # @param name hrn for new GID
1061     # @param uuid unique identifier for new GID
1062     # @param pkey_string public-key string (TODO: why is this a string and not a keypair object?)
1063     #
1064     # @return the string representation of a GID object
1065
1066     def create_gid(self, cred, name, uuid, pubkey_str):
1067         self.decode_authentication(cred, "getcredential")
1068
1069         self.verify_object_belongs_to_me(name)
1070
1071         self.verify_object_permission(name)
1072
1073         if uuid == None:
1074             uuid = create_uuid()
1075
1076         pkey = Keypair()
1077         pkey.load_pubkey_from_string(pubkey_str)
1078         gid = self.hierarchy.create_gid(name, uuid, pkey)
1079
1080         return gid.save_to_string(save_parents=True)
1081