--- /dev/null
+#
+# Geniwrapper XML-RPC and SOAP interfaces
+#
+#
+
+import sys
+import os
+import traceback
+import string
+import xmlrpclib
+from geni.util.auth import Auth
+from geni.util.config import *
+from geni.util.faults import *
+from geni.util.debug import *
+
+# See "2.2 Characters" in the XML specification:
+#
+# #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
+# avoiding
+# [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
+
+invalid_xml_ascii = map(chr, range(0x0, 0x8) + [0xB, 0xC] + range(0xE, 0x1F))
+xml_escape_table = string.maketrans("".join(invalid_xml_ascii), "?" * len(invalid_xml_ascii))
+
+def xmlrpclib_escape(s, replace = string.replace):
+ """
+ xmlrpclib does not handle invalid 7-bit control characters. This
+ function augments xmlrpclib.escape, which by default only replaces
+ '&', '<', and '>' with entities.
+ """
+
+ # This is the standard xmlrpclib.escape function
+ s = replace(s, "&", "&")
+ s = replace(s, "<", "<")
+ s = replace(s, ">", ">",)
+
+ # Replace invalid 7-bit control characters with '?'
+ return s.translate(xml_escape_table)
+
+def xmlrpclib_dump(self, value, write):
+ """
+ xmlrpclib cannot marshal instances of subclasses of built-in
+ types. This function overrides xmlrpclib.Marshaller.__dump so that
+ any value that is an instance of one of its acceptable types is
+ marshalled as that type.
+
+ xmlrpclib also cannot handle invalid 7-bit control characters. See
+ above.
+ """
+
+ # Use our escape function
+ args = [self, value, write]
+ if isinstance(value, (str, unicode)):
+ args.append(xmlrpclib_escape)
+
+ try:
+ # Try for an exact match first
+ f = self.dispatch[type(value)]
+ except KeyError:
+ raise
+ # Try for an isinstance() match
+ for Type, f in self.dispatch.iteritems():
+ if isinstance(value, Type):
+ f(*args)
+ return
+ raise TypeError, "cannot marshal %s objects" % type(value)
+ else:
+ f(*args)
+
+# You can't hide from me!
+xmlrpclib.Marshaller._Marshaller__dump = xmlrpclib_dump
+
+# SOAP support is optional
+try:
+ import SOAPpy
+ from SOAPpy.Parser import parseSOAPRPC
+ from SOAPpy.Types import faultType
+ from SOAPpy.NS import NS
+ from SOAPpy.SOAPBuilder import buildSOAP
+except ImportError:
+ SOAPpy = None
+
+import geni.methods
+
+def import_deep(name):
+ mod = __import__(name)
+ components = name.split('.')
+ for comp in components[1:]:
+ mod = getattr(mod, comp)
+ return mod
+
+class GeniAPI:
+
+ # flat list of method names
+ methods = geni.methods.methods
+
+ def __init__(self, config = "/usr/share/geniwrapper/geni/util/geni_config", encoding = "utf-8", peer_cert = None, interface = None):
+ self.encoding = encoding
+
+ # Better just be documenting the API
+ if config is None:
+ return
+
+ # Load configuration
+ self.config = Config(config)
+ self.auth = Auth(peer_cert)
+ self.interface = interface
+ self.plshell = self.getPLCShell()
+ self.basedir = self.config.GENI_BASE_DIR + os.sep
+ self.server_basedir = self.basedir + os.sep + "geni" + os.sep
+ self.hrn = self.config.GENI_INTERFACE_HRN
+
+
+ def getPLCShell(self):
+ self.plauth = {'Username': self.config.GENI_PLC_USER,
+ 'AuthMethod': 'password',
+ 'AuthString': self.config.GENI_PLC_PASSWORD}
+ try:
+ import PLC.Shell
+ shell = PLC.Shell.Shell(globals = globals())
+ shell.AuthCheck(self.plauth)
+ return shell
+ except ImportError:
+ # connect via xmlrpc
+ plc_host = self.config.GENI_PLC_HOST
+ plc_port = self.config.GENI_PLC_PORT
+ plc_api_path = self.config.GENI_PLC_API_PATH
+ url = "https://%(plc_host)s:%(plc_port)s/%(plc_api_path)s/" % \
+ locals()
+
+ shell = xmlrpclib.Server(url, verbose = 0, allow_none = True)
+ shell.AuthCheck(self.plauth)
+ return shell
+
+ def fill_record_pl_info(self, record):
+ """
+ Fill in the planetlab specific fields of a Geni record. This
+ involves calling the appropraite PLC method to retrie the
+ dtabase record for the object.
+
+ PLC data is filled into the pl_fino field of the record.
+
+ @param record record to fill in field (in/out param)
+ """
+ type = record.get_type()
+ pointer = record.get_pointer()
+
+ # records with pointer==-1 do not have plc info associated with them.
+ # for example, the top level authority records which are
+ # authorities, but not PL "sites"
+ if pointer == -1:
+ record.set_pl_info({})
+ return
+
+ if (type == "sa") or (type == "ma"):
+ pl_res = self.plshell.GetSites(self.plauth, [pointer])
+ elif (type == "slice"):
+ pl_res = self.plshell.GetSlices(self.plauth, [pointer])
+ elif (type == "user"):
+ pl_res = self.plshell.GetPersons(self.plauth, [pointer])
+ key_ids = pl_res[0]['key_ids']
+ keys = self.plshell.GetKeys(self.plauth, key_ids)
+ pubkeys = []
+ if keys:
+ pubkeys = [key['key'] for key in keys]
+ pl_res[0]['keys'] = pubkeys
+ elif (type == "node"):
+ pl_res = self.plshell.GetNodes(self.plauth, [pointer])
+ else:
+ raise UnknownGeniType(type)
+
+ if not pl_res:
+ # the planetlab record no longer exists
+ # TODO: delete the geni record ?
+ raise PlanetLabRecordDoesNotExist(record.get_name())
+
+ record.set_pl_info(pl_res[0])
+
+
+ def lookup_users(self, auth_table, user_id_list, role="*"):
+ record_list = []
+ for person_id in user_id_list:
+ user_records = auth_table.find("user", person_id, "pointer")
+ for user_record in user_records:
+ self.fill_record_info(user_record)
+
+ user_roles = user_record.get_pl_info().get("roles")
+ if (role=="*") or (role in user_roles):
+ record_list.append(user_record.get_name())
+ return record_list
+
+ def fill_record_geni_info(self, record):
+ geni_info = {}
+ type = record.get_type()
+
+ if (type == "slice"):
+ auth_table = self.auth.get_auth_table(self.auth.get_authority(record.get_name()))
+ person_ids = record.pl_info.get("person_ids", [])
+ researchers = self.lookup_users(auth_table, person_ids)
+ geni_info['researcher'] = researchers
+
+ elif (type == "sa"):
+ auth_table = self.auth.get_auth_table(record.get_name())
+ person_ids = record.pl_info.get("person_ids", [])
+ pis = self.lookup_users(auth_table, person_ids, "pi")
+ geni_info['pi'] = pis
+ # TODO: OrganizationName
+
+ elif (type == "ma"):
+ auth_table = self.auth.get_auth_table(record.get_name())
+ person_ids = record.pl_info.get("person_ids", [])
+ operators = self.lookup_users(auth_table, person_ids, "tech")
+ geni_info['operator'] = operators
+ # TODO: OrganizationName
+
+ auth_table = self.auth.get_auth_table(record.get_name())
+ person_ids = record.pl_info.get("person_ids", [])
+ owners = self.lookup_users(auth_table, person_ids, "admin")
+ geni_info['owner'] = owners
+
+ elif (type == "node"):
+ geni_info['dns'] = record.pl_info.get("hostname", "")
+ # TODO: URI, LatLong, IP, DNS
+
+ elif (type == "user"):
+ geni_info['email'] = record.pl_info.get("email", "")
+ # TODO: PostalAddress, Phone
+
+ record.set_geni_info(geni_info)
+
+ def fill_record_info(self, record):
+ """
+ Given a geni record, fill in the PLC specific and Geni specific
+ fields in the record.
+ """
+ self.fill_record_pl_info(record)
+ self.fill_record_geni_info(record)
+
+ def update_membership_list(self, oldRecord, record, listName, addFunc, delFunc):
+ # get a list of the HRNs tht are members of the old and new records^M
+ if oldRecord:
+ if oldRecord.pl_info == None:
+ oldRecord.pl_info = {}
+ oldList = oldRecord.get_geni_info().get(listName, [])
+ else:
+ oldList = []
+ newList = record.get_geni_info().get(listName, [])
+
+ # if the lists are the same, then we don't have to update anything
+ if (oldList == newList):
+ return
+
+ # build a list of the new person ids, by looking up each person to get
+ # their pointer
+ newIdList = []
+ for hrn in newList:
+ userRecord = self.resolve_raw("user", hrn)[0]
+ newIdList.append(userRecord.get_pointer())
+
+ # build a list of the old person ids from the person_ids field of the
+ # pl_info
+ if oldRecord:
+ oldIdList = oldRecord.plinfo.get("person_ids", [])
+ containerId = oldRecord.get_pointer()
+ else:
+ # if oldRecord==None, then we are doing a Register, instead of an
+ # update.
+ oldIdList = []
+ containerId = record.get_pointer()
+
+ # add people who are in the new list, but not the oldList
+ for personId in newIdList:
+ if not (personId in oldIdList):
+ print "adding id", personId, "to", record.get_name()
+ addFunc(self.plauth, personId, containerId)
+
+ # remove people who are in the old list, but not the new list
+ for personId in oldIdList:
+ if not (personId in newIdList):
+ print "removing id", personId, "from", record.get_name()
+ delFunc(self.plauth, personId, containerId)
+
+ def update_membership(self, oldRecord, record):
+ if record.type == "slice":
+ self.update_membership_list(oldRecord, record, 'researcher',
+ self.plshell.AddPersonToSlice,
+ self.plshell.DeletePersonFromSlice)
+ elif record.type == "sa":
+ # TODO
+ pass
+ elif record.type == "ma":
+ # TODO
+ pass
+
+
+ def callable(self, method):
+ """
+ Return a new instance of the specified method.
+ """
+ # Look up method
+ if method not in self.methods:
+ raise GeniInvalidAPIMethod, method
+
+ # Get new instance of method
+ try:
+ classname = method.split(".")[-1]
+ module = __import__("geni.methods." + method, globals(), locals(), [classname])
+ callablemethod = getattr(module, classname)(self)
+ return getattr(module, classname)(self)
+ except ImportError, AttributeError:
+ raise GeniInvalidAPIMethod, method
+
+ def call(self, source, method, *args):
+ """
+ Call the named method from the specified source with the
+ specified arguments.
+ """
+ function = self.callable(method)
+ function.source = source
+ return function(*args)
+
+ def handle(self, source, data):
+ """
+ Handle an XML-RPC or SOAP request from the specified source.
+ """
+
+ # Parse request into method name and arguments
+ try:
+ interface = xmlrpclib
+ (args, method) = xmlrpclib.loads(data)
+ methodresponse = True
+ except Exception, e:
+ if SOAPpy is not None:
+ interface = SOAPpy
+ (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
+ method = r._name
+ args = r._aslist()
+ # XXX Support named arguments
+ else:
+ raise e
+
+ try:
+ result = self.call(source, method, *args)
+ except Exception, fault:
+ traceback.print_exc(file = log)
+ # Handle expected faults
+ if interface == xmlrpclib:
+ result = fault
+ methodresponse = None
+ elif interface == SOAPpy:
+ result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
+ result._setDetail("Fault %d: %s" % (fault.faultCode, fault.faultString))
+
+ # Return result
+ if interface == xmlrpclib:
+ if not isinstance(result, GeniFault):
+ result = (result,)
+
+ data = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
+ elif interface == SOAPpy:
+ data = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)
+
+ return data
--- /dev/null
+#
+# GeniAPI authentication
+#
+#
+
+import time
+from geni.util.faults import *
+from geni.util.excep import *
+from geni.util.credential import Credential
+from geni.util.trustedroot import TrustedRootList
+from geni.util.hierarchy import Hierarchy
+from geni.util.rights import RightList
+from geni.util.genitable import *
+
+
+class Auth:
+ """
+ Credential based authentication
+ """
+
+ def __init__(self, peer_cert):
+ self.peer_cert = peer_cert
+ self.hierarchy = Hierarchy()
+ self.trusted_cert_list = TrustedRootList().get_list()
+
+
+
+ def check(self, cred, operation):
+ """
+ Check the credential against the peer cert (callerGID included
+ in the credential matches the caller that is connected to the
+ HTTPS connection, check if the credential was signed by a
+ trusted cert and check if the credential is allowd to perform
+ the specified operation.
+ """
+ self.client_cred = Credential(string = cred)
+ self.client_gid = self.client_cred.get_gid_caller()
+ self.object_gid = self.client_cred.get_gid_object()
+
+ # make sure the client_gid is not blank
+ if not self.client_gid:
+ raise MissingCallerGID(self.client_cred.get_subject())
+
+ # make sure the client_gid matches client's certificate
+ peer_cert = self.peer_cert
+ if not peer_cert.is_pubkey(self.client_gid.get_pubkey()):
+ raise ConnectionKeyGIDMismatch(self.client_gid.get_subject())
+
+ # make sure the client is allowed to perform the operation
+ if operation:
+ if not self.client_cred.can_perform(operation):
+ raise InsufficientRights(operation)
+
+ if self.trusted_cert_list:
+ self.client_cred.verify_chain(self.trusted_cert_list)
+ if self.client_gid:
+ self.client_gid.verify_chain(self.trusted_cert_list)
+ if self.object_gid:
+ self.object_gid.verify_chain(self.trusted_cert_list)
+
+ return True
+
+
+ def get_auth_info(self, auth_hrn):
+ """
+ Given an authority name, return the information for that authority.
+ This is basically a stub that calls the hierarchy module.
+
+ @param auth_hrn human readable name of authority
+ """
+
+ return self.hierarchy.get_auth_info(auth_hrn)
+
+
+ def get_auth_table(self, auth_name):
+ """
+ Given an authority name, return the database table for that authority.
+ If the databse table does not exist, then one will be automatically
+ created.
+
+ @param auth_name human readable name of authority
+ """
+ auth_info = self.get_auth_info(auth_name)
+ table = GeniTable(hrn=auth_name,
+ cninfo=auth_info.get_dbinfo())
+ # if the table doesn't exist, then it means we haven't put any records
+ # into this authority yet.
+
+ if not table.exists():
+ print >> log, "Registry: creating table for authority", auth_name
+ table.create()
+
+ return table
+
+ def veriry_auth_belongs_to_me(self, name):
+ """
+ Verify that an authority belongs to our hierarchy.
+ This is basically left up to the implementation of the hierarchy
+ module. If the specified name does not belong, ane exception is
+ thrown indicating the caller should contact someone else.
+
+ @param auth_name human readable name of authority
+ """
+
+ self.get_auth_info(name)
+
+
+ def verify_object_belongs_to_me(self, name):
+ """
+ Verify that an object belongs to our hierarchy. By extension,
+ this implies that the authority that owns the object belongs
+ to our hierarchy. If it does not an exception is thrown.
+
+ @param name human readable name of object
+ """
+ auth_name = self.get_authority(name)
+ if not auth_name:
+ # the root authority belongs to the registry by default?
+ # TODO: is this true?
+ return
+ self.verify_auth_belongs_to_me(auth_name)
+
+ def verify_auth_belongs_to_me(self, name):
+ # get auth info will throw an exception if the authority doesnt exist
+ self.get_auth_info(name)
+
+
+ def verify_object_permission(self, name):
+ """
+ Verify that the object gid that was specified in the credential
+ allows permission to the object 'name'. This is done by a simple
+ prefix test. For example, an object_gid for plc.arizona would
+ match the objects plc.arizona.slice1 and plc.arizona.
+
+ @param name human readable name to test
+ """
+ object_hrn = self.object_gid.get_hrn()
+ if object_hrn == name:
+ return
+ if name.startswith(object_hrn + "."):
+ return
+ raise PermissionError(name)
+
+ def verify_cancreate_credential(self, src_cred, record):
+ """
+ Verify that a user can retrive a particular type of credential.
+ For slices, the user must be on the researcher list. For SA and
+ MA the user must be on the pi and operator lists respectively
+ """
+
+ type = record.get_type()
+ cred_object_hrn = src_cred.get_gid_object().get_hrn()
+ if cred_object_hrn in [self.config.GENI_REGISTRY_ROOT_AUTH]:
+ return
+ if type=="slice":
+ researchers = record.get_geni_info().get("researcher", [])
+ if not (cred_object_hrn in researchers):
+ raise PermissionError(cred_object_hrn + " is not in researcher list for " + record.get_name())
+ elif type == "sa":
+ pis = record.get_geni_info().get("pi", [])
+ if not (cred_object_hrn in pis):
+ raise PermissionError(cred_object_hrn + " is not in pi list for " + record.get_name())
+ elif type == "ma":
+ operators = record.get_geni_info().get("operator", [])
+ if not (cred_object_hrn in operators):
+ raise PermissionError(cred_object_hrn + " is not in operator list for " + record.get_name())
+
+ def get_leaf(self, hrn):
+ parts = hrn.split(".")
+ return ".".join(parts[-1:])
+
+ def get_authority(self, hrn):
+
+ parts = hrn.split(".")
+ return ".".join(parts[:-1])
+
+ def get_auth_type(self, type):
+ if (type=="slice") or (type=="user") or (type=="sa"):
+ return "sa"
+ elif (type=="component") or (type=="ma"):
+ return "ma"
+ else:
+ raise UnknownGeniType(type)
+
+ def hrn_to_pl_slicename(self, hrn):
+ parts = hrn.split(".")
+ return parts[-2] + "_" + parts[-1]
+
+ # assuming hrn is the hrn of an authority, return the plc authority name
+ def hrn_to_pl_authname(self, hrn):
+ parts = hrn.split(".")
+ return parts[-1]
+
+ # assuming hrn is the hrn of an authority, return the plc login_base
+ def hrn_to_pl_login_base(self, hrn):
+ return self.hrn_to_pl_authname(hrn)
+
--- /dev/null
+#
+# GeniAPI XML-RPC faults
+#
+#
+
+import xmlrpclib
+
+class GeniFault(xmlrpclib.Fault):
+ def __init__(self, faultCode, faultString, extra = None):
+ if extra:
+ faultString += ": " + extra
+ xmlrpclib.Fault.__init__(self, faultCode, faultString)
+
+class GeniInvalidAPIMethod(GeniFault):
+ def __init__(self, method, role = None, extra = None):
+ faultString = "Invalid method " + method
+ if role:
+ faultString += " for role " + role
+ GeniFault.__init__(self, 100, faultString, extra)
+
+class GeniInvalidArgumentCount(GeniFault):
+ def __init__(self, got, min, max = min, extra = None):
+ if min != max:
+ expected = "%d-%d" % (min, max)
+ else:
+ expected = "%d" % min
+ faultString = "Expected %s arguments, got %d" % \
+ (expected, got)
+ GeniFault.__init__(self, 101, faultString, extra)
+
+class GeniInvalidArgument(GeniFault):
+ def __init__(self, extra = None, name = None):
+ if name is not None:
+ faultString = "Invalid %s value" % name
+ else:
+ faultString = "Invalid argument"
+ GeniFault.__init__(self, 102, faultString, extra)
+
+class GeniAuthenticationFailure(GeniFault):
+ def __init__(self, extra = None):
+ faultString = "Failed to authenticate call"
+ GeniFault.__init__(self, 103, faultString, extra)
+
+class GeniDBError(GeniFault):
+ def __init__(self, extra = None):
+ faultString = "Database error"
+ GeniFault.__init__(self, 106, faultString, extra)
+
+class GeniPermissionDenied(GeniFault):
+ def __init__(self, extra = None):
+ faultString = "Permission denied"
+ GeniFault.__init__(self, 108, faultString, extra)
+
+class GeniNotImplemented(GeniFault):
+ def __init__(self, extra = None):
+ faultString = "Not fully implemented"
+ GeniFault.__init__(self, 109, faultString, extra)
+
+class GeniAPIError(GeniFault):
+ def __init__(self, extra = None):
+ faultString = "Internal API error"
+ GeniFault.__init__(self, 111, faultString, extra)
--- /dev/null
+#
+# Shared type definitions
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id: Parameter.py 5574 2007-10-25 20:33:17Z thierry $
+#
+
+from types import *
+from geni.util.faults import *
+
+class Parameter:
+ """
+ Typed value wrapper. Use in accepts and returns to document method
+ parameters. Set the optional and default attributes for
+ sub-parameters (i.e., dict fields).
+ """
+
+ def __init__(self, type, doc = "",
+ min = None, max = None,
+ optional = None,
+ ro = False,
+ nullok = False):
+ # Basic type of the parameter. Must be a builtin type
+ # that can be marshalled by XML-RPC.
+ self.type = type
+
+ # Documentation string for the parameter
+ self.doc = doc
+
+ # Basic value checking. For numeric types, the minimum and
+ # maximum possible values, inclusive. For string types, the
+ # minimum and maximum possible UTF-8 encoded byte lengths.
+ self.min = min
+ self.max = max
+
+ # Whether the sub-parameter is optional or not. If None,
+ # unknown whether it is optional.
+ self.optional = optional
+
+ # Whether the DB field is read-only.
+ self.ro = ro
+
+ # Whether the DB field can be NULL.
+ self.nullok = nullok
+
+ def type(self):
+ return self.type
+
+ def __repr__(self):
+ return repr(self.type)
+
+class Mixed(tuple):
+ """
+ A list (technically, a tuple) of types. Use in accepts and returns
+ to document method parameters that may return mixed types.
+ """
+
+ def __new__(cls, *types):
+ return tuple.__new__(cls, types)
+
+
+def python_type(arg):
+ """
+ Returns the Python type of the specified argument, which may be a
+ Python type, a typed value, or a Parameter.
+ """
+
+ if isinstance(arg, Parameter):
+ arg = arg.type
+
+ if isinstance(arg, type):
+ return arg
+ else:
+ return type(arg)
+
+def xmlrpc_type(arg):
+ """
+ Returns the XML-RPC type of the specified argument, which may be a
+ Python type, a typed value, or a Parameter.
+ """
+
+ arg_type = python_type(arg)
+
+ if arg_type == NoneType:
+ return "nil"
+ elif arg_type == IntType or arg_type == LongType:
+ return "int"
+ elif arg_type == bool:
+ return "boolean"
+ elif arg_type == FloatType:
+ return "double"
+ elif arg_type in StringTypes:
+ return "string"
+ elif arg_type == ListType or arg_type == TupleType:
+ return "array"
+ elif arg_type == DictType:
+ return "struct"
+ elif arg_type == Mixed:
+ # Not really an XML-RPC type but return "mixed" for
+ # documentation purposes.
+ return "mixed"
+ else:
+ raise GeniAPIError, "XML-RPC cannot marshal %s objects" % arg_type