check in modpythonapi from raven project
authorScott Baker <bakers@cs.arizona.edu>
Tue, 29 Sep 2009 22:36:34 +0000 (22:36 +0000)
committerScott Baker <bakers@cs.arizona.edu>
Tue, 29 Sep 2009 22:36:34 +0000 (22:36 +0000)
12 files changed:
sfa/server/modpythonapi/ApiExceptionCodes.py [new file with mode: 0644]
sfa/server/modpythonapi/ApiExceptions.py [new file with mode: 0644]
sfa/server/modpythonapi/AuthenticatedApi.py [new file with mode: 0755]
sfa/server/modpythonapi/AuthenticatedClient.py [new file with mode: 0755]
sfa/server/modpythonapi/BaseApi.py [new file with mode: 0755]
sfa/server/modpythonapi/BaseClient.py [new file with mode: 0755]
sfa/server/modpythonapi/ModPython.py [new file with mode: 0755]
sfa/server/modpythonapi/TestApi.py [new file with mode: 0755]
sfa/server/modpythonapi/installTest.sh [new file with mode: 0755]
sfa/server/modpythonapi/test.py [new file with mode: 0755]
sfa/server/modpythonapi/test.sh [new file with mode: 0755]
sfa/server/modpythonapi/testapi.conf [new file with mode: 0644]

diff --git a/sfa/server/modpythonapi/ApiExceptionCodes.py b/sfa/server/modpythonapi/ApiExceptionCodes.py
new file mode 100644 (file)
index 0000000..cd811d2
--- /dev/null
@@ -0,0 +1,2 @@
+FAULT_UNHANDLEDSERVEREXCEPTION = 901
+FAULT_BADREQUESTHASH = 902
diff --git a/sfa/server/modpythonapi/ApiExceptions.py b/sfa/server/modpythonapi/ApiExceptions.py
new file mode 100644 (file)
index 0000000..834afd2
--- /dev/null
@@ -0,0 +1,15 @@
+import traceback
+import xmlrpclib
+
+FAULTCODE = 900
+
+class UnhandledServerException(xmlrpclib.Fault):
+    def __init__(self, type, value, tb):
+        exc_str = ''.join(traceback.format_exception(type, value, tb))
+        faultString = exc_str # "Unhandled exception: " + str(type) + "\n" + exc_str
+        xmlrpclib.Fault.__init__(self, FAULTCODE + 1, faultString)
+
+class BadRequestHash(xmlrpclib.Fault):    
+   def __init__(self, hash = None):
+        faultString = "bad request hash: " + str(hash)
+        xmlrpclib.Fault.__init__(self, FAULTCODE + 2, faultString)
diff --git a/sfa/server/modpythonapi/AuthenticatedApi.py b/sfa/server/modpythonapi/AuthenticatedApi.py
new file mode 100755 (executable)
index 0000000..c909346
--- /dev/null
@@ -0,0 +1,71 @@
+import xmlrpclib
+
+from BaseApi import BaseApi
+
+from sfa.trust.credential import Credential
+from sfa.trust.gid import GID
+from sfa.trust.trustedroot import TrustedRootList
+
+from ApiExceptionCodes import *
+
+class BadRequestHash(xmlrpclib.Fault):
+   def __init__(self, hash = None):
+        faultString = "bad request hash: " + str(hash)
+        xmlrpclib.Fault.__init__(self, FAULT_BADREQUESTHASH, faultString)
+
+class AuthenticatedApi(BaseApi):
+    def __init__(self, encoding = "utf-8", trustedRootsDir=None):
+        BaseApi.__init__(self, encoding)
+        if trustedRootsDir:
+            self.trusted_cert_list = TrustedRootList(trustedRootsDir).get_list()
+        else:
+            self.trusted_cert_list = None
+
+    def register_functions(self):
+        BaseApi.register_functions(self)
+        self.register_function(self.gidNoop)
+
+    def verifyGidRequestHash(self, gid, hash, arglist):
+        key = gid.get_pubkey()
+        if not key.verify_string(str(arglist), hash):
+            raise BadRequestHash(hash)
+
+    def verifyCredRequestHash(self, cred, hash, arglist):
+        gid = cred.get_gid_caller()
+        self.verifyGidRequestHash(gid, hash, arglist)
+
+    def validateGid(self, gid):
+        if self.trusted_cert_list:
+            gid.verify_chain(self.trusted_cert_list)
+
+    def validateCred(self, cred):
+        if self.trusted_cert_list:
+            cred.verify_chain(self.trusted_cert_list)
+            caller_gid = cred.get_gid_caller()
+            object_gid = cred.get_gid_object()
+            if caller_gid:
+                caller_gid.verify_chain(self.trusted_cert_list)
+            if object_gid:
+                object_gid.verify_chain(self.trusted_cert_list)
+
+    def authenticateGid(self, gidStr, argList, requestHash):
+        gid = GID(string = gidStr)
+        self.validateGid(gid)
+        self.verifyGidRequestHash(gid, requestHash, argList)
+        return gid
+
+    def authenticateCred(self, credStr, argList, requestHash):
+        cred = Credential(string = credStr)
+        self.validateCred(cred)
+        self.verifyCredRequestHash(cred, requestHash, argList)
+        return cred
+
+    def gidNoop(self, gidStr, value, requestHash):
+        self.authenticateGid(gidStr, [gidStr, value], requestHash)
+        return value
+
+    def credNoop(self, credStr, value, requestHash):
+        self.authenticateCred(credStr, [credStr, value], requestHash)
+        return value
+
+
diff --git a/sfa/server/modpythonapi/AuthenticatedClient.py b/sfa/server/modpythonapi/AuthenticatedClient.py
new file mode 100755 (executable)
index 0000000..6b705fc
--- /dev/null
@@ -0,0 +1,24 @@
+from sfa.trust.certificate import Keypair
+from sfa.trust.gid import GID
+
+from BaseClient import BaseClient
+
+class AuthenticatedClient(BaseClient):
+    def __init__(self, url, private_key_file, gid_file=None, cred_file=None):
+        BaseClient.__init__(self, url)
+        self.private_key_file = private_key_file
+        self.gid_file = gid_file
+        self.cred_file = cred_file
+        self.private_key = Keypair(filename = self.private_key_file)
+        if gid_file:
+            self.gid = GID(filename = self.gid_file)
+        if cred_file:
+            self.cred = Credential(filename = self.cred_file)
+
+    def computeRequestHash(self, argList):
+        return self.private_key.sign_string(str(argList))
+
+    def gidNoop(self, value):
+        gidStr = self.gid.save_to_string(True)
+        reqHash = self.computeRequestHash([gidStr, value])
+        return self.server.gidNoop(gidStr, value, reqHash)
diff --git a/sfa/server/modpythonapi/BaseApi.py b/sfa/server/modpythonapi/BaseApi.py
new file mode 100755 (executable)
index 0000000..95538ad
--- /dev/null
@@ -0,0 +1,203 @@
+#
+# PLCAPI XML-RPC and SOAP interfaces
+#
+# Aaron Klingaman <alk@absarokasoft.com>
+# Mark Huang <mlhuang@cs.princeton.edu>
+#
+# Copyright (C) 2004-2006 The Trustees of Princeton University
+# $Id: API.py 14587 2009-07-19 13:18:50Z thierry $
+# $URL: https://svn.planet-lab.org/svn/PLCAPI/trunk/PLC/API.py $
+#
+
+import sys
+import traceback
+import string
+
+import xmlrpclib
+import logging
+import logging.handlers
+
+from ApiExceptionCodes import *
+
+# Wrapper around xmlrpc fault to include a traceback of the server to the
+# client. This is done to aid in debugging from a client perspective.
+
+class FaultWithTraceback(xmlrpclib.Fault):
+    def __init__(self, code, faultString, exc_info):
+        type, value, tb = exc_info
+        exc_str = ''.join(traceback.format_exception(type, value, tb))
+        faultString = faultString + "\nFAULT_TRACEBACK:" + exc_str
+        xmlrpclib.Fault.__init__(self, code, faultString)
+
+# Exception to report to the caller when some non-XMLRPC fault occurs on the
+# server. For example a TypeError.
+
+class UnhandledServerException(FaultWithTraceback):
+    def __init__(self, exc_info):
+        type, value, tb = exc_info
+        faultString = "Unhandled exception: " + str(type)
+        FaultWithTraceback.__init__(self, FAULT_UNHANDLEDSERVEREXCEPTION, faultString, exc_info)
+
+# 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, "&", "&amp;")
+    s = replace(s, "<", "&lt;")
+    s = replace(s, ">", "&gt;",)
+
+    # 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:
+        # 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
+
+def import_deep(name):
+    mod = __import__(name)
+    components = name.split('.')
+    for comp in components[1:]:
+        mod = getattr(mod, comp)
+    return mod
+
+class BaseApi:
+    def __init__(self, encoding = "utf-8"):
+        self.encoding = encoding
+        self.init_logger()
+        self.funcs = {}
+        self.register_functions()
+
+    def init_logger(self):
+        self.logger = logging.getLogger("ApiLogger")
+        self.logger.setLevel(logging.INFO)
+        self.logger.addHandler(logging.handlers.RotatingFileHandler(self.get_log_name(), maxBytes=100000, backupCount=5))
+
+    def get_log_name(self):
+        return "/tmp/apilogfile.txt"
+
+    def register_functions(self):
+        self.register_function(self.noop)
+
+    def register_function(self, function, name = None):
+        if name is None:
+            name = function.__name__
+        self.funcs[name] = function
+
+    def call(self, source, method, *args):
+        """
+        Call the named method from the specified source with the
+        specified arguments.
+        """
+
+        if not method in self.funcs:
+            raise "Unknown method: " + method
+
+        return self.funcs[method](*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
+
+        self.logger.debug("OP:" + str(method) + " from " + str(source))
+
+        try:
+            result = self.call(source, method, *args)
+        except xmlrpclib.Fault, fault:
+            self.logger.warning("FAULT: " + str(fault.faultCode) + " " + str(fault.faultString))
+            self.logger.info(traceback.format_exc())
+            # Handle expected faults
+            if interface == xmlrpclib:
+                result = FaultWithTraceback(fault.faultCode, fault.faultString, sys.exc_info())
+                methodresponse = None
+            elif interface == SOAPpy:
+                result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
+                result._setDetail("Fault %d: %s" % (fault.faultCode, fault.faultString))
+                self.logger.debug
+        except:
+            self.logger.warning("EXCEPTION: " + str(sys.exc_info()[0]))
+            self.logger.info(traceback.format_exc())
+            result = UnhandledServerException(sys.exc_info())
+            methodresponse = None
+
+        # Return result
+        if interface == xmlrpclib:
+            if not isinstance(result, xmlrpclib.Fault):
+                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
+
+    def noop(self, value):
+        return value
+
+
+
diff --git a/sfa/server/modpythonapi/BaseClient.py b/sfa/server/modpythonapi/BaseClient.py
new file mode 100755 (executable)
index 0000000..448f934
--- /dev/null
@@ -0,0 +1,46 @@
+import xmlrpclib
+
+from ApiExceptionCodes import *
+
+VerboseExceptions = False
+
+def EnableVerboseExceptions(x=True):
+    global VerboseExceptions
+    VerboseExceptions = x
+
+class ExceptionUnmarshaller(xmlrpclib.Unmarshaller):
+    def close(self):
+        try:\r
+            return xmlrpclib.Unmarshaller.close(self)\r
+        except xmlrpclib.Fault, e:\r
+            # if the server tagged some traceback info onto the end of the\r
+            # exception text, then print it out on the client.\r
+\r
+            if "\nFAULT_TRACEBACK:" in e.faultString:\r
+                parts = e.faultString.split("\nFAULT_TRACEBACK:")\r
+                e.faultString = parts[0]\r
+                if VerboseExceptions:\r
+                    print "\n|Server Traceback:", "\n|".join(parts[1].split("\n"))\r
+\r
+            raise e\r
+\r
+class ExceptionReportingTransport(xmlrpclib.Transport):
+    def make_connection(self, host):
+        import httplib\r
+        if host.startswith("https:"):\r
+           return httplib.HTTPS(host)\r
+        else:\r
+           return httplib.HTTP(host)\r
+\r
+    def getparser(self):\r
+        unmarshaller = ExceptionUnmarshaller()\r
+        parser = xmlrpclib.ExpatParser(unmarshaller)\r
+        return parser, unmarshaller
+
+class BaseClient():
+    def __init__(self, url):
+        self.url = url
+        self.server = xmlrpclib.ServerProxy(self.url, ExceptionReportingTransport())
+
+    def noop(self, value):
+        return self.server.noop(value)
diff --git a/sfa/server/modpythonapi/ModPython.py b/sfa/server/modpythonapi/ModPython.py
new file mode 100755 (executable)
index 0000000..64ceb99
--- /dev/null
@@ -0,0 +1,68 @@
+#
+# Apache mod_python interface
+#
+# Aaron Klingaman <alk@absarokasoft.com>
+# Mark Huang <mlhuang@cs.princeton.edu>
+#
+# Copyright (C) 2004-2006 The Trustees of Princeton University
+#
+
+import sys
+import traceback
+import xmlrpclib
+from mod_python import apache
+
+from API import RemoteApi
+api = RemoteApi()
+
+class unbuffered:
+    """\r
+    Write to /var/log/httpd/error_log. See\r
+\r
+    http://www.modpython.org/FAQ/faqw.py?req=edit&file=faq02.003.htp\r
+    """\r
+\r
+    def write(self, data):\r
+        sys.stderr.write(data)\r
+        sys.stderr.flush()\r
+\r
+#log = unbuffered()
+
+def handler(req):
+    try:
+        if req.method != "POST":
+            req.content_type = "text/html"
+            req.send_http_header()
+            req.write("""
+<html><head>
+<title>PLCAPI XML-RPC/SOAP Interface</title>
+</head><body>
+<h1>PLCAPI XML-RPC/SOAP Interface</h1>
+<p>Please use XML-RPC or SOAP to access the PLCAPI.</p>
+</body></html>
+""")
+            return apache.OK
+
+        # Read request
+        request = req.read(int(req.headers_in['content-length']))
+
+        # mod_python < 3.2: The IP address portion of remote_addr is
+        # incorrect (always 0.0.0.0) when IPv6 is enabled.
+        # http://issues.apache.org/jira/browse/MODPYTHON-64?page=all
+        (remote_ip, remote_port) = req.connection.remote_addr
+        remote_addr = (req.connection.remote_ip, remote_port)
+
+        # Handle request
+        response = api.handle(remote_addr, request)
+
+        # Write response
+        req.content_type = "text/xml; charset=" + api.encoding
+        req.send_http_header()
+        req.write(response)
+
+        return apache.OK
+
+    except Exception, err:
+        # Log error in /var/log/httpd/(ssl_)?error_log
+        print >> log, err, traceback.format_exc()
+        return apache.HTTP_INTERNAL_SERVER_ERROR
diff --git a/sfa/server/modpythonapi/TestApi.py b/sfa/server/modpythonapi/TestApi.py
new file mode 100755 (executable)
index 0000000..11daed5
--- /dev/null
@@ -0,0 +1,19 @@
+from AuthenticatedApi import AuthenticatedApi, BadRequestHash
+
+class RemoteApi(AuthenticatedApi):
+    def __init__(self, encoding="utf-8", trustedRootsDir="/usr/local/testapi/var/trusted_roots"):
+        return AuthenticatedApi.__init__(self, encoding)
+
+    def get_log_name(self):
+        return "/usr/local/testapi/var/logfile.txt"
+
+    def register_functions(self):
+        AuthenticatedApi.register_functions(self)
+        self.register_function(self.typeError)
+        self.register_function(self.badRequestHash)
+
+    def typeError(self):
+        raise TypeError()
+
+    def badRequestHash(self):
+        raise BadRequestHash("somehashvalue")
diff --git a/sfa/server/modpythonapi/installTest.sh b/sfa/server/modpythonapi/installTest.sh
new file mode 100755 (executable)
index 0000000..7ae489e
--- /dev/null
@@ -0,0 +1,37 @@
+GENI_SRC_DIR=/home/smbaker/projects/geniwrapper/trunk
+
+mkdir -p /usr/local/testapi/bin
+mkdir -p /usr/local/testapi/bin/sfa/trust
+mkdir -p /usr/local/testapi/bin/sfa/util
+mkdir -p /usr/local/testapi/var/trusted_roots
+mkdir -p /repository/testapi
+
+# source code for the API
+cp BaseApi.py /usr/local/testapi/bin/
+cp AuthenticatedApi.py /usr/local/testapi/bin/
+cp TestApi.py /usr/local/testapi/bin/API.py
+cp ModPython.py /usr/local/testapi/bin/
+cp ApiExceptionCodes.py /usr/local/testapi/bin/
+
+# trusted root certificates that match gackstestuser.*
+cp trusted_roots/*.gid /usr/local/testapi/var/trusted_roots/
+
+# apache config file to enable the api
+cp testapi.conf /etc/httpd/conf.d/
+
+# copy over geniwrapper stuff that we need
+echo > /usr/local/testapi/bin/sfa/__init__.py
+echo > /usr/local/testapi/bin/sfa/trust/__init__.py
+echo > /usr/local/testapi/bin/sfa/util/__init__.py
+cp $GENI_SRC_DIR/sfa/trust/gid.py /usr/local/testapi/bin/sfa/trust/
+cp $GENI_SRC_DIR/sfa/trust/certificate.py /usr/local/testapi/bin/sfa/trust/
+cp $GENI_SRC_DIR/sfa/trust/trustedroot.py /usr/local/testapi/bin/sfa/trust/
+cp $GENI_SRC_DIR/sfa/trust/credential.py /usr/local/testapi/bin/sfa/trust/
+cp $GENI_SRC_DIR/sfa/trust/rights.py /usr/local/testapi/bin/sfa/trust/
+cp $GENI_SRC_DIR/sfa/util/faults.py /usr/local/testapi/bin/sfa/util/ 
+
+# make everything owned by apache
+chown -R apache /usr/local/testapi
+chown apache /etc/httpd/conf.d/testapi.conf
+
+/etc/init.d/httpd restart
\ No newline at end of file
diff --git a/sfa/server/modpythonapi/test.py b/sfa/server/modpythonapi/test.py
new file mode 100755 (executable)
index 0000000..d3fafed
--- /dev/null
@@ -0,0 +1,44 @@
+import sys
+import traceback
+
+from BaseClient import BaseClient, EnableVerboseExceptions
+from AuthenticatedClient import AuthenticatedClient
+
+EnableVerboseExceptions(True)
+
+HOST = "localhost"
+URL = "http://" + HOST + "/TESTAPI/"
+SURL = "https://" + HOST + "/TESTAPI/"
+
+print "*** testing some valid ops; these should print \"Hello, World\" ***"
+
+bc = BaseClient(URL)
+print "HTTP noop:", bc.noop("Hello, World")
+
+ac = AuthenticatedClient(URL, "gackstestuser.pkey", "gackstestuser.gid")
+print "HTTP gidNoop:", ac.gidNoop("Hello, World")
+
+bc = BaseClient(SURL)
+print "HTTPS noop:", bc.noop("Hello, World")
+
+ac = AuthenticatedClient(URL, "gackstestuser.pkey", "gackstestuser.gid")
+print "HTTPS gidNoop:", ac.gidNoop("Hello, World")
+
+print
+print "*** testing some exception handling: ***"
+
+bc = BaseClient(URL)
+print "HTTP typeError:",
+try:
+    result = bc.server.typeError()
+    print result
+except Exception, e:
+    print ''.join(traceback.format_exception(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2]))
+
+print "HTTP badrequesthash:",
+try:
+    result = bc.server.badRequestHash()
+    print result
+except:
+    print ''.join(traceback.format_exception(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2]))
+
diff --git a/sfa/server/modpythonapi/test.sh b/sfa/server/modpythonapi/test.sh
new file mode 100755 (executable)
index 0000000..745c6a8
--- /dev/null
@@ -0,0 +1,3 @@
+export PYTHONPATH=/home/smbaker/projects/geniwrapper/trunk
+
+python ./test.py
diff --git a/sfa/server/modpythonapi/testapi.conf b/sfa/server/modpythonapi/testapi.conf
new file mode 100644 (file)
index 0000000..5495fd2
--- /dev/null
@@ -0,0 +1,5 @@
+<Location /TESTAPI/>
+    SetHandler mod_python
+    PythonPath "sys.path + ['/usr/local/testapi/bin/']"
+    PythonHandler ModPython
+</Location>
\ No newline at end of file