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