2 # Registry is a GeniServer that implements the Registry interface
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 *
26 # Convert geni fields to PLC fields for use when registering up updating
27 # registry record in the PLC database
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)
34 def geni_fields_to_pl_fields(type, hrn, geni_fields, pl_fields):
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"]
41 if not "first_name" in pl_fields:
42 pl_fields["first_name"] = "geni"
44 if not "last_name" in pl_fields:
45 pl_fields["last_name"] = hrn
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
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"]
61 if not "model" in pl_fields:
62 pl_fields["model"] = "geni"
65 pl_fields["login_base"] = hrn_to_pl_login_base(hrn)
67 if not "name" in pl_fields:
68 pl_fields["name"] = hrn
70 if not "abbreviated_name" in pl_fields:
71 pl_fields["abbreviated_name"] = hrn
73 if not "enabled" in pl_fields:
74 pl_fields["enabled"] = True
76 if not "is_public" in pl_fields:
77 pl_fields["is_public"] = True
80 # Registry is a GeniServer that serves registry and slice operations at PLC.
82 class Registry(GeniServer):
84 # Create a new registry object.
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)
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)
94 # get PL account settings from config module
95 self.pl_auth = get_pl_auth()
97 # connect to planetlab
98 if "Url" in self.pl_auth:
99 self.connect_remote_shell()
101 self.connect_local_shell()
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
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()
120 # Connect to a remote shell via XMLRPC
122 def connect_remote_shell(self):
123 from geni.util import remoteshell
124 self.shell = remoteshell.RemoteShell()
127 # Connect to a local shell via local API functions
129 def connect_local_shell(self):
131 self.shell = PLC.Shell.Shell(globals = globals())
134 # Register the server RPCs for the registry
136 def register_functions(self):
137 GeniServer.register_functions(self)
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)
151 def loadCredential(self):
153 Attempt to load credential from file if it exists. If it doesnt get
154 credential from registry.
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"
162 self.credential = Credential(filename = ma_cred_filename)
164 self.credential = self.getCredentialFromRegistry()
166 def getCredentialFromRegistry(self):
168 Get our current credential from the registry.
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)
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)
181 def connectRegistry(self):
183 Connect to the registry
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)
191 def connectRegistries(self):
193 Get connection details for the trusted peer registries from file and
194 create an GeniClient connection to each.
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 if not hrn or not address or not port:
205 hrn, address, port = registry['hrn'], registry['addr'], registry['port']
206 url = 'http://%(address)s:%(port)s' % locals()
207 self.registries[hrn] = GeniClient(url, self.key_file, self.cert_file)
210 # Given an authority name, return the information for that authority. This
211 # is basically a stub that calls the hierarchy module.
213 # @param auth_hrn human readable name of authority
215 def get_auth_info(self, auth_hrn):
216 return self.hierarchy.get_auth_info(auth_hrn)
219 # Given an authority name, return the database table for that authority. If
220 # the database table does not exist, then one will be automatically
223 # @param auth_name human readable name of authority
225 def get_auth_table(self, auth_name):
226 auth_info = self.get_auth_info(auth_name)
228 table = GeniTable(hrn=auth_name,
229 cninfo=auth_info.get_dbinfo())
231 # if the table doesn't exist, then it means we haven't put any records
232 # into this authority yet.
234 if not table.exists():
235 print "Registry: creating table for authority", auth_name
241 # Verify that an authority belongs to this registry. This is basically left
242 # up to the implementation of the hierarchy module. If the specified name
243 # does not belong to this registry, an exception is thrown indicating the
244 # caller should contact someone else.
246 # @param auth_name human readable name of authority
248 def verify_auth_belongs_to_me(self, name):
249 # get_auth_info will throw an exception if the authority does not
251 self.get_auth_info(name)
254 # Verify that an object belongs to this registry. By extension, this implies
255 # that the authority that owns the object belongs to this registry. If the
256 # object does not belong to this registry, then an exception is thrown.
258 # @param name human readable name of object
260 def verify_object_belongs_to_me(self, name):
261 auth_name = get_authority(name)
263 # the root authority belongs to the registry by default?
264 # TODO: is this true?
266 self.verify_auth_belongs_to_me(auth_name)
269 # Verify that the object_gid that was specified in the credential allows
270 # permission to the object 'name'. This is done by a simple prefix test.
271 # For example, an object_gid for planetlab.us.arizona would match the
272 # objects planetlab.us.arizona.slice1 and planetlab.us.arizona.
274 # @param name human readable name to test
276 def verify_object_permission(self, name):
277 object_hrn = self.object_gid.get_hrn()
278 if object_hrn == name:
280 if name.startswith(object_hrn + "."):
282 raise PermissionError(name)
285 # Fill in the planetlab-specific fields of a Geni record. This involves
286 # calling the appropriate PLC methods to retrieve the database record for
289 # PLC data is filled into the pl_info field of the record.
291 # @param record record to fill in fields (in/out param)
293 def fill_record_pl_info(self, record):
294 type = record.get_type()
295 pointer = record.get_pointer()
297 # records with pointer==-1 do not have plc info associated with them.
298 # for example, the top level authority records which are
299 # authorities, but not PL "sites"
301 record.set_pl_info({})
304 if (type == "sa") or (type == "ma"):
305 pl_res = self.shell.GetSites(self.pl_auth, [pointer])
306 elif (type == "slice"):
307 pl_res = self.shell.GetSlices(self.pl_auth, [pointer])
308 elif (type == "user"):
309 pl_res = self.shell.GetPersons(self.pl_auth, [pointer])
310 key_ids = pl_res[0]['key_ids']
311 keys = self.shell.GetKeys(self.pl_auth, key_ids)
314 pubkeys = [key['key'] for key in keys]
315 pl_res[0]['keys'] = pubkeys
316 elif (type == "node"):
317 pl_res = self.shell.GetNodes(self.pl_auth, [pointer])
319 raise UnknownGeniType(type)
322 # the planetlab record no longer exists
323 # TODO: delete the geni record ?
324 raise PlanetLabRecordDoesNotExist(record.get_name())
326 record.set_pl_info(pl_res[0])
329 # Look up user records given PLC user-ids. This is used as part of the
330 # process for reverse-mapping PLC records into Geni records.
332 # @param auth_table database table for the authority that holds the user records
333 # @param user_id_list list of user ids
334 # @param role either "*" or a string describing the role to look for ("pi", "user", ...)
336 # TODO: This function currently only searches one authority because it would
337 # be inefficient to brute-force search all authorities for a user id. The
338 # solution would likely be to implement a reverse mapping of user-id to
341 def lookup_users(self, auth_table, user_id_list, role="*"):
343 for person_id in user_id_list:
344 user_records = auth_table.find("user", person_id, "pointer")
345 for user_record in user_records:
346 self.fill_record_info(user_record)
348 user_roles = user_record.get_pl_info().get("roles")
349 if (role=="*") or (role in user_roles):
350 record_list.append(user_record.get_name())
354 # Fill in the geni-specific fields of the record.
356 # Note: It is assumed the fill_record_pl_info() has already been performed
359 def fill_record_geni_info(self, record):
361 type = record.get_type()
363 if (type == "slice"):
364 auth_table = self.get_auth_table(get_authority(record.get_name()))
365 person_ids = record.pl_info.get("person_ids", [])
366 researchers = self.lookup_users(auth_table, person_ids)
367 geni_info['researcher'] = researchers
370 auth_table = self.get_auth_table(record.get_name())
371 person_ids = record.pl_info.get("person_ids", [])
372 pis = self.lookup_users(auth_table, person_ids, "pi")
373 geni_info['pi'] = pis
374 # TODO: OrganizationName
377 auth_table = self.get_auth_table(record.get_name())
378 person_ids = record.pl_info.get("person_ids", [])
379 operators = self.lookup_users(auth_table, person_ids, "tech")
380 geni_info['operator'] = operators
381 # TODO: OrganizationName
383 auth_table = self.get_auth_table(record.get_name())
384 person_ids = record.pl_info.get("person_ids", [])
385 owners = self.lookup_users(auth_table, person_ids, "admin")
386 geni_info['owner'] = owners
388 elif (type == "node"):
389 geni_info['dns'] = record.pl_info.get("hostname", "")
390 # TODO: URI, LatLong, IP, DNS
392 elif (type == "user"):
393 geni_info['email'] = record.pl_info.get("email", "")
394 # TODO: PostalAddress, Phone
396 record.set_geni_info(geni_info)
399 # Given a Geni record, fill in the PLC-specific and Geni-specific fields
402 def fill_record_info(self, record):
403 self.fill_record_pl_info(record)
404 self.fill_record_geni_info(record)
406 def update_membership_list(self, oldRecord, record, listName, addFunc, delFunc):
407 # get a list of the HRNs tht are members of the old and new records
\r
409 if oldRecord.pl_info == None:
\r
410 oldRecord.pl_info = {}
\r
411 oldList = oldRecord.get_geni_info().get(listName, [])
\r
414 newList = record.get_geni_info().get(listName, [])
\r
416 # if the lists are the same, then we don't have to update anything
\r
417 if (oldList == newList):
\r
420 # build a list of the new person ids, by looking up each person to get
\r
423 for hrn in newList:
\r
424 userRecord = self.resolve_raw("user", hrn)[0]
\r
425 newIdList.append(userRecord.get_pointer())
\r
427 # build a list of the old person ids from the person_ids field of the
\r
430 oldIdList = oldRecord.pl_info.get("person_ids", [])
\r
431 containerId = oldRecord.get_pointer()
\r
433 # if oldRecord==None, then we are doing a Register, instead of an
\r
436 containerId = record.get_pointer()
\r
438 # add people who are in the new list, but not the oldList
\r
439 for personId in newIdList:
\r
440 if not (personId in oldIdList):
\r
441 print "adding id", personId, "to", record.get_name()
\r
442 addFunc(self.pl_auth, personId, containerId)
\r
444 # remove people who are in the old list, but not the new list
\r
445 for personId in oldIdList:
\r
446 if not (personId in newIdList):
\r
447 print "removing id", personId, "from", record.get_name()
\r
448 delFunc(self.pl_auth, personId, containerId)
\r
450 def update_membership(self, oldRecord, record):
\r
451 if record.type == "slice":
\r
452 self.update_membership_list(oldRecord, record, 'researcher',
\r
453 self.shell.AddPersonToSlice,
\r
454 self.shell.DeletePersonFromSlice)
\r
455 elif record.type == "sa":
\r
458 elif record.type == "ma":
\r
465 # Register an object with the registry. In addition to being stored in the
466 # Geni database, the appropriate records will also be created in the
469 # @param cred credential string
470 # @param record_dict dictionary containing record fields
472 def register(self, cred, record_dict):
473 self.decode_authentication(cred, "register")
475 record = GeniRecord(dict = record_dict)
476 type = record.get_type()
477 name = record.get_name()
479 auth_name = get_authority(name)
480 self.verify_object_permission(auth_name)
481 auth_info = self.get_auth_info(auth_name)
482 table = self.get_auth_table(auth_name)
486 # check if record already exists
487 existing_records = table.resolve(type, name)
489 raise ExistingRecord(name)
491 if (type == "sa") or (type=="ma"):
493 if not self.hierarchy.auth_exists(name):
494 self.hierarchy.create_auth(name)
496 # authorities are special since they are managed by the registry
497 # rather than by the caller. We create our own GID for the
498 # authority rather than relying on the caller to supply one.
500 # get the GID from the newly created authority
501 child_auth_info = self.get_auth_info(name)
502 gid = auth_info.get_gid_object()
503 record.set_gid(gid.save_to_string(save_parents=True))
505 geni_fields = record.get_geni_info()
506 site_fields = record.get_pl_info()
508 # if registering a sa, see if a ma already exists
509 # if registering a ma, see if a sa already exists
511 other_rec = table.resolve("ma", record.get_name())
513 other_rec = table.resolve("sa", record.get_name())
516 print "linking ma and sa to the same plc site"
517 pointer = other_rec[0].get_pointer()
519 geni_fields_to_pl_fields(type, name, geni_fields, site_fields)
520 print "adding site with fields", site_fields
521 pointer = self.shell.AddSite(self.pl_auth, site_fields)
523 record.set_pointer(pointer)
525 elif (type == "slice"):
526 geni_fields = record.get_geni_info()
527 slice_fields = record.get_pl_info()
529 geni_fields_to_pl_fields(type, name, geni_fields, slice_fields)
531 pointer = self.shell.AddSlice(self.pl_auth, slice_fields)
532 record.set_pointer(pointer)
534 elif (type == "user"):
535 geni_fields = record.get_geni_info()
536 user_fields = record.get_pl_info()
538 geni_fields_to_pl_fields(type, name, geni_fields, user_fields)
540 pointer = self.shell.AddPerson(self.pl_auth, user_fields)
541 record.set_pointer(pointer)
543 elif (type == "node"):
544 geni_fields = record.get_geni_info()
545 node_fields = record.get_pl_info()
547 geni_fields_to_pl_fields(type, name, geni_fields, node_fields)
549 login_base = hrn_to_pl_login_base(auth_name)
551 print "calling addnode with", login_base, node_fields
552 pointer = self.shell.AddNode(self.pl_auth, login_base, node_fields)
553 record.set_pointer(pointer)
556 raise UnknownGeniType(type)
560 # update membership for researchers, pis, owners, operators
561 self.update_membership(None, record)
563 return record.get_gid_object().save_to_string(save_parents=True)
568 # Remove an object from the registry. If the object represents a PLC object,
569 # then the PLC records will also be removed.
571 # @param cred credential string
572 # @param record_dict dictionary containing record fields. The only relevant
573 # fields of the record are 'name' and 'type', which are used to lookup
574 # the current copy of the record in the Geni database, to make sure
575 # that the appopriate record is removed.
577 def remove(self, cred, type, hrn):
578 self.decode_authentication(cred, "remove")
580 self.verify_object_permission(hrn)
582 auth_name = get_authority(hrn)
583 table = self.get_auth_table(auth_name)
585 record_list = table.resolve(type, hrn)
587 raise RecordNotFound(hrn)
588 record = record_list[0]
592 self.shell.DeletePerson(self.pl_auth, record.get_pointer())
593 elif type == "slice":
594 self.shell.DeleteSlice(self.pl_auth, record.get_pointer())
596 self.shell.DeleteNode(self.pl_auth, record.get_pointer())
597 elif (type == "sa") or (type == "ma"):
599 other_rec = table.resolve("ma", record.get_name())
601 other_rec = table.resolve("sa", record.get_name())
604 # sa and ma both map to a site, so if we are deleting one
605 # but the other still exists, then do not delete the site
606 print "not removing site", record.get_name(), "because either sa or ma still exists"
609 print "removing site", record.get_name()
610 self.shell.DeleteSite(self.pl_auth, record.get_pointer())
612 raise UnknownGeniType(type)
621 # Update an object in the registry. Currently, this only updates the
622 # PLC information associated with the record. The Geni fields (name, type,
625 # The record is expected to have the pl_info field filled in with the data
626 # that should be updated.
628 # TODO: The geni_info member of the record should be parsed and the pl_info
629 # adjusted as necessary (add/remove users from a slice, etc)
631 # @param cred credential string specifying rights of the caller
632 # @param record a record dictionary to be updated
634 def update(self, cred, record_dict):
635 self.decode_authentication(cred, "update")
637 record = GeniRecord(dict = record_dict)
638 type = record.get_type()
640 self.verify_object_permission(record.get_name())
642 auth_name = get_authority(record.get_name())
644 auth_name = record.get_name()
645 table = self.get_auth_table(auth_name)
647 # make sure the record exists
648 existing_record_list = table.resolve(type, record.get_name())
649 if not existing_record_list:
650 raise RecordNotFound(record.get_name())
651 existing_record = existing_record_list[0]
653 # Update_membership needs the membership lists in the existing record
654 # filled in, so it can see if members were added or removed
655 self.fill_record_info(existing_record)
657 # Use the pointer from the existing record, not the one that the user
658 # gave us. This prevents the user from inserting a forged pointer
659 pointer = existing_record.get_pointer()
661 # update the PLC information that was specified with the record
663 if (type == "sa") or (type == "ma"):
664 self.shell.UpdateSite(self.pl_auth, pointer, record.get_pl_info())
666 elif type == "slice":
667 self.shell.UpdateSlice(self.pl_auth, pointer, record.get_pl_info())
670 # SMBAKER: UpdatePerson only allows a limited set of fields to be
671 # updated. Ideally we should have a more generic way of doing
672 # this. I copied the field names from UpdatePerson.py...
674 all_fields = record.get_pl_info()
675 for key in all_fields.keys():
676 if key in ['first_name', 'last_name', 'title', 'email',
677 'password', 'phone', 'url', 'bio', 'accepted_aup',
\r
679 update_fields[key] = all_fields[key]
680 self.shell.UpdatePerson(self.pl_auth, pointer, update_fields)
683 self.shell.UpdateNode(self.pl_auth, pointer, record.get_pl_info())
686 raise UnknownGeniType(type)
688 # update membership for researchers, pis, owners, operators
\r
689 self.update_membership(existing_record, record)
692 # List the records in an authority. The objectGID in the supplied credential
693 # should name the authority that will be listed.
695 # TODO: List doesn't take an hrn and uses the hrn contained in the
696 # objectGid of the credential. Does this mean the only way to list an
697 # authority is by having a credential for that authority?
699 # @param cred credential string specifying rights of the caller
701 # @return list of record dictionaries
702 def list(self, cred, auth_hrn):
703 self.decode_authentication(cred, "list")
705 if not self.hierarchy.auth_exists(auth_hrn):
706 raise MissingAuthority(auth_hrn)
708 table = self.get_auth_table(auth_hrn)
710 records = table.list()
713 for record in records:
715 self.fill_record_info(record)
716 good_records.append(record)
717 except PlanetLabRecordDoesNotExist:
718 # silently drop the ones that are missing in PL.
719 # is this the right thing to do?
720 print "ignoring geni record ", record.get_name(), " because pl record does not exist"
724 for record in good_records:
725 dicts.append(record.as_dict())
732 # Resolve a record. This is an internal version of the Resolve API call
733 # and returns records in record object format rather than dictionaries
734 # that may be sent over XMLRPC.
736 # @param type type of record to resolve (user | sa | ma | slice | node)
737 # @param name human readable name of object
738 # @param must_exist if True, throw an exception if no records are found
740 # @return a list of record objects, or an empty list []
742 def resolve_raw(self, type, name, must_exist=True):
743 auth_name = get_authority(name)
746 table = self.get_auth_table(auth_name)
747 records = table.resolve(type, name)
748 if (not records) and must_exist:
749 raise RecordNotFound(name)
752 for record in records:
754 self.fill_record_info(record)
755 good_records.append(record)
756 except PlanetLabRecordDoesNotExist:
757 # silently drop the ones that are missing in PL.
758 # is this the right thing to do?
759 print "ignoring geni record ", record.get_name(), "because pl record does not exist"
767 # This is a wrapper around resolve_raw that converts records objects into
768 # dictionaries before returning them to the user.
770 # @param cred credential string authorizing the caller
771 # @param name human readable name to resolve
773 # @return a list of record dictionaries, or an empty list
775 def resolve(self, cred, name):
776 self.decode_authentication(cred, "resolve")
779 records = self.resolve_raw("*", name)
782 for registry in self.registries:
783 if name.startswith(registry):
784 records = self.registries[registry].resolve(self.credential, name)
788 for record in records:
789 dicts.append(record.as_dict())
796 # Retrieve the GID for an object. This function looks up a record in the
797 # registry and returns the GID of the record if it exists.
798 # TODO: Is this function needed? It's a shortcut for Resolve()
800 # @param name hrn to look up
802 # @return the string representation of a GID object
804 def get_gid(self, name):
805 self.verify_object_belongs_to_me(name)
806 records = self.resolve_raw("*", name)
808 for record in records:
809 gid = record.get_gid_object()
810 gid_string_list.append(gid.save_to_string(save_parents=True))
811 return gid_string_list
814 # Determine tje rights that an object should have. The rights are entirely
815 # dependent on the type of the object. For example, users automatically
816 # get "refresh", "resolve", and "info".
818 # @param type the type of the object (user | sa | ma | slice | node)
819 # @param name human readable name of the object (not used at this time)
821 # @return RightList object containing rights
823 def determine_rights(self, type, name):
826 # rights seem to be somewhat redundant with the type of the credential.
827 # For example, a "sa" credential implies the authority right, because
828 # a sa credential cannot be issued to a user who is not an owner of
836 rl.add("authority,sa")
838 rl.add("authority,ma")
839 elif type == "slice":
845 elif type == "component":
851 # GENI API: Get_self_credential
853 # Get_self_credential a degenerate version of get_credential used by a
854 # client to get his initial credential when he doesn't have one. This is
855 # the same as get_credential(..., cred=None,...).
857 # The registry ensures that the client is the principal that is named by
858 # (type, name) by comparing the public key in the record's GID to the
859 # private key used to encrypt the client-side of the HTTPS connection. Thus
860 # it is impossible for one principal to retrieve another principal's
861 # credential without having the appropriate private key.
863 # @param type type of object (user | slice | sa | ma | node
864 # @param name human readable name of object
866 # @return the string representation of a credential object
868 def get_self_credential(self, type, name):
869 self.verify_object_belongs_to_me(name)
871 auth_hrn = get_authority(name)
874 auth_info = self.get_auth_info(auth_hrn)
875 # find a record that matches
876 records = self.resolve_raw(type, name, must_exist=True)
879 gid = record.get_gid_object()
880 peer_cert = self.server.peer_cert
881 if not peer_cert.is_pubkey(gid.get_pubkey()):
882 raise ConnectionKeyGIDMismatch(gid.get_subject())
884 # create the credential
885 gid = record.get_gid_object()
886 cred = Credential(subject = gid.get_subject())
887 cred.set_gid_caller(gid)
888 cred.set_gid_object(gid)
889 cred.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn)
890 cred.set_pubkey(gid.get_pubkey())
892 rl = self.determine_rights(type, name)
893 cred.set_privileges(rl)
895 # determine the type of credential that we want to use as a parent for
898 if (type == "ma") or (type == "node"):
899 auth_kind = "authority,ma"
900 else: # user, slice, sa
901 auth_kind = "authority,sa"
903 cred.set_parent(self.hierarchy.get_auth_cred(auth_hrn, kind=auth_kind))
908 return cred.save_to_string(save_parents=True)
911 # verify_cancreate_credential
913 # Verify that a user can retrieve a particular type of credential. For
914 # slices, the user must be on the researcher list. For SA and MA the user
915 # must be on the pi and operator lists respectively.
917 def verify_cancreate_credential(self, src_cred, record):
918 type = record.get_type()
919 cred_object_hrn = src_cred.get_gid_object().get_hrn()
921 if cred_object_hrn in [config.GENI_REGISTRY_ROOT_AUTH]:
924 researchers = record.get_geni_info().get("researcher", [])
925 if not (cred_object_hrn in researchers):
926 raise PermissionError(cred_object_hrn + " is not in researcher list for " + record.get_name())
928 pis = record.get_geni_info().get("pi", [])
929 if not (cred_object_hrn in pis):
930 raise PermissionError(cred_object_hrn + " is not in pi list for " + record.get_name())
932 operators = record.get_geni_info().get("operator", [])
933 if not (cred_object_hrn in operators):
934 raise PermissionError(cred_object_hrn + " is not in operator list for " + record.get_name())
937 # GENI API: Get_credential
939 # Retrieve a credential for an object.
941 # If cred==None, then the behavior reverts to get_self_credential()
943 # @param cred credential object specifying rights of the caller
944 # @param type type of object (user | slice | sa | ma | node)
945 # @param name human readable name of object
947 # @return the string representation of a credental object
949 def get_credential(self, cred, type, name):
951 return self.get_self_credential(type, name)
953 self.decode_authentication(cred, "getcredential")
955 self.verify_object_belongs_to_me(name)
957 auth_hrn = get_authority(name)
960 auth_info = self.get_auth_info(auth_hrn)
962 records = self.resolve_raw(type, name, must_exist=True)
965 # verify_cancreate_credential requires that the member lists
966 # (researchers, pis, etc) be filled in
967 self.fill_record_info(record)
969 self.verify_cancreate_credential(self.client_cred, record)
971 # TODO: Check permission that self.client_cred can access the object
973 object_gid = record.get_gid_object()
974 new_cred = Credential(subject = object_gid.get_subject())
975 new_cred.set_gid_caller(self.client_gid)
976 new_cred.set_gid_object(object_gid)
977 new_cred.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn)
978 new_cred.set_pubkey(object_gid.get_pubkey())
980 rl = self.determine_rights(type, name)
981 new_cred.set_privileges(rl)
983 # determine the type of credential that we want to use as a parent for
986 if (type == "ma") or (type == "node"):
987 auth_kind = "authority,ma"
988 else: # user, slice, sa
989 auth_kind = "authority,sa"
991 new_cred.set_parent(self.hierarchy.get_auth_cred(auth_hrn, kind=auth_kind))
996 return new_cred.save_to_string(save_parents=True)
999 # GENI API: get_ticket
1001 # Retrieve a ticket. This operation is currently implemented on PLC
1002 # only (see SFA, engineering decisions); it is not implemented on
1005 # The ticket is filled in with information from the PLC database. This
1006 # information includes resources, and attributes such as user keys and
1009 # @param cred credential string
1010 # @param name name of the slice to retrieve a ticket for
1011 # @param rspec resource specification dictionary
1013 # @return the string representation of a ticket object
1015 def get_ticket(self, cred, name, rspec):
1016 self.decode_authentication(cred, "getticket")
1018 self.verify_object_belongs_to_me(name)
1020 self.verify_object_permission(name)
1022 # XXX much of this code looks like get_credential... are they so similar
1023 # that they should be combined?
1025 auth_hrn = get_authority(name)
1028 auth_info = self.get_auth_info(auth_hrn)
1030 records = self.resolve_raw("slice", name, must_exist=True)
1033 object_gid = record.get_gid_object()
1034 new_ticket = Ticket(subject = object_gid.get_subject())
1035 new_ticket.set_gid_caller(self.client_gid)
1036 new_ticket.set_gid_object(object_gid)
1037 new_ticket.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn)
1038 new_ticket.set_pubkey(object_gid.get_pubkey())
1040 self.fill_record_info(record)
1042 (attributes, rspec) = self.record_to_slice_info(record)
1044 new_ticket.set_attributes(attributes)
1045 new_ticket.set_rspec(rspec)
1047 new_ticket.set_parent(AuthHierarchy.get_auth_ticket(auth_hrn))
1052 return new_ticket.save_to_string(save_parents=True)
1055 # GENI_API: Create_gid
1057 # Create a new GID. For MAs and SAs that are physically located on the
1058 # registry, this allows a owner/operator/PI to create a new GID and have it
1059 # signed by his respective authority.
1061 # @param cred credential of caller
1062 # @param name hrn for new GID
1063 # @param uuid unique identifier for new GID
1064 # @param pkey_string public-key string (TODO: why is this a string and not a keypair object?)
1066 # @return the string representation of a GID object
1068 def create_gid(self, cred, name, uuid, pubkey_str):
1069 self.decode_authentication(cred, "getcredential")
1071 self.verify_object_belongs_to_me(name)
1073 self.verify_object_permission(name)
1076 uuid = create_uuid()
1079 pkey.load_pubkey_from_string(pubkey_str)
1080 gid = self.hierarchy.create_gid(name, uuid, pkey)
1082 return gid.save_to_string(save_parents=True)