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 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)
208 # Given an authority name, return the information for that authority. This
209 # is basically a stub that calls the hierarchy module.
211 # @param auth_hrn human readable name of authority
213 def get_auth_info(self, auth_hrn):
214 return self.hierarchy.get_auth_info(auth_hrn)
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
221 # @param auth_name human readable name of authority
223 def get_auth_table(self, auth_name):
224 auth_info = self.get_auth_info(auth_name)
226 table = GeniTable(hrn=auth_name,
227 cninfo=auth_info.get_dbinfo())
229 # if the table doesn't exist, then it means we haven't put any records
230 # into this authority yet.
232 if not table.exists():
233 print "Registry: creating table for authority", auth_name
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.
244 # @param auth_name human readable name of authority
246 def verify_auth_belongs_to_me(self, name):
247 # get_auth_info will throw an exception if the authority does not
249 self.get_auth_info(name)
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.
256 # @param name human readable name of object
258 def verify_object_belongs_to_me(self, name):
259 auth_name = get_authority(name)
261 # the root authority belongs to the registry by default?
262 # TODO: is this true?
264 self.verify_auth_belongs_to_me(auth_name)
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.
272 # @param name human readable name to test
274 def verify_object_permission(self, name):
275 object_hrn = self.object_gid.get_hrn()
276 if object_hrn == name:
278 if name.startswith(object_hrn + "."):
280 raise PermissionError(name)
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
287 # PLC data is filled into the pl_info field of the record.
289 # @param record record to fill in fields (in/out param)
291 def fill_record_pl_info(self, record):
292 type = record.get_type()
293 pointer = record.get_pointer()
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"
299 record.set_pl_info({})
302 if (type == "sa") or (type == "ma"):
303 pl_res = self.shell.GetSites(self.pl_auth, [pointer])
304 elif (type == "slice"):
305 pl_res = self.shell.GetSlices(self.pl_auth, [pointer])
306 elif (type == "user"):
307 pl_res = self.shell.GetPersons(self.pl_auth, [pointer])
308 key_ids = pl_res[0]['key_ids']
309 keys = self.shell.GetKeys(self.pl_auth, key_ids)
312 pubkeys = [key['key'] for key in keys]
313 pl_res[0]['keys'] = pubkeys
314 elif (type == "node"):
315 pl_res = self.shell.GetNodes(self.pl_auth, [pointer])
317 raise UnknownGeniType(type)
320 # the planetlab record no longer exists
321 # TODO: delete the geni record ?
322 raise PlanetLabRecordDoesNotExist(record.get_name())
324 record.set_pl_info(pl_res[0])
327 # Look up user records given PLC user-ids. This is used as part of the
328 # process for reverse-mapping PLC records into Geni records.
330 # @param auth_table database table for the authority that holds the user records
331 # @param user_id_list list of user ids
332 # @param role either "*" or a string describing the role to look for ("pi", "user", ...)
334 # TODO: This function currently only searches one authority because it would
335 # be inefficient to brute-force search all authorities for a user id. The
336 # solution would likely be to implement a reverse mapping of user-id to
339 def lookup_users(self, auth_table, user_id_list, role="*"):
341 for person_id in user_id_list:
342 user_records = auth_table.find("user", person_id, "pointer")
343 for user_record in user_records:
344 self.fill_record_info(user_record)
346 user_roles = user_record.get_pl_info().get("roles")
347 if (role=="*") or (role in user_roles):
348 record_list.append(user_record.get_name())
352 # Fill in the geni-specific fields of the record.
354 # Note: It is assumed the fill_record_pl_info() has already been performed
357 def fill_record_geni_info(self, record):
359 type = record.get_type()
361 if (type == "slice"):
362 auth_table = self.get_auth_table(get_authority(record.get_name()))
363 person_ids = record.pl_info.get("person_ids", [])
364 researchers = self.lookup_users(auth_table, person_ids)
365 geni_info['researcher'] = researchers
368 auth_table = self.get_auth_table(record.get_name())
369 person_ids = record.pl_info.get("person_ids", [])
370 pis = self.lookup_users(auth_table, person_ids, "pi")
371 geni_info['pi'] = pis
372 # TODO: OrganizationName
375 auth_table = self.get_auth_table(record.get_name())
376 person_ids = record.pl_info.get("person_ids", [])
377 operators = self.lookup_users(auth_table, person_ids, "tech")
378 geni_info['operator'] = operators
379 # TODO: OrganizationName
381 auth_table = self.get_auth_table(record.get_name())
382 person_ids = record.pl_info.get("person_ids", [])
383 owners = self.lookup_users(auth_table, person_ids, "admin")
384 geni_info['owner'] = owners
386 elif (type == "node"):
387 geni_info['dns'] = record.pl_info.get("hostname", "")
388 # TODO: URI, LatLong, IP, DNS
390 elif (type == "user"):
391 geni_info['email'] = record.pl_info.get("email", "")
392 # TODO: PostalAddress, Phone
394 record.set_geni_info(geni_info)
397 # Given a Geni record, fill in the PLC-specific and Geni-specific fields
400 def fill_record_info(self, record):
401 self.fill_record_pl_info(record)
402 self.fill_record_geni_info(record)
404 def update_membership_list(self, oldRecord, record, listName, addFunc, delFunc):
405 # get a list of the HRNs tht are members of the old and new records
\r
407 if oldRecord.pl_info == None:
\r
408 oldRecord.pl_info = {}
\r
409 oldList = oldRecord.get_geni_info().get(listName, [])
\r
412 newList = record.get_geni_info().get(listName, [])
\r
414 # if the lists are the same, then we don't have to update anything
\r
415 if (oldList == newList):
\r
418 # build a list of the new person ids, by looking up each person to get
\r
421 for hrn in newList:
\r
422 userRecord = self.resolve_raw("user", hrn)[0]
\r
423 newIdList.append(userRecord.get_pointer())
\r
425 # build a list of the old person ids from the person_ids field of the
\r
428 oldIdList = oldRecord.pl_info.get("person_ids", [])
\r
429 containerId = oldRecord.get_pointer()
\r
431 # if oldRecord==None, then we are doing a Register, instead of an
\r
434 containerId = record.get_pointer()
\r
436 # add people who are in the new list, but not the oldList
\r
437 for personId in newIdList:
\r
438 if not (personId in oldIdList):
\r
439 print "adding id", personId, "to", record.get_name()
\r
440 addFunc(self.pl_auth, personId, containerId)
\r
442 # remove people who are in the old list, but not the new list
\r
443 for personId in oldIdList:
\r
444 if not (personId in newIdList):
\r
445 print "removing id", personId, "from", record.get_name()
\r
446 delFunc(self.pl_auth, personId, containerId)
\r
448 def update_membership(self, oldRecord, record):
\r
449 if record.type == "slice":
\r
450 self.update_membership_list(oldRecord, record, 'researcher',
\r
451 self.shell.AddPersonToSlice,
\r
452 self.shell.DeletePersonFromSlice)
\r
453 elif record.type == "sa":
\r
456 elif record.type == "ma":
\r
463 # Register an object with the registry. In addition to being stored in the
464 # Geni database, the appropriate records will also be created in the
467 # @param cred credential string
468 # @param record_dict dictionary containing record fields
470 def register(self, cred, record_dict):
471 self.decode_authentication(cred, "register")
473 record = GeniRecord(dict = record_dict)
474 type = record.get_type()
475 name = record.get_name()
477 auth_name = get_authority(name)
478 self.verify_object_permission(auth_name)
479 auth_info = self.get_auth_info(auth_name)
480 table = self.get_auth_table(auth_name)
484 # check if record already exists
485 existing_records = table.resolve(type, name)
487 raise ExistingRecord(name)
489 if (type == "sa") or (type=="ma"):
491 if not self.hierarchy.auth_exists(name):
492 self.hierarchy.create_auth(name)
494 # authorities are special since they are managed by the registry
495 # rather than by the caller. We create our own GID for the
496 # authority rather than relying on the caller to supply one.
498 # get the GID from the newly created authority
499 child_auth_info = self.get_auth_info(name)
500 gid = auth_info.get_gid_object()
501 record.set_gid(gid.save_to_string(save_parents=True))
503 geni_fields = record.get_geni_info()
504 site_fields = record.get_pl_info()
506 # if registering a sa, see if a ma already exists
507 # if registering a ma, see if a sa already exists
509 other_rec = table.resolve("ma", record.get_name())
511 other_rec = table.resolve("sa", record.get_name())
514 print "linking ma and sa to the same plc site"
515 pointer = other_rec[0].get_pointer()
517 geni_fields_to_pl_fields(type, name, geni_fields, site_fields)
518 print "adding site with fields", site_fields
519 pointer = self.shell.AddSite(self.pl_auth, site_fields)
521 record.set_pointer(pointer)
523 elif (type == "slice"):
524 geni_fields = record.get_geni_info()
525 slice_fields = record.get_pl_info()
527 geni_fields_to_pl_fields(type, name, geni_fields, slice_fields)
529 pointer = self.shell.AddSlice(self.pl_auth, slice_fields)
530 record.set_pointer(pointer)
532 elif (type == "user"):
533 geni_fields = record.get_geni_info()
534 user_fields = record.get_pl_info()
536 geni_fields_to_pl_fields(type, name, geni_fields, user_fields)
538 pointer = self.shell.AddPerson(self.pl_auth, user_fields)
539 record.set_pointer(pointer)
541 elif (type == "node"):
542 geni_fields = record.get_geni_info()
543 node_fields = record.get_pl_info()
545 geni_fields_to_pl_fields(type, name, geni_fields, node_fields)
547 login_base = hrn_to_pl_login_base(auth_name)
549 print "calling addnode with", login_base, node_fields
550 pointer = self.shell.AddNode(self.pl_auth, login_base, node_fields)
551 record.set_pointer(pointer)
554 raise UnknownGeniType(type)
558 # update membership for researchers, pis, owners, operators
559 self.update_membership(None, record)
561 return record.get_gid_object().save_to_string(save_parents=True)
566 # Remove an object from the registry. If the object represents a PLC object,
567 # then the PLC records will also be removed.
569 # @param cred credential string
570 # @param record_dict dictionary containing record fields. The only relevant
571 # fields of the record are 'name' and 'type', which are used to lookup
572 # the current copy of the record in the Geni database, to make sure
573 # that the appopriate record is removed.
575 def remove(self, cred, type, hrn):
576 self.decode_authentication(cred, "remove")
578 self.verify_object_permission(hrn)
580 auth_name = get_authority(hrn)
581 table = self.get_auth_table(auth_name)
583 record_list = table.resolve(type, hrn)
585 raise RecordNotFound(hrn)
586 record = record_list[0]
590 self.shell.DeletePerson(self.pl_auth, record.get_pointer())
591 elif type == "slice":
592 self.shell.DeleteSlice(self.pl_auth, record.get_pointer())
594 self.shell.DeleteNode(self.pl_auth, record.get_pointer())
595 elif (type == "sa") or (type == "ma"):
597 other_rec = table.resolve("ma", record.get_name())
599 other_rec = table.resolve("sa", record.get_name())
602 # sa and ma both map to a site, so if we are deleting one
603 # but the other still exists, then do not delete the site
604 print "not removing site", record.get_name(), "because either sa or ma still exists"
607 print "removing site", record.get_name()
608 self.shell.DeleteSite(self.pl_auth, record.get_pointer())
610 raise UnknownGeniType(type)
619 # Update an object in the registry. Currently, this only updates the
620 # PLC information associated with the record. The Geni fields (name, type,
623 # The record is expected to have the pl_info field filled in with the data
624 # that should be updated.
626 # TODO: The geni_info member of the record should be parsed and the pl_info
627 # adjusted as necessary (add/remove users from a slice, etc)
629 # @param cred credential string specifying rights of the caller
630 # @param record a record dictionary to be updated
632 def update(self, cred, record_dict):
633 self.decode_authentication(cred, "update")
635 record = GeniRecord(dict = record_dict)
636 type = record.get_type()
638 self.verify_object_permission(record.get_name())
640 auth_name = get_authority(record.get_name())
642 auth_name = record.get_name()
643 table = self.get_auth_table(auth_name)
645 # make sure the record exists
646 existing_record_list = table.resolve(type, record.get_name())
647 if not existing_record_list:
648 raise RecordNotFound(record.get_name())
649 existing_record = existing_record_list[0]
651 # Update_membership needs the membership lists in the existing record
652 # filled in, so it can see if members were added or removed
653 self.fill_record_info(existing_record)
655 # Use the pointer from the existing record, not the one that the user
656 # gave us. This prevents the user from inserting a forged pointer
657 pointer = existing_record.get_pointer()
659 # update the PLC information that was specified with the record
661 if (type == "sa") or (type == "ma"):
662 self.shell.UpdateSite(self.pl_auth, pointer, record.get_pl_info())
664 elif type == "slice":
665 self.shell.UpdateSlice(self.pl_auth, pointer, record.get_pl_info())
668 # SMBAKER: UpdatePerson only allows a limited set of fields to be
669 # updated. Ideally we should have a more generic way of doing
670 # this. I copied the field names from UpdatePerson.py...
672 all_fields = record.get_pl_info()
673 for key in all_fields.keys():
674 if key in ['first_name', 'last_name', 'title', 'email',
675 'password', 'phone', 'url', 'bio', 'accepted_aup',
\r
677 update_fields[key] = all_fields[key]
678 self.shell.UpdatePerson(self.pl_auth, pointer, update_fields)
681 self.shell.UpdateNode(self.pl_auth, pointer, record.get_pl_info())
684 raise UnknownGeniType(type)
686 # update membership for researchers, pis, owners, operators
\r
687 self.update_membership(existing_record, record)
690 # List the records in an authority. The objectGID in the supplied credential
691 # should name the authority that will be listed.
693 # TODO: List doesn't take an hrn and uses the hrn contained in the
694 # objectGid of the credential. Does this mean the only way to list an
695 # authority is by having a credential for that authority?
697 # @param cred credential string specifying rights of the caller
699 # @return list of record dictionaries
700 def list(self, cred, auth_hrn):
701 self.decode_authentication(cred, "list")
703 if not self.hierarchy.auth_exists(auth_hrn):
704 raise MissingAuthority(auth_hrn)
706 table = self.get_auth_table(auth_hrn)
708 records = table.list()
711 for record in records:
713 self.fill_record_info(record)
714 good_records.append(record)
715 except PlanetLabRecordDoesNotExist:
716 # silently drop the ones that are missing in PL.
717 # is this the right thing to do?
718 print "ignoring geni record ", record.get_name(), " because pl record does not exist"
722 for record in good_records:
723 dicts.append(record.as_dict())
730 # Resolve a record. This is an internal version of the Resolve API call
731 # and returns records in record object format rather than dictionaries
732 # that may be sent over XMLRPC.
734 # @param type type of record to resolve (user | sa | ma | slice | node)
735 # @param name human readable name of object
736 # @param must_exist if True, throw an exception if no records are found
738 # @return a list of record objects, or an empty list []
740 def resolve_raw(self, type, name, must_exist=True):
741 auth_name = get_authority(name)
744 table = self.get_auth_table(auth_name)
745 records = table.resolve(type, name)
746 if (not records) and must_exist:
747 raise RecordNotFound(name)
750 for record in records:
752 self.fill_record_info(record)
753 good_records.append(record)
754 except PlanetLabRecordDoesNotExist:
755 # silently drop the ones that are missing in PL.
756 # is this the right thing to do?
757 print "ignoring geni record ", record.get_name(), "because pl record does not exist"
765 # This is a wrapper around resolve_raw that converts records objects into
766 # dictionaries before returning them to the user.
768 # @param cred credential string authorizing the caller
769 # @param name human readable name to resolve
771 # @return a list of record dictionaries, or an empty list
773 def resolve(self, cred, name):
774 self.decode_authentication(cred, "resolve")
777 records = self.resolve_raw("*", name)
780 for registry in self.registries:
781 if name.startswith(registry):
782 records = self.registries[registry].resolve(self.credential, name)
786 for record in records:
787 dicts.append(record.as_dict())
794 # Retrieve the GID for an object. This function looks up a record in the
795 # registry and returns the GID of the record if it exists.
796 # TODO: Is this function needed? It's a shortcut for Resolve()
798 # @param name hrn to look up
800 # @return the string representation of a GID object
802 def get_gid(self, name):
803 self.verify_object_belongs_to_me(name)
804 records = self.resolve_raw("*", name)
806 for record in records:
807 gid = record.get_gid_object()
808 gid_string_list.append(gid.save_to_string(save_parents=True))
809 return gid_string_list
812 # Determine tje rights that an object should have. The rights are entirely
813 # dependent on the type of the object. For example, users automatically
814 # get "refresh", "resolve", and "info".
816 # @param type the type of the object (user | sa | ma | slice | node)
817 # @param name human readable name of the object (not used at this time)
819 # @return RightList object containing rights
821 def determine_rights(self, type, name):
824 # rights seem to be somewhat redundant with the type of the credential.
825 # For example, a "sa" credential implies the authority right, because
826 # a sa credential cannot be issued to a user who is not an owner of
834 rl.add("authority,sa")
836 rl.add("authority,ma")
837 elif type == "slice":
843 elif type == "component":
849 # GENI API: Get_self_credential
851 # Get_self_credential a degenerate version of get_credential used by a
852 # client to get his initial credential when he doesn't have one. This is
853 # the same as get_credential(..., cred=None,...).
855 # The registry ensures that the client is the principal that is named by
856 # (type, name) by comparing the public key in the record's GID to the
857 # private key used to encrypt the client-side of the HTTPS connection. Thus
858 # it is impossible for one principal to retrieve another principal's
859 # credential without having the appropriate private key.
861 # @param type type of object (user | slice | sa | ma | node
862 # @param name human readable name of object
864 # @return the string representation of a credential object
866 def get_self_credential(self, type, name):
867 self.verify_object_belongs_to_me(name)
869 auth_hrn = get_authority(name)
872 auth_info = self.get_auth_info(auth_hrn)
873 # find a record that matches
874 records = self.resolve_raw(type, name, must_exist=True)
877 gid = record.get_gid_object()
878 peer_cert = self.server.peer_cert
879 if not peer_cert.is_pubkey(gid.get_pubkey()):
880 raise ConnectionKeyGIDMismatch(gid.get_subject())
882 # create the credential
883 gid = record.get_gid_object()
884 cred = Credential(subject = gid.get_subject())
885 cred.set_gid_caller(gid)
886 cred.set_gid_object(gid)
887 cred.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn)
888 cred.set_pubkey(gid.get_pubkey())
890 rl = self.determine_rights(type, name)
891 cred.set_privileges(rl)
893 # determine the type of credential that we want to use as a parent for
896 if (type == "ma") or (type == "node"):
897 auth_kind = "authority,ma"
898 else: # user, slice, sa
899 auth_kind = "authority,sa"
901 cred.set_parent(self.hierarchy.get_auth_cred(auth_hrn, kind=auth_kind))
906 return cred.save_to_string(save_parents=True)
909 # verify_cancreate_credential
911 # Verify that a user can retrieve a particular type of credential. For
912 # slices, the user must be on the researcher list. For SA and MA the user
913 # must be on the pi and operator lists respectively.
915 def verify_cancreate_credential(self, src_cred, record):
916 type = record.get_type()
917 cred_object_hrn = src_cred.get_gid_object().get_hrn()
919 if cred_object_hrn in [config.GENI_REGISTRY_ROOT_AUTH]:
922 researchers = record.get_geni_info().get("researcher", [])
923 if not (cred_object_hrn in researchers):
924 raise PermissionError(cred_object_hrn + " is not in researcher list for " + record.get_name())
926 pis = record.get_geni_info().get("pi", [])
927 if not (cred_object_hrn in pis):
928 raise PermissionError(cred_object_hrn + " is not in pi list for " + record.get_name())
930 operators = record.get_geni_info().get("operator", [])
931 if not (cred_object_hrn in operators):
932 raise PermissionError(cred_object_hrn + " is not in operator list for " + record.get_name())
935 # GENI API: Get_credential
937 # Retrieve a credential for an object.
939 # If cred==None, then the behavior reverts to get_self_credential()
941 # @param cred credential object specifying rights of the caller
942 # @param type type of object (user | slice | sa | ma | node)
943 # @param name human readable name of object
945 # @return the string representation of a credental object
947 def get_credential(self, cred, type, name):
949 return self.get_self_credential(type, name)
951 self.decode_authentication(cred, "getcredential")
953 self.verify_object_belongs_to_me(name)
955 auth_hrn = get_authority(name)
958 auth_info = self.get_auth_info(auth_hrn)
960 records = self.resolve_raw(type, name, must_exist=True)
963 # verify_cancreate_credential requires that the member lists
964 # (researchers, pis, etc) be filled in
965 self.fill_record_info(record)
967 self.verify_cancreate_credential(self.client_cred, record)
969 # TODO: Check permission that self.client_cred can access the object
971 object_gid = record.get_gid_object()
972 new_cred = Credential(subject = object_gid.get_subject())
973 new_cred.set_gid_caller(self.client_gid)
974 new_cred.set_gid_object(object_gid)
975 new_cred.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn)
976 new_cred.set_pubkey(object_gid.get_pubkey())
978 rl = self.determine_rights(type, name)
979 new_cred.set_privileges(rl)
981 # determine the type of credential that we want to use as a parent for
984 if (type == "ma") or (type == "node"):
985 auth_kind = "authority,ma"
986 else: # user, slice, sa
987 auth_kind = "authority,sa"
989 new_cred.set_parent(self.hierarchy.get_auth_cred(auth_hrn, kind=auth_kind))
994 return new_cred.save_to_string(save_parents=True)
997 # GENI API: get_ticket
999 # Retrieve a ticket. This operation is currently implemented on PLC
1000 # only (see SFA, engineering decisions); it is not implemented on
1003 # The ticket is filled in with information from the PLC database. This
1004 # information includes resources, and attributes such as user keys and
1007 # @param cred credential string
1008 # @param name name of the slice to retrieve a ticket for
1009 # @param rspec resource specification dictionary
1011 # @return the string representation of a ticket object
1013 def get_ticket(self, cred, name, rspec):
1014 self.decode_authentication(cred, "getticket")
1016 self.verify_object_belongs_to_me(name)
1018 self.verify_object_permission(name)
1020 # XXX much of this code looks like get_credential... are they so similar
1021 # that they should be combined?
1023 auth_hrn = get_authority(name)
1026 auth_info = self.get_auth_info(auth_hrn)
1028 records = self.resolve_raw("slice", name, must_exist=True)
1031 object_gid = record.get_gid_object()
1032 new_ticket = Ticket(subject = object_gid.get_subject())
1033 new_ticket.set_gid_caller(self.client_gid)
1034 new_ticket.set_gid_object(object_gid)
1035 new_ticket.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn)
1036 new_ticket.set_pubkey(object_gid.get_pubkey())
1038 self.fill_record_info(record)
1040 (attributes, rspec) = self.record_to_slice_info(record)
1042 new_ticket.set_attributes(attributes)
1043 new_ticket.set_rspec(rspec)
1045 new_ticket.set_parent(AuthHierarchy.get_auth_ticket(auth_hrn))
1050 return new_ticket.save_to_string(save_parents=True)
1053 # GENI_API: Create_gid
1055 # Create a new GID. For MAs and SAs that are physically located on the
1056 # registry, this allows a owner/operator/PI to create a new GID and have it
1057 # signed by his respective authority.
1059 # @param cred credential of caller
1060 # @param name hrn for new GID
1061 # @param uuid unique identifier for new GID
1062 # @param pkey_string public-key string (TODO: why is this a string and not a keypair object?)
1064 # @return the string representation of a GID object
1066 def create_gid(self, cred, name, uuid, pubkey_str):
1067 self.decode_authentication(cred, "getcredential")
1069 self.verify_object_belongs_to_me(name)
1071 self.verify_object_permission(name)
1074 uuid = create_uuid()
1077 pkey.load_pubkey_from_string(pubkey_str)
1078 gid = self.hierarchy.create_gid(name, uuid, pkey)
1080 return gid.save_to_string(save_parents=True)