+from sfa.storage.model import (
+ make_record, RegRecord, RegAuthority, RegUser, RegSlice, RegKey,
+ augment_with_sfa_builtins)
+# the types that we need to exclude from sqlobjects before being able to dump
+# them on the xmlrpc wire
+from sqlalchemy.orm.collections import InstrumentedList
+
+# historical note -- april 2014
+# the myslice chaps rightfully complained about the following discrepancy
+# they found that
+# * read operations (resolve) expose stuff like e.g.
+# 'reg-researchers', or 'reg-pis', but that
+# * write operations (register, update) need e.g.
+# 'researcher' or 'pi' to be set - reg-* are just ignored
+#
+# the '_normalize_input' helper functions below aim at ironing this out
+# however in order to break as few code as possible we essentially
+# make sure that *both* fields are set upon entering the write methods
+# (again: register and update) for legacy, as some driver code
+# might depend on the presence of, say, 'researcher'
+
+# normalize an input record to a write method - register or update
+# e.g. registry calls this 'reg-researchers'
+# while some drivers call this 'researcher'
+# we need to make sure that both keys appear and are the same
+
+
+def _normalize_input(record, reg_key, driver_key):
+ # this looks right, use this for both keys
+ if reg_key in record:
+ # and issue a warning if they were both set and different
+ # as we're overwriting some user data here
+ if driver_key in record:
+ logger.warning("normalize_input: incoming record has both values, using {}"
+ .format(reg_key))
+ record[driver_key] = record[reg_key]
+ # we only have one key set, duplicate for the other one
+ elif driver_key in record:
+ logger.warning("normalize_input: you should use '{}' instead of '{}'"
+ .format(reg_key, driver_key))
+ record[reg_key] = record[driver_key]
+
+
+def normalize_input_record(record):
+ _normalize_input(record, 'reg-researchers', 'researcher')
+ _normalize_input(record, 'reg-pis', 'pi')
+ _normalize_input(record, 'reg-keys', 'keys')
+ # xxx the keys thing could use a little bit more attention:
+ # some parts of the code are using 'keys' while they should use 'reg-keys'
+ # but I run out of time for now
+ if 'reg-keys' in record:
+ record['keys'] = record['reg-keys']
+ return record
+
+
+class RegistryManager:
+
+ def __init__(self, config):
+ logger.debug("Creating RegistryManager[{}]".format(id(self)))
+
+ # The GENI GetVersion call
+ def GetVersion(self, api, options):
+ peers = {hrn: interface.get_url()
+ for (hrn, interface) in api.registries.iteritems()
+ if hrn != api.hrn}
+ xrn = Xrn(api.hrn, type='authority')
+ return version_core({'interface': 'registry',
+ 'sfa': 3,
+ 'hrn': xrn.get_hrn(),
+ 'urn': xrn.get_urn(),
+ 'peers': peers})
+
+ def GetCredential(self, api, xrn, input_type, caller_xrn=None):
+ # convert xrn to hrn
+ if input_type:
+ hrn, _ = urn_to_hrn(xrn)
+ type = input_type
+ else:
+ hrn, type = urn_to_hrn(xrn)
+
+ # Slivers don't have credentials but users should be able to
+ # specify a sliver xrn and receive the slice's credential
+ # However if input_type is specified
+ if type == 'sliver' or (not input_type and '-' in Xrn(hrn).leaf):
+ slice_xrn = api.driver.sliver_to_slice_xrn(hrn)
+ hrn = slice_xrn.hrn
+
+ # Is this a root or sub authority
+ auth_hrn = api.auth.get_authority(hrn)
+ if not auth_hrn or hrn == api.config.SFA_INTERFACE_HRN:
+ auth_hrn = hrn
+ auth_info = api.auth.get_auth_info(auth_hrn)
+
+ # get record info
+ dbsession = api.dbsession()
+ record = dbsession.query(RegRecord).filter_by(
+ type=type, hrn=hrn).first()
+ if not record:
+ raise RecordNotFound("hrn={}, type={}".format(hrn, type))
+
+ # get the callers gid
+ # if caller_xrn is not specified assume the caller is the record
+ # object itself.
+ if not caller_xrn:
+ caller_hrn = hrn
+ caller_gid = record.get_gid_object()
+ else:
+ caller_hrn, caller_type = urn_to_hrn(caller_xrn)
+ if caller_type:
+ caller_record = dbsession.query(RegRecord).filter_by(
+ hrn=caller_hrn, type=caller_type).first()
+ else:
+ caller_record = dbsession.query(
+ RegRecord).filter_by(hrn=caller_hrn).first()
+ if not caller_record:
+ raise RecordNotFound(
+ "Unable to associated caller (hrn={}, type={}) "
+ "with credential for (hrn: {}, type: {})"
+ .format(caller_hrn, caller_type, hrn, type))
+ caller_gid = GID(string=caller_record.gid)
+
+ object_hrn = record.get_gid_object().get_hrn()
+ # call the builtin authorization/credential generation engine
+ rights = api.auth.determine_user_rights(caller_hrn, record)
+ # make sure caller has rights to this object
+ if rights.is_empty():
+ raise PermissionError("{} has no rights to {} ({})"
+ .format(caller_hrn, object_hrn, xrn))
+ object_gid = GID(string=record.gid)
+ new_cred = Credential(subject=object_gid.get_subject())
+ new_cred.set_gid_caller(caller_gid)
+ new_cred.set_gid_object(object_gid)
+ new_cred.set_issuer_keys(
+ auth_info.get_privkey_filename(), auth_info.get_gid_filename())
+ # new_cred.set_pubkey(object_gid.get_pubkey())
+ new_cred.set_privileges(rights)
+ new_cred.get_privileges().delegate_all_privileges(True)
+ if hasattr(record, 'expires'):
+ date = utcparse(record.expires)
+ expires = datetime_to_epoch(date)
+ new_cred.set_expiration(int(expires))
+ auth_kind = "authority,ma,sa"
+ # Parent not necessary, verify with certs
+ # new_cred.set_parent(api.auth.hierarchy.get_auth_cred(auth_hrn, kind=auth_kind))
+ new_cred.encode()
+ new_cred.sign()
+
+ return new_cred.save_to_string(save_parents=True)
+
+ # the default for full, which means 'dig into the testbed as well', should
+ # be false
+ def Resolve(self, api, xrns, type=None, details=False):
+
+ dbsession = api.dbsession()
+ if not isinstance(xrns, list):
+ # try to infer type if not set and we get a single input
+ if not type:
+ type = Xrn(xrns).get_type()
+ xrns = [xrns]
+ hrns = [urn_to_hrn(xrn)[0] for xrn in xrns]
+
+ # load all known registry names into a prefix tree and attempt to find
+ # the longest matching prefix
+ # create a dict where key is a registry hrn and its value is a list
+ # of hrns at that registry (determined by the known prefix tree).
+ xrn_dict = {}
+ registries = api.registries
+ tree = prefixTree()
+ registry_hrns = registries.keys()
+ tree.load(registry_hrns)
+ for xrn in xrns:
+ registry_hrn = tree.best_match(urn_to_hrn(xrn)[0])
+ if registry_hrn not in xrn_dict:
+ xrn_dict[registry_hrn] = []
+ xrn_dict[registry_hrn].append(xrn)
+
+ records = []
+ for registry_hrn in xrn_dict:
+ # skip the hrn without a registry hrn
+ # XX should we let the user know the authority is unknown?
+ if not registry_hrn:
+ continue
+
+ # if the best match (longest matching hrn) is not the local registry,
+ # forward the request
+ xrns = xrn_dict[registry_hrn]
+ if registry_hrn != api.hrn:
+ credential = api.getCredential()
+ interface = api.registries[registry_hrn]
+ server_proxy = api.server_proxy(interface, credential)
+ # should propagate the details flag but that's not supported
+ # in the xmlrpc interface yet
+ # peer_records = server_proxy.Resolve(xrns, credential,type, details=details)
+ peer_records = server_proxy.Resolve(xrns, credential)
+ # pass foreign records as-is
+ # previous code used to read
+ # records.extend([SfaRecord(dict=record).as_dict() for record in peer_records])
+ # not sure why the records coming through xmlrpc had to be
+ # processed at all
+ records.extend(peer_records)
+
+ # try resolving the remaining unfound records at the local registry
+ local_hrns = list(set(hrns).difference(
+ [record['hrn'] for record in records]))
+ #
+ local_records = dbsession.query(RegRecord).filter(
+ RegRecord.hrn.in_(local_hrns))
+ if type:
+ local_records = local_records.filter_by(type=type)
+ local_records = local_records.all()
+
+ for local_record in local_records:
+ augment_with_sfa_builtins(local_record)
+
+ logger.info("Resolve, (details={}, type={}) local_records={} "
+ .format(details, type, local_records))
+ local_dicts = [record.__dict__ for record in local_records]
+
+ if details:
+ # in details mode we get as much info as we can, which involves contacting the
+ # testbed for getting implementation details about the record
+ api.driver.augment_records_with_testbed_info(local_dicts)
+ # also we fill the 'url' field for known authorities
+ # used to be in the driver code, sounds like a poorman thing though
+
+ def solve_neighbour_url(record):
+ if not record.type.startswith('authority'):
+ return
+ hrn = record.hrn
+ for neighbour_dict in [api.aggregates, api.registries]:
+ if hrn in neighbour_dict:
+ record.url = neighbour_dict[hrn].get_url()
+ return
+ for record in local_records:
+ solve_neighbour_url(record)
+
+ # convert local record objects to dicts for xmlrpc
+ # xxx somehow here calling dict(record) issues a weird error
+ # however record.todict() seems to work fine
+ # records.extend( [ dict(record) for record in local_records ] )
+ records.extend([record.record_to_dict(exclude_types=(
+ InstrumentedList,)) for record in local_records])
+
+ if not records:
+ raise RecordNotFound(str(hrns))
+
+ return records
+
+ def List(self, api, xrn, origin_hrn=None, options=None):
+ if options is None:
+ options = {}
+ dbsession = api.dbsession()
+ # load all know registry names into a prefix tree and attempt to find
+ # the longest matching prefix