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