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