Merge branch 'master' into senslab2
authorSandrine Avakian <sandrine.avakian@inria.fr>
Thu, 30 Aug 2012 12:23:36 +0000 (14:23 +0200)
committerSandrine Avakian <sandrine.avakian@inria.fr>
Thu, 30 Aug 2012 12:23:36 +0000 (14:23 +0200)
Conflicts:
sfa/trust/credential.py
sfa/util/xrn.py

1  2 
setup.py
sfa/client/sfi.py
sfa/trust/credential.py
sfa/util/xrn.py

diff --cc setup.py
Simple merge
Simple merge
 -#----------------------------------------------------------------------\r
 -# Copyright (c) 2008 Board of Trustees, Princeton University\r
 -#\r
 -# Permission is hereby granted, free of charge, to any person obtaining\r
 -# a copy of this software and/or hardware specification (the "Work") to\r
 -# deal in the Work without restriction, including without limitation the\r
 -# rights to use, copy, modify, merge, publish, distribute, sublicense,\r
 -# and/or sell copies of the Work, and to permit persons to whom the Work\r
 -# is furnished to do so, subject to the following conditions:\r
 -#\r
 -# The above copyright notice and this permission notice shall be\r
 -# included in all copies or substantial portions of the Work.\r
 -#\r
 -# THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS \r
 -# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF \r
 -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND \r
 -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT \r
 -# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, \r
 -# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, \r
 -# OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS \r
 -# IN THE WORK.\r
 -#----------------------------------------------------------------------\r
 -##\r
 -# Implements SFA Credentials\r
 -#\r
 -# Credentials are signed XML files that assign a subject gid privileges to an object gid\r
 -##\r
 -\r
 -import os\r
 -from types import StringTypes\r
 -import datetime\r
 -from StringIO import StringIO\r
 -from tempfile import mkstemp\r
 -from xml.dom.minidom import Document, parseString\r
 -\r
 -HAVELXML = False\r
 -try:\r
 -    from lxml import etree\r
 -    HAVELXML = True\r
 -except:\r
 -    pass\r
 -\r
 -from xml.parsers.expat import ExpatError\r
 -\r
 -from sfa.util.faults import CredentialNotVerifiable, ChildRightsNotSubsetOfParent\r
 -from sfa.util.sfalogging import logger\r
 -from sfa.util.sfatime import utcparse\r
 -from sfa.trust.credential_legacy import CredentialLegacy\r
 -from sfa.trust.rights import Right, Rights, determine_rights\r
 -from sfa.trust.gid import GID\r
 -from sfa.util.xrn import urn_to_hrn, hrn_authfor_hrn\r
 -\r
 -# 2 weeks, in seconds \r
 -DEFAULT_CREDENTIAL_LIFETIME = 86400 * 31\r
 -\r
 -\r
 -# TODO:\r
 -# . make privs match between PG and PL\r
 -# . Need to add support for other types of credentials, e.g. tickets\r
 -# . add namespaces to signed-credential element?\r
 -\r
 -signature_template = \\r
 -'''\r
 -<Signature xml:id="Sig_%s" xmlns="http://www.w3.org/2000/09/xmldsig#">\r
 -  <SignedInfo>\r
 -    <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>\r
 -    <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>\r
 -    <Reference URI="#%s">\r
 -      <Transforms>\r
 -        <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />\r
 -      </Transforms>\r
 -      <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>\r
 -      <DigestValue></DigestValue>\r
 -    </Reference>\r
 -  </SignedInfo>\r
 -  <SignatureValue />\r
 -  <KeyInfo>\r
 -    <X509Data>\r
 -      <X509SubjectName/>\r
 -      <X509IssuerSerial/>\r
 -      <X509Certificate/>\r
 -    </X509Data>\r
 -    <KeyValue />\r
 -  </KeyInfo>\r
 -</Signature>\r
 -'''\r
 -\r
 -# PG formats the template (whitespace) slightly differently.\r
 -# Note that they don't include the xmlns in the template, but add it later.\r
 -# Otherwise the two are equivalent.\r
 -#signature_template_as_in_pg = \\r
 -#'''\r
 -#<Signature xml:id="Sig_%s" >\r
 -# <SignedInfo>\r
 -#  <CanonicalizationMethod      Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>\r
 -#  <SignatureMethod      Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>\r
 -#  <Reference URI="#%s">\r
 -#    <Transforms>\r
 -#      <Transform         Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />\r
 -#    </Transforms>\r
 -#    <DigestMethod        Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>\r
 -#    <DigestValue></DigestValue>\r
 -#    </Reference>\r
 -# </SignedInfo>\r
 -# <SignatureValue />\r
 -# <KeyInfo>\r
 -#  <X509Data >\r
 -#   <X509SubjectName/>\r
 -#   <X509IssuerSerial/>\r
 -#   <X509Certificate/>\r
 -#  </X509Data>\r
 -#  <KeyValue />\r
 -# </KeyInfo>\r
 -#</Signature>\r
 -#'''\r
 -\r
 -##\r
 -# Convert a string into a bool\r
 -# used to convert an xsd:boolean to a Python boolean\r
 -def str2bool(str):\r
 -    if str.lower() in ['true','1']:\r
 -        return True\r
 -    return False\r
 -\r
 -\r
 -##\r
 -# Utility function to get the text of an XML element\r
 -\r
 -def getTextNode(element, subele):\r
 -    sub = element.getElementsByTagName(subele)[0]\r
 -    if len(sub.childNodes) > 0:            \r
 -        return sub.childNodes[0].nodeValue\r
 -    else:\r
 -        return None\r
 -        \r
 -##\r
 -# Utility function to set the text of an XML element\r
 -# It creates the element, adds the text to it,\r
 -# and then appends it to the parent.\r
 -\r
 -def append_sub(doc, parent, element, text):\r
 -    ele = doc.createElement(element)\r
 -    ele.appendChild(doc.createTextNode(text))\r
 -    parent.appendChild(ele)\r
 -\r
 -##\r
 -# Signature contains information about an xmlsec1 signature\r
 -# for a signed-credential\r
 -#\r
 -\r
 -class Signature(object):\r
 -   \r
 -    def __init__(self, string=None):\r
 -        self.refid = None\r
 -        self.issuer_gid = None\r
 -        self.xml = None\r
 -        if string:\r
 -            self.xml = string\r
 -            self.decode()\r
 -\r
 -\r
 -    def get_refid(self):\r
 -        if not self.refid:\r
 -            self.decode()\r
 -        return self.refid\r
 -\r
 -    def get_xml(self):\r
 -        if not self.xml:\r
 -            self.encode()\r
 -        return self.xml\r
 -\r
 -    def set_refid(self, id):\r
 -        self.refid = id\r
 -\r
 -    def get_issuer_gid(self):\r
 -        if not self.gid:\r
 -            self.decode()\r
 -        return self.gid        \r
 -\r
 -    def set_issuer_gid(self, gid):\r
 -        self.gid = gid\r
 -\r
 -    def decode(self):\r
 -        try:\r
 -            doc = parseString(self.xml)\r
 -        except ExpatError,e:\r
 -            logger.log_exc ("Failed to parse credential, %s"%self.xml)\r
 -            raise\r
 -        sig = doc.getElementsByTagName("Signature")[0]\r
 -        self.set_refid(sig.getAttribute("xml:id").strip("Sig_"))\r
 -        keyinfo = sig.getElementsByTagName("X509Data")[0]\r
 -        szgid = getTextNode(keyinfo, "X509Certificate")\r
 -        szgid = "-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----" % szgid\r
 -        self.set_issuer_gid(GID(string=szgid))        \r
 -        \r
 -    def encode(self):\r
 -        self.xml = signature_template % (self.get_refid(), self.get_refid())\r
 -\r
 -\r
 -##\r
 -# A credential provides a caller gid with privileges to an object gid.\r
 -# A signed credential is signed by the object's authority.\r
 -#\r
 -# Credentials are encoded in one of two ways.  The legacy style places\r
 -# it in the subjectAltName of an X509 certificate.  The new credentials\r
 -# are placed in signed XML.\r
 -#\r
 -# WARNING:\r
 -# In general, a signed credential obtained externally should\r
 -# not be changed else the signature is no longer valid.  So, once\r
 -# you have loaded an existing signed credential, do not call encode() or sign() on it.\r
 -\r
 -def filter_creds_by_caller(creds, caller_hrn_list):\r
 -        """\r
 -        Returns a list of creds who's gid caller matches the\r
 -        specified caller hrn\r
 -        """\r
 -        if not isinstance(creds, list): creds = [creds]\r
 -        if not isinstance(caller_hrn_list, list): \r
 -            caller_hrn_list = [caller_hrn_list]\r
 -        caller_creds = []\r
 -        for cred in creds:\r
 -            try:\r
 -                tmp_cred = Credential(string=cred)\r
 -                if tmp_cred.get_gid_caller().get_hrn() in caller_hrn_list:\r
 -                    caller_creds.append(cred)\r
 -            except: pass\r
 -        return caller_creds\r
 -\r
 -class Credential(object):\r
 -\r
 -    ##\r
 -    # Create a Credential object\r
 -    #\r
 -    # @param create If true, create a blank x509 certificate\r
 -    # @param subject If subject!=None, create an x509 cert with the subject name\r
 -    # @param string If string!=None, load the credential from the string\r
 -    # @param filename If filename!=None, load the credential from the file\r
 -    # FIXME: create and subject are ignored!\r
 -    def __init__(self, create=False, subject=None, string=None, filename=None):\r
 -        self.gidCaller = None\r
 -        self.gidObject = None\r
 -        self.expiration = None\r
 -        self.privileges = None\r
 -        self.issuer_privkey = None\r
 -        self.issuer_gid = None\r
 -        self.issuer_pubkey = None\r
 -        self.parent = None\r
 -        self.signature = None\r
 -        self.xml = None\r
 -        self.refid = None\r
 -        self.legacy = None\r
 -\r
 -        # Check if this is a legacy credential, translate it if so\r
 -        if string or filename:\r
 -            if string:                \r
 -                str = string\r
 -            elif filename:\r
 -                str = file(filename).read()\r
 -                \r
 -            if str.strip().startswith("-----"):\r
 -                self.legacy = CredentialLegacy(False,string=str)\r
 -                self.translate_legacy(str)\r
 -            else:\r
 -                self.xml = str\r
 -                self.decode()\r
 -\r
 -        # Find an xmlsec1 path\r
 -        self.xmlsec_path = ''\r
 -        paths = ['/usr/bin','/usr/local/bin','/bin','/opt/bin','/opt/local/bin']\r
 -        for path in paths:\r
 -            if os.path.isfile(path + '/' + 'xmlsec1'):\r
 -                self.xmlsec_path = path + '/' + 'xmlsec1'\r
 -                break\r
 -\r
 -    def get_subject(self):\r
 -        if not self.gidObject:\r
 -            self.decode()\r
 -        return self.gidObject.get_printable_subject()\r
 -\r
 -    # sounds like this should be __repr__ instead ??\r
 -    def get_summary_tostring(self):\r
 -        if not self.gidObject:\r
 -            self.decode()\r
 -        obj = self.gidObject.get_printable_subject()\r
 -        caller = self.gidCaller.get_printable_subject()\r
 -        exp = self.get_expiration()\r
 -        # Summarize the rights too? The issuer?\r
 -        return "[ Grant %s rights on %s until %s ]" % (caller, obj, exp)\r
 -\r
 -    def get_signature(self):\r
 -        if not self.signature:\r
 -            self.decode()\r
 -        return self.signature\r
 -\r
 -    def set_signature(self, sig):\r
 -        self.signature = sig\r
 -\r
 -        \r
 -    ##\r
 -    # Translate a legacy credential into a new one\r
 -    #\r
 -    # @param String of the legacy credential\r
 -\r
 -    def translate_legacy(self, str):\r
 -        legacy = CredentialLegacy(False,string=str)\r
 -        self.gidCaller = legacy.get_gid_caller()\r
 -        self.gidObject = legacy.get_gid_object()\r
 -        lifetime = legacy.get_lifetime()\r
 -        if not lifetime:\r
 -            self.set_expiration(datetime.datetime.utcnow() + datetime.timedelta(seconds=DEFAULT_CREDENTIAL_LIFETIME))\r
 -        else:\r
 -            self.set_expiration(int(lifetime))\r
 -        self.lifeTime = legacy.get_lifetime()\r
 -        self.set_privileges(legacy.get_privileges())\r
 -        self.get_privileges().delegate_all_privileges(legacy.get_delegate())\r
 -\r
 -    ##\r
 -    # Need the issuer's private key and name\r
 -    # @param key Keypair object containing the private key of the issuer\r
 -    # @param gid GID of the issuing authority\r
 -\r
 -    def set_issuer_keys(self, privkey, gid):\r
 -        self.issuer_privkey = privkey\r
 -        self.issuer_gid = gid\r
 -\r
 -\r
 -    ##\r
 -    # Set this credential's parent\r
 -    def set_parent(self, cred):\r
 -        self.parent = cred\r
 -        self.updateRefID()\r
 -\r
 -    ##\r
 -    # set the GID of the caller\r
 -    #\r
 -    # @param gid GID object of the caller\r
 -\r
 -    def set_gid_caller(self, gid):\r
 -        self.gidCaller = gid\r
 -        # gid origin caller is the caller's gid by default\r
 -        self.gidOriginCaller = gid\r
 -\r
 -    ##\r
 -    # get the GID of the object\r
 -\r
 -    def get_gid_caller(self):\r
 -        if not self.gidCaller:\r
 -            self.decode()\r
 -        return self.gidCaller\r
 -\r
 -    ##\r
 -    # set the GID of the object\r
 -    #\r
 -    # @param gid GID object of the object\r
 -\r
 -    def set_gid_object(self, gid):\r
 -        self.gidObject = gid\r
 -\r
 -    ##\r
 -    # get the GID of the object\r
 -\r
 -    def get_gid_object(self):\r
 -        if not self.gidObject:\r
 -            self.decode()\r
 -        return self.gidObject\r
 -            \r
 -    ##\r
 -    # Expiration: an absolute UTC time of expiration (as either an int or string or datetime)\r
 -    # \r
 -    def set_expiration(self, expiration):\r
 -        if isinstance(expiration, (int, float)):\r
 -            self.expiration = datetime.datetime.fromtimestamp(expiration)\r
 -        elif isinstance (expiration, datetime.datetime):\r
 -            self.expiration = expiration\r
 -        elif isinstance (expiration, StringTypes):\r
 -            self.expiration = utcparse (expiration)\r
 -        else:\r
 -            logger.error ("unexpected input type in Credential.set_expiration")\r
 -\r
 -\r
 -    ##\r
 -    # get the lifetime of the credential (always in datetime format)\r
 -\r
 -    def get_expiration(self):\r
 -        if not self.expiration:\r
 -            self.decode()\r
 -        # at this point self.expiration is normalized as a datetime - DON'T call utcparse again\r
 -        return self.expiration\r
 -\r
 -    ##\r
 -    # For legacy sake\r
 -    def get_lifetime(self):\r
 -        return self.get_expiration()\r
 - \r
 -    ##\r
 -    # set the privileges\r
 -    #\r
 -    # @param privs either a comma-separated list of privileges of a Rights object\r
 -\r
 -    def set_privileges(self, privs):\r
 -        if isinstance(privs, str):\r
 -            self.privileges = Rights(string = privs)\r
 -        else:\r
 -            self.privileges = privs        \r
 -\r
 -    ##\r
 -    # return the privileges as a Rights object\r
 -\r
 -    def get_privileges(self):\r
 -        if not self.privileges:\r
 -            self.decode()\r
 -        return self.privileges\r
 -\r
 -    ##\r
 -    # determine whether the credential allows a particular operation to be\r
 -    # performed\r
 -    #\r
 -    # @param op_name string specifying name of operation ("lookup", "update", etc)\r
 -\r
 -    def can_perform(self, op_name):\r
 -        rights = self.get_privileges()\r
 -        \r
 -        if not rights:\r
 -            return False\r
 -\r
 -        return rights.can_perform(op_name)\r
 -\r
 -\r
 -    ##\r
 -    # Encode the attributes of the credential into an XML string    \r
 -    # This should be done immediately before signing the credential.    \r
 -    # WARNING:\r
 -    # In general, a signed credential obtained externally should\r
 -    # not be changed else the signature is no longer valid.  So, once\r
 -    # you have loaded an existing signed credential, do not call encode() or sign() on it.\r
 -\r
 -    def encode(self):\r
 -        # Create the XML document\r
 -        doc = Document()\r
 -        signed_cred = doc.createElement("signed-credential")\r
 -\r
 -# Declare namespaces\r
 -# Note that credential/policy.xsd are really the PG schemas\r
 -# in a PL namespace.\r
 -# Note that delegation of credentials between the 2 only really works\r
 -# cause those schemas are identical.\r
 -# Also note these PG schemas talk about PG tickets and CM policies.\r
 -        signed_cred.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")\r
 -        signed_cred.setAttribute("xsi:noNamespaceSchemaLocation", "http://www.planet-lab.org/resources/sfa/credential.xsd")\r
 -        signed_cred.setAttribute("xsi:schemaLocation", "http://www.planet-lab.org/resources/sfa/ext/policy/1 http://www.planet-lab.org/resources/sfa/ext/policy/1/policy.xsd")\r
 -\r
 -# PG says for those last 2:\r
 -#        signed_cred.setAttribute("xsi:noNamespaceSchemaLocation", "http://www.protogeni.net/resources/credential/credential.xsd")\r
 -#        signed_cred.setAttribute("xsi:schemaLocation", "http://www.protogeni.net/resources/credential/ext/policy/1 http://www.protogeni.net/resources/credential/ext/policy/1/policy.xsd")\r
 -\r
 -        doc.appendChild(signed_cred)  \r
 -        \r
 -        # Fill in the <credential> bit        \r
 -        cred = doc.createElement("credential")\r
 -        cred.setAttribute("xml:id", self.get_refid())\r
 -        signed_cred.appendChild(cred)\r
 -        append_sub(doc, cred, "type", "privilege")\r
 -        append_sub(doc, cred, "serial", "8")\r
 -        append_sub(doc, cred, "owner_gid", self.gidCaller.save_to_string())\r
 -        append_sub(doc, cred, "owner_urn", self.gidCaller.get_urn())\r
 -        append_sub(doc, cred, "target_gid", self.gidObject.save_to_string())\r
 -        append_sub(doc, cred, "target_urn", self.gidObject.get_urn())\r
 -        append_sub(doc, cred, "uuid", "")\r
 -        if not self.expiration:\r
 -            self.set_expiration(datetime.datetime.utcnow() + datetime.timedelta(seconds=DEFAULT_CREDENTIAL_LIFETIME))\r
 -        self.expiration = self.expiration.replace(microsecond=0)\r
 -        append_sub(doc, cred, "expires", self.expiration.isoformat())\r
 -        privileges = doc.createElement("privileges")\r
 -        cred.appendChild(privileges)\r
 -\r
 -        if self.privileges:\r
 -            rights = self.get_privileges()\r
 -            for right in rights.rights:\r
 -                priv = doc.createElement("privilege")\r
 -                append_sub(doc, priv, "name", right.kind)\r
 -                append_sub(doc, priv, "can_delegate", str(right.delegate).lower())\r
 -                privileges.appendChild(priv)\r
 -\r
 -        # Add the parent credential if it exists\r
 -        if self.parent:\r
 -            sdoc = parseString(self.parent.get_xml())\r
 -            # If the root node is a signed-credential (it should be), then\r
 -            # get all its attributes and attach those to our signed_cred\r
 -            # node.\r
 -            # Specifically, PG and PLadd attributes for namespaces (which is reasonable),\r
 -            # and we need to include those again here or else their signature\r
 -            # no longer matches on the credential.\r
 -            # We expect three of these, but here we copy them all:\r
 -#        signed_cred.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")\r
 -# and from PG (PL is equivalent, as shown above):\r
 -#        signed_cred.setAttribute("xsi:noNamespaceSchemaLocation", "http://www.protogeni.net/resources/credential/credential.xsd")\r
 -#        signed_cred.setAttribute("xsi:schemaLocation", "http://www.protogeni.net/resources/credential/ext/policy/1 http://www.protogeni.net/resources/credential/ext/policy/1/policy.xsd")\r
 -\r
 -            # HOWEVER!\r
 -            # PL now also declares these, with different URLs, so\r
 -            # the code notices those attributes already existed with\r
 -            # different values, and complains.\r
 -            # This happens regularly on delegation now that PG and\r
 -            # PL both declare the namespace with different URLs.\r
 -            # If the content ever differs this is a problem,\r
 -            # but for now it works - different URLs (values in the attributes)\r
 -            # but the same actual schema, so using the PG schema\r
 -            # on delegated-to-PL credentials works fine.\r
 -\r
 -            # Note: you could also not copy attributes\r
 -            # which already exist. It appears that both PG and PL\r
 -            # will actually validate a slicecred with a parent\r
 -            # signed using PG namespaces and a child signed with PL\r
 -            # namespaces over the whole thing. But I don't know\r
 -            # if that is a bug in xmlsec1, an accident since\r
 -            # the contents of the schemas are the same,\r
 -            # or something else, but it seems odd. And this works.\r
 -            parentRoot = sdoc.documentElement\r
 -            if parentRoot.tagName == "signed-credential" and parentRoot.hasAttributes():\r
 -                for attrIx in range(0, parentRoot.attributes.length):\r
 -                    attr = parentRoot.attributes.item(attrIx)\r
 -                    # returns the old attribute of same name that was\r
 -                    # on the credential\r
 -                    # Below throws InUse exception if we forgot to clone the attribute first\r
 -                    oldAttr = signed_cred.setAttributeNode(attr.cloneNode(True))\r
 -                    if oldAttr and oldAttr.value != attr.value:\r
 -                        msg = "Delegating cred from owner %s to %s over %s replaced attribute %s value '%s' with '%s'" % (self.parent.gidCaller.get_urn(), self.gidCaller.get_urn(), self.gidObject.get_urn(), oldAttr.name, oldAttr.value, attr.value)\r
 -                        logger.warn(msg)\r
 -                        #raise CredentialNotVerifiable("Can't encode new valid delegated credential: %s" % msg)\r
 -\r
 -            p_cred = doc.importNode(sdoc.getElementsByTagName("credential")[0], True)\r
 -            p = doc.createElement("parent")\r
 -            p.appendChild(p_cred)\r
 -            cred.appendChild(p)\r
 -        # done handling parent credential\r
 -\r
 -        # Create the <signatures> tag\r
 -        signatures = doc.createElement("signatures")\r
 -        signed_cred.appendChild(signatures)\r
 -\r
 -        # Add any parent signatures\r
 -        if self.parent:\r
 -            for cur_cred in self.get_credential_list()[1:]:\r
 -                sdoc = parseString(cur_cred.get_signature().get_xml())\r
 -                ele = doc.importNode(sdoc.getElementsByTagName("Signature")[0], True)\r
 -                signatures.appendChild(ele)\r
 -                \r
 -        # Get the finished product\r
 -        self.xml = doc.toxml()\r
 -\r
 -\r
 -    def save_to_random_tmp_file(self):       \r
 -        fp, filename = mkstemp(suffix='cred', text=True)\r
 -        fp = os.fdopen(fp, "w")\r
 -        self.save_to_file(filename, save_parents=True, filep=fp)\r
 -        return filename\r
 -    \r
 -    def save_to_file(self, filename, save_parents=True, filep=None):\r
 -        if not self.xml:\r
 -            self.encode()\r
 -        if filep:\r
 -            f = filep \r
 -        else:\r
 -            f = open(filename, "w")\r
 -        f.write(self.xml)\r
 -        f.close()\r
 -\r
 -    def save_to_string(self, save_parents=True):\r
 -        if not self.xml:\r
 -            self.encode()\r
 -        return self.xml\r
 -\r
 -    def get_refid(self):\r
 -        if not self.refid:\r
 -            self.refid = 'ref0'\r
 -        return self.refid\r
 -\r
 -    def set_refid(self, rid):\r
 -        self.refid = rid\r
 -\r
 -    ##\r
 -    # Figure out what refids exist, and update this credential's id\r
 -    # so that it doesn't clobber the others.  Returns the refids of\r
 -    # the parents.\r
 -    \r
 -    def updateRefID(self):\r
 -        if not self.parent:\r
 -            self.set_refid('ref0')\r
 -            return []\r
 -        \r
 -        refs = []\r
 -\r
 -        next_cred = self.parent\r
 -        while next_cred:\r
 -            refs.append(next_cred.get_refid())\r
 -            if next_cred.parent:\r
 -                next_cred = next_cred.parent\r
 -            else:\r
 -                next_cred = None\r
 -\r
 -        \r
 -        # Find a unique refid for this credential\r
 -        rid = self.get_refid()\r
 -        while rid in refs:\r
 -            val = int(rid[3:])\r
 -            rid = "ref%d" % (val + 1)\r
 -\r
 -        # Set the new refid\r
 -        self.set_refid(rid)\r
 -\r
 -        # Return the set of parent credential ref ids\r
 -        return refs\r
 -\r
 -    def get_xml(self):\r
 -        if not self.xml:\r
 -            self.encode()\r
 -        return self.xml\r
 -\r
 -    ##\r
 -    # Sign the XML file created by encode()\r
 -    #\r
 -    # WARNING:\r
 -    # In general, a signed credential obtained externally should\r
 -    # not be changed else the signature is no longer valid.  So, once\r
 -    # you have loaded an existing signed credential, do not call encode() or sign() on it.\r
 -\r
 -    def sign(self):\r
 -        if not self.issuer_privkey or not self.issuer_gid:\r
 -            return\r
 -        doc = parseString(self.get_xml())\r
 -        sigs = doc.getElementsByTagName("signatures")[0]\r
 -\r
 -        # Create the signature template to be signed\r
 -        signature = Signature()\r
 -        signature.set_refid(self.get_refid())\r
 -        sdoc = parseString(signature.get_xml())        \r
 -        sig_ele = doc.importNode(sdoc.getElementsByTagName("Signature")[0], True)\r
 -        sigs.appendChild(sig_ele)\r
 -\r
 -        self.xml = doc.toxml()\r
 -\r
 -\r
 -        # Split the issuer GID into multiple certificates if it's a chain\r
 -        chain = GID(filename=self.issuer_gid)\r
 -        gid_files = []\r
 -        while chain:\r
 -            gid_files.append(chain.save_to_random_tmp_file(False))\r
 -            if chain.get_parent():\r
 -                chain = chain.get_parent()\r
 -            else:\r
 -                chain = None\r
 -\r
 -\r
 -        # Call out to xmlsec1 to sign it\r
 -        ref = 'Sig_%s' % self.get_refid()\r
 -        filename = self.save_to_random_tmp_file()\r
 -        signed = os.popen('%s --sign --node-id "%s" --privkey-pem %s,%s %s' \\r
 -                 % (self.xmlsec_path, ref, self.issuer_privkey, ",".join(gid_files), filename)).read()\r
 -        os.remove(filename)\r
 -\r
 -        for gid_file in gid_files:\r
 -            os.remove(gid_file)\r
 -\r
 -        self.xml = signed\r
 -\r
 -        # This is no longer a legacy credential\r
 -        if self.legacy:\r
 -            self.legacy = None\r
 -\r
 -        # Update signatures\r
 -        self.decode()       \r
 -\r
 -        \r
 -    ##\r
 -    # Retrieve the attributes of the credential from the XML.\r
 -    # This is automatically called by the various get_* methods of\r
 -    # this class and should not need to be called explicitly.\r
 -\r
 -    def decode(self):\r
 -        if not self.xml:\r
 -            return\r
 -        doc = parseString(self.xml)\r
 -        sigs = []\r
 -        signed_cred = doc.getElementsByTagName("signed-credential")\r
 -\r
 -        # Is this a signed-cred or just a cred?\r
 -        if len(signed_cred) > 0:\r
 -            creds = signed_cred[0].getElementsByTagName("credential")\r
 -            signatures = signed_cred[0].getElementsByTagName("signatures")\r
 -            if len(signatures) > 0:\r
 -                sigs = signatures[0].getElementsByTagName("Signature")\r
 -        else:\r
 -            creds = doc.getElementsByTagName("credential")\r
 -        \r
 -        if creds is None or len(creds) == 0:\r
 -            # malformed cred file\r
 -            raise CredentialNotVerifiable("Malformed XML: No credential tag found")\r
 -\r
 -        # Just take the first cred if there are more than one\r
 -        cred = creds[0]\r
 -\r
 -        self.set_refid(cred.getAttribute("xml:id"))\r
 -        self.set_expiration(utcparse(getTextNode(cred, "expires")))\r
 -        self.gidCaller = GID(string=getTextNode(cred, "owner_gid"))\r
 -        self.gidObject = GID(string=getTextNode(cred, "target_gid"))   \r
 -\r
 -\r
 -        # Process privileges\r
 -        privs = cred.getElementsByTagName("privileges")[0]\r
 -        rlist = Rights()\r
 -        for priv in privs.getElementsByTagName("privilege"):\r
 -            kind = getTextNode(priv, "name")\r
 -            deleg = str2bool(getTextNode(priv, "can_delegate"))\r
 -            if kind == '*':\r
 -                # Convert * into the default privileges for the credential's type\r
 -                # Each inherits the delegatability from the * above\r
 -                _ , type = urn_to_hrn(self.gidObject.get_urn())\r
 -                rl = determine_rights(type, self.gidObject.get_urn())\r
 -                for r in rl.rights:\r
 -                    r.delegate = deleg\r
 -                    rlist.add(r)\r
 -            else:\r
 -                rlist.add(Right(kind.strip(), deleg))\r
 -        self.set_privileges(rlist)\r
 -\r
 -\r
 -        # Is there a parent?\r
 -        parent = cred.getElementsByTagName("parent")\r
 -        if len(parent) > 0:\r
 -            parent_doc = parent[0].getElementsByTagName("credential")[0]\r
 -            parent_xml = parent_doc.toxml()\r
 -            self.parent = Credential(string=parent_xml)\r
 -            self.updateRefID()\r
 -\r
 -        # Assign the signatures to the credentials\r
 -        for sig in sigs:\r
 -            Sig = Signature(string=sig.toxml())\r
 -\r
 -            for cur_cred in self.get_credential_list():\r
 -                if cur_cred.get_refid() == Sig.get_refid():\r
 -                    cur_cred.set_signature(Sig)\r
 -                                    \r
 -            \r
 -    ##\r
 -    # Verify\r
 -    #   trusted_certs: A list of trusted GID filenames (not GID objects!) \r
 -    #                  Chaining is not supported within the GIDs by xmlsec1.\r
 -    #\r
 -    #   trusted_certs_required: Should usually be true. Set False means an\r
 -    #                 empty list of trusted_certs would still let this method pass.\r
 -    #                 It just skips xmlsec1 verification et al. Only used by some utils\r
 -    #    \r
 -    # Verify that:\r
 -    # . All of the signatures are valid and that the issuers trace back\r
 -    #   to trusted roots (performed by xmlsec1)\r
 -    # . The XML matches the credential schema\r
 -    # . That the issuer of the credential is the authority in the target's urn\r
 -    #    . In the case of a delegated credential, this must be true of the root\r
 -    # . That all of the gids presented in the credential are valid\r
 -    #    . Including verifying GID chains, and includ the issuer\r
 -    # . The credential is not expired\r
 -    #\r
 -    # -- For Delegates (credentials with parents)\r
 -    # . The privileges must be a subset of the parent credentials\r
 -    # . The privileges must have "can_delegate" set for each delegated privilege\r
 -    # . The target gid must be the same between child and parents\r
 -    # . The expiry time on the child must be no later than the parent\r
 -    # . The signer of the child must be the owner of the parent\r
 -    #\r
 -    # -- Verify does *NOT*\r
 -    # . ensure that an xmlrpc client's gid matches a credential gid, that\r
 -    #   must be done elsewhere\r
 -    #\r
 -    # @param trusted_certs: The certificates of trusted CA certificates\r
 -    def verify(self, trusted_certs=None, schema=None, trusted_certs_required=True):\r
 -        if not self.xml:\r
 -            self.decode()\r
 -\r
 -        # validate against RelaxNG schema\r
 -        if HAVELXML and not self.legacy:\r
 -            if schema and os.path.exists(schema):\r
 -                tree = etree.parse(StringIO(self.xml))\r
 -                schema_doc = etree.parse(schema)\r
 -                xmlschema = etree.XMLSchema(schema_doc)\r
 -                if not xmlschema.validate(tree):\r
 -                    error = xmlschema.error_log.last_error\r
 -                    message = "%s: %s (line %s)" % (self.get_summary_tostring(), error.message, error.line)\r
 -                    raise CredentialNotVerifiable(message)\r
 -\r
 -        if trusted_certs_required and trusted_certs is None:\r
 -            trusted_certs = []\r
 -\r
 -#        trusted_cert_objects = [GID(filename=f) for f in trusted_certs]\r
 -        trusted_cert_objects = []\r
 -        ok_trusted_certs = []\r
 -        # If caller explicitly passed in None that means skip cert chain validation.\r
 -        # Strange and not typical\r
 -        if trusted_certs is not None:\r
 -            for f in trusted_certs:\r
 -                try:\r
 -                    # Failures here include unreadable files\r
 -                    # or non PEM files\r
 -                    trusted_cert_objects.append(GID(filename=f))\r
 -                    ok_trusted_certs.append(f)\r
 -                except Exception, exc:\r
 -                    logger.error("Failed to load trusted cert from %s: %r", f, exc)\r
 -            trusted_certs = ok_trusted_certs\r
 -\r
 -        # Use legacy verification if this is a legacy credential\r
 -        if self.legacy:\r
 -            self.legacy.verify_chain(trusted_cert_objects)\r
 -            if self.legacy.client_gid:\r
 -                self.legacy.client_gid.verify_chain(trusted_cert_objects)\r
 -            if self.legacy.object_gid:\r
 -                self.legacy.object_gid.verify_chain(trusted_cert_objects)\r
 -            return True\r
 -        \r
 -        # make sure it is not expired\r
 -        if self.get_expiration() < datetime.datetime.utcnow():\r
 -            raise CredentialNotVerifiable("Credential %s expired at %s" % (self.get_summary_tostring(), self.expiration.isoformat()))\r
 -\r
 -        # Verify the signatures\r
 -        filename = self.save_to_random_tmp_file()\r
 -        if trusted_certs is not None:\r
 -            cert_args = " ".join(['--trusted-pem %s' % x for x in trusted_certs])\r
 -\r
 -        # If caller explicitly passed in None that means skip cert chain validation.\r
 -        # - Strange and not typical\r
 -        if trusted_certs is not None:\r
 -            # Verify the gids of this cred and of its parents\r
 -            for cur_cred in self.get_credential_list():\r
 -                cur_cred.get_gid_object().verify_chain(trusted_cert_objects)\r
 -                cur_cred.get_gid_caller().verify_chain(trusted_cert_objects)\r
 -\r
 -        refs = []\r
 -        refs.append("Sig_%s" % self.get_refid())\r
 -\r
 -        parentRefs = self.updateRefID()\r
 -        for ref in parentRefs:\r
 -            refs.append("Sig_%s" % ref)\r
 -\r
 -        for ref in refs:\r
 -            # If caller explicitly passed in None that means skip xmlsec1 validation.\r
 -            # Strange and not typical\r
 -            if trusted_certs is None:\r
 -                break\r
 -\r
 -#            print "Doing %s --verify --node-id '%s' %s %s 2>&1" % \\r
 -#                (self.xmlsec_path, ref, cert_args, filename)\r
 -            verified = os.popen('%s --verify --node-id "%s" %s %s 2>&1' \\r
 -                            % (self.xmlsec_path, ref, cert_args, filename)).read()\r
 -            if not verified.strip().startswith("OK"):\r
 -                # xmlsec errors have a msg= which is the interesting bit.\r
 -                mstart = verified.find("msg=")\r
 -                msg = ""\r
 -                if mstart > -1 and len(verified) > 4:\r
 -                    mstart = mstart + 4\r
 -                    mend = verified.find('\\', mstart)\r
 -                    msg = verified[mstart:mend]\r
 -                raise CredentialNotVerifiable("xmlsec1 error verifying cred %s using Signature ID %s: %s %s" % (self.get_summary_tostring(), ref, msg, verified.strip()))\r
 -        os.remove(filename)\r
 -\r
 -        # Verify the parents (delegation)\r
 -        if self.parent:\r
 -            self.verify_parent(self.parent)\r
 -\r
 -        # Make sure the issuer is the target's authority, and is\r
 -        # itself a valid GID\r
 -        self.verify_issuer(trusted_cert_objects)\r
 -        return True\r
 -\r
 -    ##\r
 -    # Creates a list of the credential and its parents, with the root \r
 -    # (original delegated credential) as the last item in the list\r
 -    def get_credential_list(self):    \r
 -        cur_cred = self\r
 -        list = []\r
 -        while cur_cred:\r
 -            list.append(cur_cred)\r
 -            if cur_cred.parent:\r
 -                cur_cred = cur_cred.parent\r
 -            else:\r
 -                cur_cred = None\r
 -        return list\r
 -    \r
 -    ##\r
 -    # Make sure the credential's target gid (a) was signed by or (b)\r
 -    # is the same as the entity that signed the original credential,\r
 -    # or (c) is an authority over the target's namespace.\r
 -    # Also ensure that the credential issuer / signer itself has a valid\r
 -    # GID signature chain (signed by an authority with namespace rights).\r
 -    def verify_issuer(self, trusted_gids):\r
 -        root_cred = self.get_credential_list()[-1]\r
 -        root_target_gid = root_cred.get_gid_object()\r
 -        root_cred_signer = root_cred.get_signature().get_issuer_gid()\r
 -\r
 -        # Case 1:\r
 -        # Allow non authority to sign target and cred about target.\r
 -        #\r
 -        # Why do we need to allow non authorities to sign?\r
 -        # If in the target gid validation step we correctly\r
 -        # checked that the target is only signed by an authority,\r
 -        # then this is just a special case of case 3.\r
 -        # This short-circuit is the common case currently -\r
 -        # and cause GID validation doesn't check 'authority',\r
 -        # this allows users to generate valid slice credentials.\r
 -        if root_target_gid.is_signed_by_cert(root_cred_signer):\r
 -            # cred signer matches target signer, return success\r
 -            return\r
 -\r
 -        # Case 2:\r
 -        # Allow someone to sign credential about themeselves. Used?\r
 -        # If not, remove this.\r
 -        #root_target_gid_str = root_target_gid.save_to_string()\r
 -        #root_cred_signer_str = root_cred_signer.save_to_string()\r
 -        #if root_target_gid_str == root_cred_signer_str:\r
 -        #    # cred signer is target, return success\r
 -        #    return\r
 -\r
 -        # Case 3:\r
 -\r
 -        # root_cred_signer is not the target_gid\r
 -        # So this is a different gid that we have not verified.\r
 -        # xmlsec1 verified the cert chain on this already, but\r
 -        # it hasn't verified that the gid meets the HRN namespace\r
 -        # requirements.\r
 -        # Below we'll ensure that it is an authority.\r
 -        # But we haven't verified that it is _signed by_ an authority\r
 -        # We also don't know if xmlsec1 requires that cert signers\r
 -        # are marked as CAs.\r
 -\r
 -        # Note that if verify() gave us no trusted_gids then this\r
 -        # call will fail. So skip it if we have no trusted_gids\r
 -        if trusted_gids and len(trusted_gids) > 0:\r
 -            root_cred_signer.verify_chain(trusted_gids)\r
 -        else:\r
 -            logger.debug("No trusted gids. Cannot verify that cred signer is signed by a trusted authority. Skipping that check.")\r
 -\r
 -        # See if the signer is an authority over the domain of the target.\r
 -        # There are multiple types of authority - accept them all here\r
 -        # Maybe should be (hrn, type) = urn_to_hrn(root_cred_signer.get_urn())\r
 -        root_cred_signer_type = root_cred_signer.get_type()\r
 -        if (root_cred_signer_type.find('authority') == 0):\r
 -            #logger.debug('Cred signer is an authority')\r
 -            # signer is an authority, see if target is in authority's domain\r
 -            signerhrn = root_cred_signer.get_hrn()\r
 -            if hrn_authfor_hrn(signerhrn, root_target_gid.get_hrn()):\r
 -                return\r
 -\r
 -        # We've required that the credential be signed by an authority\r
 -        # for that domain. Reasonable and probably correct.\r
 -        # A looser model would also allow the signer to be an authority\r
 -        # in my control framework - eg My CA or CH. Even if it is not\r
 -        # the CH that issued these, eg, user credentials.\r
 -\r
 -        # Give up, credential does not pass issuer verification\r
 -\r
 -        raise CredentialNotVerifiable("Could not verify credential owned by %s for object %s. Cred signer %s not the trusted authority for Cred target %s" % (self.gidCaller.get_urn(), self.gidObject.get_urn(), root_cred_signer.get_hrn(), root_target_gid.get_hrn()))\r
 -\r
 -\r
 -    ##\r
 -    # -- For Delegates (credentials with parents) verify that:\r
 -    # . The privileges must be a subset of the parent credentials\r
 -    # . The privileges must have "can_delegate" set for each delegated privilege\r
 -    # . The target gid must be the same between child and parents\r
 -    # . The expiry time on the child must be no later than the parent\r
 -    # . The signer of the child must be the owner of the parent        \r
 -    def verify_parent(self, parent_cred):\r
 -        # make sure the rights given to the child are a subset of the\r
 -        # parents rights (and check delegate bits)\r
 -        if not parent_cred.get_privileges().is_superset(self.get_privileges()):\r
 -            raise ChildRightsNotSubsetOfParent(("Parent cred ref %s rights " % parent_cred.get_refid()) +\r
 -                self.parent.get_privileges().save_to_string() + (" not superset of delegated cred %s ref %s rights " % (self.get_summary_tostring(), self.get_refid())) +\r
 -                self.get_privileges().save_to_string())\r
 -\r
 -        # make sure my target gid is the same as the parent's\r
 -        if not parent_cred.get_gid_object().save_to_string() == \\r
 -           self.get_gid_object().save_to_string():\r
 -            raise CredentialNotVerifiable("Delegated cred %s: Target gid not equal between parent and child. Parent %s" % (self.get_summary_tostring(), parent_cred.get_summary_tostring()))\r
 -\r
 -        # make sure my expiry time is <= my parent's\r
 -        if not parent_cred.get_expiration() >= self.get_expiration():\r
 -            raise CredentialNotVerifiable("Delegated credential %s expires after parent %s" % (self.get_summary_tostring(), parent_cred.get_summary_tostring()))\r
 -\r
 -        # make sure my signer is the parent's caller\r
 -        if not parent_cred.get_gid_caller().save_to_string(False) == \\r
 -           self.get_signature().get_issuer_gid().save_to_string(False):\r
 -            raise CredentialNotVerifiable("Delegated credential %s not signed by parent %s's caller" % (self.get_summary_tostring(), parent_cred.get_summary_tostring()))\r
 -                \r
 -        # Recurse\r
 -        if parent_cred.parent:\r
 -            parent_cred.verify_parent(parent_cred.parent)\r
 -\r
 -\r
 -    def delegate(self, delegee_gidfile, caller_keyfile, caller_gidfile):\r
 -        """\r
 -        Return a delegated copy of this credential, delegated to the \r
 -        specified gid's user.    \r
 -        """\r
 -        # get the gid of the object we are delegating\r
 -        object_gid = self.get_gid_object()\r
 -        object_hrn = object_gid.get_hrn()        \r
 - \r
 -        # the hrn of the user who will be delegated to\r
 -        delegee_gid = GID(filename=delegee_gidfile)\r
 -        delegee_hrn = delegee_gid.get_hrn()\r
 -  \r
 -        #user_key = Keypair(filename=keyfile)\r
 -        #user_hrn = self.get_gid_caller().get_hrn()\r
 -        subject_string = "%s delegated to %s" % (object_hrn, delegee_hrn)\r
 -        dcred = Credential(subject=subject_string)\r
 -        dcred.set_gid_caller(delegee_gid)\r
 -        dcred.set_gid_object(object_gid)\r
 -        dcred.set_parent(self)\r
 -        dcred.set_expiration(self.get_expiration())\r
 -        dcred.set_privileges(self.get_privileges())\r
 -        dcred.get_privileges().delegate_all_privileges(True)\r
 -        #dcred.set_issuer_keys(keyfile, delegee_gidfile)\r
 -        dcred.set_issuer_keys(caller_keyfile, caller_gidfile)\r
 -        dcred.encode()\r
 -        dcred.sign()\r
 -\r
 -        return dcred\r
 -\r
 -    # only informative\r
 -    def get_filename(self):\r
 -        return getattr(self,'filename',None)\r
 -\r
 -    ##\r
 -    # Dump the contents of a credential to stdout in human-readable format\r
 -    #\r
 -    # @param dump_parents If true, also dump the parent certificates\r
 -    def dump (self, *args, **kwargs):\r
 -        print self.dump_string(*args, **kwargs)\r
 -\r
 -\r
 -    def dump_string(self, dump_parents=False):\r
 -        result=""\r
 -        result += "CREDENTIAL %s\n" % self.get_subject()\r
 -        filename=self.get_filename()\r
 -        if filename: result += "Filename %s\n"%filename\r
 -        result += "      privs: %s\n" % self.get_privileges().save_to_string()\r
 -        gidCaller = self.get_gid_caller()\r
 -        if gidCaller:\r
 -            result += "  gidCaller:\n"\r
 -            result += gidCaller.dump_string(8, dump_parents)\r
 -\r
 -        if self.get_signature():\r
 -            print "  gidIssuer:"\r
 -            self.get_signature().get_issuer_gid().dump(8, dump_parents)\r
 -\r
 -        gidObject = self.get_gid_object()\r
 -        if gidObject:\r
 -            result += "  gidObject:\n"\r
 -            result += gidObject.dump_string(8, dump_parents)\r
 -\r
 -        if self.parent and dump_parents:\r
 -            result += "\nPARENT"\r
 -            result += self.parent.dump_string(True)\r
 -\r
 -        return result\r
 +#----------------------------------------------------------------------
 +# Copyright (c) 2008 Board of Trustees, Princeton University
 +#
 +# Permission is hereby granted, free of charge, to any person obtaining
 +# a copy of this software and/or hardware specification (the "Work") to
 +# deal in the Work without restriction, including without limitation the
 +# rights to use, copy, modify, merge, publish, distribute, sublicense,
 +# and/or sell copies of the Work, and to permit persons to whom the Work
 +# is furnished to do so, subject to the following conditions:
 +#
 +# The above copyright notice and this permission notice shall be
 +# included in all copies or substantial portions of the Work.
 +#
 +# THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
 +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
 +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
 +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
 +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 +# OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS 
 +# IN THE WORK.
 +#----------------------------------------------------------------------
 +##
 +# Implements SFA Credentials
 +#
 +# Credentials are signed XML files that assign a subject gid privileges to an object gid
 +##
 +
- import os,sys
++import os
 +from types import StringTypes
 +import datetime
 +from StringIO import StringIO
 +from tempfile import mkstemp
 +from xml.dom.minidom import Document, parseString
 +
 +HAVELXML = False
 +try:
 +    from lxml import etree
 +    HAVELXML = True
 +except:
 +    pass
 +
 +from xml.parsers.expat import ExpatError
 +
 +from sfa.util.faults import CredentialNotVerifiable, ChildRightsNotSubsetOfParent
 +from sfa.util.sfalogging import logger
 +from sfa.util.sfatime import utcparse
 +from sfa.trust.credential_legacy import CredentialLegacy
 +from sfa.trust.rights import Right, Rights, determine_rights
 +from sfa.trust.gid import GID
 +from sfa.util.xrn import urn_to_hrn, hrn_authfor_hrn
 +
 +# 2 weeks, in seconds 
 +DEFAULT_CREDENTIAL_LIFETIME = 86400 * 31
 +
 +
 +# TODO:
 +# . make privs match between PG and PL
 +# . Need to add support for other types of credentials, e.g. tickets
 +# . add namespaces to signed-credential element?
 +
 +signature_template = \
 +'''
 +<Signature xml:id="Sig_%s" xmlns="http://www.w3.org/2000/09/xmldsig#">
 +  <SignedInfo>
 +    <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
 +    <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
 +    <Reference URI="#%s">
 +      <Transforms>
 +        <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
 +      </Transforms>
 +      <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
 +      <DigestValue></DigestValue>
 +    </Reference>
 +  </SignedInfo>
 +  <SignatureValue />
 +  <KeyInfo>
 +    <X509Data>
 +      <X509SubjectName/>
 +      <X509IssuerSerial/>
 +      <X509Certificate/>
 +    </X509Data>
 +    <KeyValue />
 +  </KeyInfo>
 +</Signature>
 +'''
 +
 +# PG formats the template (whitespace) slightly differently.
 +# Note that they don't include the xmlns in the template, but add it later.
 +# Otherwise the two are equivalent.
 +#signature_template_as_in_pg = \
 +#'''
 +#<Signature xml:id="Sig_%s" >
 +# <SignedInfo>
 +#  <CanonicalizationMethod      Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
 +#  <SignatureMethod      Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
 +#  <Reference URI="#%s">
 +#    <Transforms>
 +#      <Transform         Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
 +#    </Transforms>
 +#    <DigestMethod        Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
 +#    <DigestValue></DigestValue>
 +#    </Reference>
 +# </SignedInfo>
 +# <SignatureValue />
 +# <KeyInfo>
 +#  <X509Data >
 +#   <X509SubjectName/>
 +#   <X509IssuerSerial/>
 +#   <X509Certificate/>
 +#  </X509Data>
 +#  <KeyValue />
 +# </KeyInfo>
 +#</Signature>
 +#'''
 +
 +##
 +# Convert a string into a bool
 +# used to convert an xsd:boolean to a Python boolean
 +def str2bool(str):
 +    if str.lower() in ['true','1']:
 +        return True
 +    return False
 +
 +
 +##
 +# Utility function to get the text of an XML element
 +
 +def getTextNode(element, subele):
 +    sub = element.getElementsByTagName(subele)[0]
 +    if len(sub.childNodes) > 0:            
 +        return sub.childNodes[0].nodeValue
 +    else:
 +        return None
 +        
 +##
 +# Utility function to set the text of an XML element
 +# It creates the element, adds the text to it,
 +# and then appends it to the parent.
 +
 +def append_sub(doc, parent, element, text):
 +    ele = doc.createElement(element)
 +    ele.appendChild(doc.createTextNode(text))
 +    parent.appendChild(ele)
 +
 +##
 +# Signature contains information about an xmlsec1 signature
 +# for a signed-credential
 +#
 +
 +class Signature(object):
 +   
 +    def __init__(self, string=None):
 +        self.refid = None
 +        self.issuer_gid = None
 +        self.xml = None
 +        if string:
 +            self.xml = string
 +            self.decode()
 +
 +
 +    def get_refid(self):
 +        if not self.refid:
 +            self.decode()
 +        return self.refid
 +
 +    def get_xml(self):
 +        if not self.xml:
 +            self.encode()
 +        return self.xml
 +
 +    def set_refid(self, id):
 +        self.refid = id
 +
 +    def get_issuer_gid(self):
 +        if not self.gid:
 +            self.decode()
 +        return self.gid        
 +
 +    def set_issuer_gid(self, gid):
 +        self.gid = gid
 +
 +    def decode(self):
 +        try:
 +            doc = parseString(self.xml)
 +        except ExpatError,e:
 +            logger.log_exc ("Failed to parse credential, %s"%self.xml)
 +            raise
 +        sig = doc.getElementsByTagName("Signature")[0]
 +        self.set_refid(sig.getAttribute("xml:id").strip("Sig_"))
 +        keyinfo = sig.getElementsByTagName("X509Data")[0]
 +        szgid = getTextNode(keyinfo, "X509Certificate")
 +        szgid = "-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----" % szgid
 +        self.set_issuer_gid(GID(string=szgid))        
 +        
 +    def encode(self):
 +        self.xml = signature_template % (self.get_refid(), self.get_refid())
 +
 +
 +##
 +# A credential provides a caller gid with privileges to an object gid.
 +# A signed credential is signed by the object's authority.
 +#
 +# Credentials are encoded in one of two ways.  The legacy style places
 +# it in the subjectAltName of an X509 certificate.  The new credentials
 +# are placed in signed XML.
 +#
 +# WARNING:
 +# In general, a signed credential obtained externally should
 +# not be changed else the signature is no longer valid.  So, once
 +# you have loaded an existing signed credential, do not call encode() or sign() on it.
 +
 +def filter_creds_by_caller(creds, caller_hrn_list):
 +        """
 +        Returns a list of creds who's gid caller matches the
 +        specified caller hrn
 +        """
 +        if not isinstance(creds, list): creds = [creds]
 +        if not isinstance(caller_hrn_list, list): 
 +            caller_hrn_list = [caller_hrn_list]
 +        caller_creds = []
 +        for cred in creds:
 +            try:
 +                tmp_cred = Credential(string=cred)
 +                if tmp_cred.get_gid_caller().get_hrn() in caller_hrn_list:
 +                    caller_creds.append(cred)
 +            except: pass
 +        return caller_creds
 +
 +class Credential(object):
 +
 +    ##
 +    # Create a Credential object
 +    #
 +    # @param create If true, create a blank x509 certificate
 +    # @param subject If subject!=None, create an x509 cert with the subject name
 +    # @param string If string!=None, load the credential from the string
 +    # @param filename If filename!=None, load the credential from the file
 +    # FIXME: create and subject are ignored!
 +    def __init__(self, create=False, subject=None, string=None, filename=None):
 +        self.gidCaller = None
 +        self.gidObject = None
 +        self.expiration = None
 +        self.privileges = None
 +        self.issuer_privkey = None
 +        self.issuer_gid = None
 +        self.issuer_pubkey = None
 +        self.parent = None
 +        self.signature = None
 +        self.xml = None
 +        self.refid = None
 +        self.legacy = None
 +
 +        # Check if this is a legacy credential, translate it if so
 +        if string or filename:
 +            if string:                
 +                str = string
 +            elif filename:
 +                str = file(filename).read()
 +                
 +            if str.strip().startswith("-----"):
 +                self.legacy = CredentialLegacy(False,string=str)
 +                self.translate_legacy(str)
 +            else:
 +                self.xml = str
 +                self.decode()
 +
 +        # Find an xmlsec1 path
 +        self.xmlsec_path = ''
 +        paths = ['/usr/bin','/usr/local/bin','/bin','/opt/bin','/opt/local/bin']
 +        for path in paths:
 +            if os.path.isfile(path + '/' + 'xmlsec1'):
 +                self.xmlsec_path = path + '/' + 'xmlsec1'
 +                break
 +
 +    def get_subject(self):
 +        if not self.gidObject:
 +            self.decode()
 +        return self.gidObject.get_printable_subject()
 +
 +    # sounds like this should be __repr__ instead ??
 +    def get_summary_tostring(self):
 +        if not self.gidObject:
 +            self.decode()
 +        obj = self.gidObject.get_printable_subject()
 +        caller = self.gidCaller.get_printable_subject()
 +        exp = self.get_expiration()
 +        # Summarize the rights too? The issuer?
 +        return "[ Grant %s rights on %s until %s ]" % (caller, obj, exp)
 +
 +    def get_signature(self):
 +        if not self.signature:
 +            self.decode()
 +        return self.signature
 +
 +    def set_signature(self, sig):
 +        self.signature = sig
 +
 +        
 +    ##
 +    # Translate a legacy credential into a new one
 +    #
 +    # @param String of the legacy credential
 +
 +    def translate_legacy(self, str):
 +        legacy = CredentialLegacy(False,string=str)
 +        self.gidCaller = legacy.get_gid_caller()
 +        self.gidObject = legacy.get_gid_object()
 +        lifetime = legacy.get_lifetime()
 +        if not lifetime:
 +            self.set_expiration(datetime.datetime.utcnow() + datetime.timedelta(seconds=DEFAULT_CREDENTIAL_LIFETIME))
 +        else:
 +            self.set_expiration(int(lifetime))
 +        self.lifeTime = legacy.get_lifetime()
 +        self.set_privileges(legacy.get_privileges())
 +        self.get_privileges().delegate_all_privileges(legacy.get_delegate())
 +
 +    ##
 +    # Need the issuer's private key and name
 +    # @param key Keypair object containing the private key of the issuer
 +    # @param gid GID of the issuing authority
 +
 +    def set_issuer_keys(self, privkey, gid):
 +        self.issuer_privkey = privkey
 +        self.issuer_gid = gid
 +
 +
 +    ##
 +    # Set this credential's parent
 +    def set_parent(self, cred):
 +        self.parent = cred
 +        self.updateRefID()
 +
 +    ##
 +    # set the GID of the caller
 +    #
 +    # @param gid GID object of the caller
 +
 +    def set_gid_caller(self, gid):
 +        self.gidCaller = gid
 +        # gid origin caller is the caller's gid by default
 +        self.gidOriginCaller = gid
 +
 +    ##
 +    # get the GID of the object
 +
 +    def get_gid_caller(self):
 +        if not self.gidCaller:
 +            self.decode()
 +        return self.gidCaller
 +
 +    ##
 +    # set the GID of the object
 +    #
 +    # @param gid GID object of the object
 +
 +    def set_gid_object(self, gid):
 +        self.gidObject = gid
 +
 +    ##
 +    # get the GID of the object
 +
 +    def get_gid_object(self):
 +        if not self.gidObject:
 +            self.decode()
 +        return self.gidObject
 +            
 +    ##
 +    # Expiration: an absolute UTC time of expiration (as either an int or string or datetime)
 +    # 
 +    def set_expiration(self, expiration):
 +        if isinstance(expiration, (int, float)):
 +            self.expiration = datetime.datetime.fromtimestamp(expiration)
 +        elif isinstance (expiration, datetime.datetime):
 +            self.expiration = expiration
 +        elif isinstance (expiration, StringTypes):
 +            self.expiration = utcparse (expiration)
 +        else:
 +            logger.error ("unexpected input type in Credential.set_expiration")
 +
 +
 +    ##
 +    # get the lifetime of the credential (always in datetime format)
 +
 +    def get_expiration(self):
 +        if not self.expiration:
 +            self.decode()
 +        # at this point self.expiration is normalized as a datetime - DON'T call utcparse again
 +        return self.expiration
 +
 +    ##
 +    # For legacy sake
 +    def get_lifetime(self):
 +        return self.get_expiration()
 + 
 +    ##
 +    # set the privileges
 +    #
 +    # @param privs either a comma-separated list of privileges of a Rights object
 +
 +    def set_privileges(self, privs):
 +        if isinstance(privs, str):
 +            self.privileges = Rights(string = privs)
 +        else:
-             self.privileges = privs
-         
++            self.privileges = privs        
 +
 +    ##
 +    # return the privileges as a Rights object
 +
 +    def get_privileges(self):
 +        if not self.privileges:
 +            self.decode()
 +        return self.privileges
 +
 +    ##
 +    # determine whether the credential allows a particular operation to be
 +    # performed
 +    #
 +    # @param op_name string specifying name of operation ("lookup", "update", etc)
 +
 +    def can_perform(self, op_name):
 +        rights = self.get_privileges()
 +        
 +        if not rights:
 +            return False
 +
 +        return rights.can_perform(op_name)
 +
 +
 +    ##
 +    # Encode the attributes of the credential into an XML string    
 +    # This should be done immediately before signing the credential.    
 +    # WARNING:
 +    # In general, a signed credential obtained externally should
 +    # not be changed else the signature is no longer valid.  So, once
 +    # you have loaded an existing signed credential, do not call encode() or sign() on it.
 +
 +    def encode(self):
 +        # Create the XML document
 +        doc = Document()
 +        signed_cred = doc.createElement("signed-credential")
 +
 +# Declare namespaces
 +# Note that credential/policy.xsd are really the PG schemas
 +# in a PL namespace.
 +# Note that delegation of credentials between the 2 only really works
 +# cause those schemas are identical.
 +# Also note these PG schemas talk about PG tickets and CM policies.
 +        signed_cred.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
 +        signed_cred.setAttribute("xsi:noNamespaceSchemaLocation", "http://www.planet-lab.org/resources/sfa/credential.xsd")
 +        signed_cred.setAttribute("xsi:schemaLocation", "http://www.planet-lab.org/resources/sfa/ext/policy/1 http://www.planet-lab.org/resources/sfa/ext/policy/1/policy.xsd")
 +
 +# PG says for those last 2:
 +#        signed_cred.setAttribute("xsi:noNamespaceSchemaLocation", "http://www.protogeni.net/resources/credential/credential.xsd")
 +#        signed_cred.setAttribute("xsi:schemaLocation", "http://www.protogeni.net/resources/credential/ext/policy/1 http://www.protogeni.net/resources/credential/ext/policy/1/policy.xsd")
 +
 +        doc.appendChild(signed_cred)  
 +        
 +        # Fill in the <credential> bit        
 +        cred = doc.createElement("credential")
 +        cred.setAttribute("xml:id", self.get_refid())
 +        signed_cred.appendChild(cred)
 +        append_sub(doc, cred, "type", "privilege")
 +        append_sub(doc, cred, "serial", "8")
 +        append_sub(doc, cred, "owner_gid", self.gidCaller.save_to_string())
 +        append_sub(doc, cred, "owner_urn", self.gidCaller.get_urn())
 +        append_sub(doc, cred, "target_gid", self.gidObject.save_to_string())
 +        append_sub(doc, cred, "target_urn", self.gidObject.get_urn())
 +        append_sub(doc, cred, "uuid", "")
 +        if not self.expiration:
 +            self.set_expiration(datetime.datetime.utcnow() + datetime.timedelta(seconds=DEFAULT_CREDENTIAL_LIFETIME))
 +        self.expiration = self.expiration.replace(microsecond=0)
 +        append_sub(doc, cred, "expires", self.expiration.isoformat())
 +        privileges = doc.createElement("privileges")
 +        cred.appendChild(privileges)
 +
 +        if self.privileges:
 +            rights = self.get_privileges()
 +            for right in rights.rights:
 +                priv = doc.createElement("privilege")
 +                append_sub(doc, priv, "name", right.kind)
 +                append_sub(doc, priv, "can_delegate", str(right.delegate).lower())
 +                privileges.appendChild(priv)
 +
 +        # Add the parent credential if it exists
 +        if self.parent:
 +            sdoc = parseString(self.parent.get_xml())
 +            # If the root node is a signed-credential (it should be), then
 +            # get all its attributes and attach those to our signed_cred
 +            # node.
 +            # Specifically, PG and PLadd attributes for namespaces (which is reasonable),
 +            # and we need to include those again here or else their signature
 +            # no longer matches on the credential.
 +            # We expect three of these, but here we copy them all:
 +#        signed_cred.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
 +# and from PG (PL is equivalent, as shown above):
 +#        signed_cred.setAttribute("xsi:noNamespaceSchemaLocation", "http://www.protogeni.net/resources/credential/credential.xsd")
 +#        signed_cred.setAttribute("xsi:schemaLocation", "http://www.protogeni.net/resources/credential/ext/policy/1 http://www.protogeni.net/resources/credential/ext/policy/1/policy.xsd")
 +
 +            # HOWEVER!
 +            # PL now also declares these, with different URLs, so
 +            # the code notices those attributes already existed with
 +            # different values, and complains.
 +            # This happens regularly on delegation now that PG and
 +            # PL both declare the namespace with different URLs.
 +            # If the content ever differs this is a problem,
 +            # but for now it works - different URLs (values in the attributes)
 +            # but the same actual schema, so using the PG schema
 +            # on delegated-to-PL credentials works fine.
 +
 +            # Note: you could also not copy attributes
 +            # which already exist. It appears that both PG and PL
 +            # will actually validate a slicecred with a parent
 +            # signed using PG namespaces and a child signed with PL
 +            # namespaces over the whole thing. But I don't know
 +            # if that is a bug in xmlsec1, an accident since
 +            # the contents of the schemas are the same,
 +            # or something else, but it seems odd. And this works.
 +            parentRoot = sdoc.documentElement
 +            if parentRoot.tagName == "signed-credential" and parentRoot.hasAttributes():
 +                for attrIx in range(0, parentRoot.attributes.length):
 +                    attr = parentRoot.attributes.item(attrIx)
 +                    # returns the old attribute of same name that was
 +                    # on the credential
 +                    # Below throws InUse exception if we forgot to clone the attribute first
 +                    oldAttr = signed_cred.setAttributeNode(attr.cloneNode(True))
 +                    if oldAttr and oldAttr.value != attr.value:
 +                        msg = "Delegating cred from owner %s to %s over %s replaced attribute %s value '%s' with '%s'" % (self.parent.gidCaller.get_urn(), self.gidCaller.get_urn(), self.gidObject.get_urn(), oldAttr.name, oldAttr.value, attr.value)
 +                        logger.warn(msg)
 +                        #raise CredentialNotVerifiable("Can't encode new valid delegated credential: %s" % msg)
 +
 +            p_cred = doc.importNode(sdoc.getElementsByTagName("credential")[0], True)
 +            p = doc.createElement("parent")
 +            p.appendChild(p_cred)
 +            cred.appendChild(p)
 +        # done handling parent credential
 +
 +        # Create the <signatures> tag
 +        signatures = doc.createElement("signatures")
 +        signed_cred.appendChild(signatures)
 +
 +        # Add any parent signatures
 +        if self.parent:
 +            for cur_cred in self.get_credential_list()[1:]:
 +                sdoc = parseString(cur_cred.get_signature().get_xml())
 +                ele = doc.importNode(sdoc.getElementsByTagName("Signature")[0], True)
 +                signatures.appendChild(ele)
 +                
 +        # Get the finished product
 +        self.xml = doc.toxml()
 +
 +
 +    def save_to_random_tmp_file(self):       
 +        fp, filename = mkstemp(suffix='cred', text=True)
 +        fp = os.fdopen(fp, "w")
 +        self.save_to_file(filename, save_parents=True, filep=fp)
 +        return filename
 +    
 +    def save_to_file(self, filename, save_parents=True, filep=None):
 +        if not self.xml:
 +            self.encode()
 +        if filep:
 +            f = filep 
 +        else:
 +            f = open(filename, "w")
 +        f.write(self.xml)
 +        f.close()
 +
 +    def save_to_string(self, save_parents=True):
 +        if not self.xml:
 +            self.encode()
 +        return self.xml
 +
 +    def get_refid(self):
 +        if not self.refid:
 +            self.refid = 'ref0'
 +        return self.refid
 +
 +    def set_refid(self, rid):
 +        self.refid = rid
 +
 +    ##
 +    # Figure out what refids exist, and update this credential's id
 +    # so that it doesn't clobber the others.  Returns the refids of
 +    # the parents.
 +    
 +    def updateRefID(self):
 +        if not self.parent:
-             self.set_refid('ref0') 
++            self.set_refid('ref0')
 +            return []
 +        
 +        refs = []
 +
 +        next_cred = self.parent
-        
 +        while next_cred:
-           
 +            refs.append(next_cred.get_refid())
 +            if next_cred.parent:
 +                next_cred = next_cred.parent
 +            else:
 +                next_cred = None
 +
 +        
 +        # Find a unique refid for this credential
 +        rid = self.get_refid()
 +        while rid in refs:
 +            val = int(rid[3:])
 +            rid = "ref%d" % (val + 1)
 +
 +        # Set the new refid
 +        self.set_refid(rid)
 +
 +        # Return the set of parent credential ref ids
 +        return refs
 +
 +    def get_xml(self):
 +        if not self.xml:
 +            self.encode()
 +        return self.xml
 +
 +    ##
 +    # Sign the XML file created by encode()
 +    #
 +    # WARNING:
 +    # In general, a signed credential obtained externally should
 +    # not be changed else the signature is no longer valid.  So, once
 +    # you have loaded an existing signed credential, do not call encode() or sign() on it.
 +
 +    def sign(self):
 +        if not self.issuer_privkey or not self.issuer_gid:
 +            return
 +        doc = parseString(self.get_xml())
 +        sigs = doc.getElementsByTagName("signatures")[0]
 +
 +        # Create the signature template to be signed
 +        signature = Signature()
 +        signature.set_refid(self.get_refid())
 +        sdoc = parseString(signature.get_xml())        
 +        sig_ele = doc.importNode(sdoc.getElementsByTagName("Signature")[0], True)
 +        sigs.appendChild(sig_ele)
 +
 +        self.xml = doc.toxml()
 +
 +
 +        # Split the issuer GID into multiple certificates if it's a chain
 +        chain = GID(filename=self.issuer_gid)
 +        gid_files = []
 +        while chain:
 +            gid_files.append(chain.save_to_random_tmp_file(False))
 +            if chain.get_parent():
 +                chain = chain.get_parent()
 +            else:
 +                chain = None
 +
 +
 +        # Call out to xmlsec1 to sign it
 +        ref = 'Sig_%s' % self.get_refid()
 +        filename = self.save_to_random_tmp_file()
 +        signed = os.popen('%s --sign --node-id "%s" --privkey-pem %s,%s %s' \
 +                 % (self.xmlsec_path, ref, self.issuer_privkey, ",".join(gid_files), filename)).read()
 +        os.remove(filename)
 +
 +        for gid_file in gid_files:
 +            os.remove(gid_file)
 +
 +        self.xml = signed
 +
 +        # This is no longer a legacy credential
 +        if self.legacy:
 +            self.legacy = None
 +
 +        # Update signatures
 +        self.decode()       
 +
 +        
 +    ##
 +    # Retrieve the attributes of the credential from the XML.
 +    # This is automatically called by the various get_* methods of
 +    # this class and should not need to be called explicitly.
 +
 +    def decode(self):
 +        if not self.xml:
 +            return
 +        doc = parseString(self.xml)
 +        sigs = []
 +        signed_cred = doc.getElementsByTagName("signed-credential")
 +
 +        # Is this a signed-cred or just a cred?
 +        if len(signed_cred) > 0:
 +            creds = signed_cred[0].getElementsByTagName("credential")
 +            signatures = signed_cred[0].getElementsByTagName("signatures")
 +            if len(signatures) > 0:
 +                sigs = signatures[0].getElementsByTagName("Signature")
 +        else:
 +            creds = doc.getElementsByTagName("credential")
 +        
 +        if creds is None or len(creds) == 0:
 +            # malformed cred file
 +            raise CredentialNotVerifiable("Malformed XML: No credential tag found")
 +
 +        # Just take the first cred if there are more than one
 +        cred = creds[0]
 +
 +        self.set_refid(cred.getAttribute("xml:id"))
 +        self.set_expiration(utcparse(getTextNode(cred, "expires")))
 +        self.gidCaller = GID(string=getTextNode(cred, "owner_gid"))
 +        self.gidObject = GID(string=getTextNode(cred, "target_gid"))   
 +
 +
 +        # Process privileges
 +        privs = cred.getElementsByTagName("privileges")[0]
 +        rlist = Rights()
 +        for priv in privs.getElementsByTagName("privilege"):
 +            kind = getTextNode(priv, "name")
 +            deleg = str2bool(getTextNode(priv, "can_delegate"))
 +            if kind == '*':
 +                # Convert * into the default privileges for the credential's type
 +                # Each inherits the delegatability from the * above
 +                _ , type = urn_to_hrn(self.gidObject.get_urn())
 +                rl = determine_rights(type, self.gidObject.get_urn())
 +                for r in rl.rights:
 +                    r.delegate = deleg
 +                    rlist.add(r)
 +            else:
 +                rlist.add(Right(kind.strip(), deleg))
 +        self.set_privileges(rlist)
 +
 +
 +        # Is there a parent?
 +        parent = cred.getElementsByTagName("parent")
 +        if len(parent) > 0:
 +            parent_doc = parent[0].getElementsByTagName("credential")[0]
 +            parent_xml = parent_doc.toxml()
 +            self.parent = Credential(string=parent_xml)
 +            self.updateRefID()
 +
 +        # Assign the signatures to the credentials
 +        for sig in sigs:
 +            Sig = Signature(string=sig.toxml())
 +
 +            for cur_cred in self.get_credential_list():
 +                if cur_cred.get_refid() == Sig.get_refid():
 +                    cur_cred.set_signature(Sig)
 +                                    
 +            
 +    ##
 +    # Verify
 +    #   trusted_certs: A list of trusted GID filenames (not GID objects!) 
 +    #                  Chaining is not supported within the GIDs by xmlsec1.
 +    #
 +    #   trusted_certs_required: Should usually be true. Set False means an
 +    #                 empty list of trusted_certs would still let this method pass.
 +    #                 It just skips xmlsec1 verification et al. Only used by some utils
 +    #    
 +    # Verify that:
 +    # . All of the signatures are valid and that the issuers trace back
 +    #   to trusted roots (performed by xmlsec1)
 +    # . The XML matches the credential schema
 +    # . That the issuer of the credential is the authority in the target's urn
 +    #    . In the case of a delegated credential, this must be true of the root
 +    # . That all of the gids presented in the credential are valid
 +    #    . Including verifying GID chains, and includ the issuer
 +    # . The credential is not expired
 +    #
 +    # -- For Delegates (credentials with parents)
 +    # . The privileges must be a subset of the parent credentials
 +    # . The privileges must have "can_delegate" set for each delegated privilege
 +    # . The target gid must be the same between child and parents
 +    # . The expiry time on the child must be no later than the parent
 +    # . The signer of the child must be the owner of the parent
 +    #
 +    # -- Verify does *NOT*
 +    # . ensure that an xmlrpc client's gid matches a credential gid, that
 +    #   must be done elsewhere
 +    #
 +    # @param trusted_certs: The certificates of trusted CA certificates
 +    def verify(self, trusted_certs=None, schema=None, trusted_certs_required=True):
 +        if not self.xml:
 +            self.decode()
 +
 +        # validate against RelaxNG schema
 +        if HAVELXML and not self.legacy:
 +            if schema and os.path.exists(schema):
 +                tree = etree.parse(StringIO(self.xml))
 +                schema_doc = etree.parse(schema)
 +                xmlschema = etree.XMLSchema(schema_doc)
 +                if not xmlschema.validate(tree):
 +                    error = xmlschema.error_log.last_error
 +                    message = "%s: %s (line %s)" % (self.get_summary_tostring(), error.message, error.line)
 +                    raise CredentialNotVerifiable(message)
 +
 +        if trusted_certs_required and trusted_certs is None:
 +            trusted_certs = []
 +
 +#        trusted_cert_objects = [GID(filename=f) for f in trusted_certs]
 +        trusted_cert_objects = []
 +        ok_trusted_certs = []
 +        # If caller explicitly passed in None that means skip cert chain validation.
 +        # Strange and not typical
 +        if trusted_certs is not None:
 +            for f in trusted_certs:
 +                try:
 +                    # Failures here include unreadable files
 +                    # or non PEM files
 +                    trusted_cert_objects.append(GID(filename=f))
 +                    ok_trusted_certs.append(f)
 +                except Exception, exc:
 +                    logger.error("Failed to load trusted cert from %s: %r", f, exc)
 +            trusted_certs = ok_trusted_certs
 +
 +        # Use legacy verification if this is a legacy credential
 +        if self.legacy:
 +            self.legacy.verify_chain(trusted_cert_objects)
 +            if self.legacy.client_gid:
 +                self.legacy.client_gid.verify_chain(trusted_cert_objects)
 +            if self.legacy.object_gid:
 +                self.legacy.object_gid.verify_chain(trusted_cert_objects)
 +            return True
 +        
 +        # make sure it is not expired
 +        if self.get_expiration() < datetime.datetime.utcnow():
 +            raise CredentialNotVerifiable("Credential %s expired at %s" % (self.get_summary_tostring(), self.expiration.isoformat()))
 +
 +        # Verify the signatures
 +        filename = self.save_to_random_tmp_file()
 +        if trusted_certs is not None:
 +            cert_args = " ".join(['--trusted-pem %s' % x for x in trusted_certs])
 +
 +        # If caller explicitly passed in None that means skip cert chain validation.
 +        # - Strange and not typical
 +        if trusted_certs is not None:
 +            # Verify the gids of this cred and of its parents
 +            for cur_cred in self.get_credential_list():
 +                cur_cred.get_gid_object().verify_chain(trusted_cert_objects)
-                 cur_cred.get_gid_caller().verify_chain(trusted_cert_objects)        
++                cur_cred.get_gid_caller().verify_chain(trusted_cert_objects)
 +
 +        refs = []
 +        refs.append("Sig_%s" % self.get_refid())
 +
 +        parentRefs = self.updateRefID()
 +        for ref in parentRefs:
 +            refs.append("Sig_%s" % ref)
++
 +        for ref in refs:
 +            # If caller explicitly passed in None that means skip xmlsec1 validation.
 +            # Strange and not typical
 +            if trusted_certs is None:
 +                break
 +
 +#            print "Doing %s --verify --node-id '%s' %s %s 2>&1" % \
 +#                (self.xmlsec_path, ref, cert_args, filename)
 +            verified = os.popen('%s --verify --node-id "%s" %s %s 2>&1' \
 +                            % (self.xmlsec_path, ref, cert_args, filename)).read()
 +            if not verified.strip().startswith("OK"):
 +                # xmlsec errors have a msg= which is the interesting bit.
 +                mstart = verified.find("msg=")
 +                msg = ""
 +                if mstart > -1 and len(verified) > 4:
 +                    mstart = mstart + 4
 +                    mend = verified.find('\\', mstart)
 +                    msg = verified[mstart:mend]
 +                raise CredentialNotVerifiable("xmlsec1 error verifying cred %s using Signature ID %s: %s %s" % (self.get_summary_tostring(), ref, msg, verified.strip()))
 +        os.remove(filename)
-         
++
 +        # Verify the parents (delegation)
 +        if self.parent:
 +            self.verify_parent(self.parent)
++
 +        # Make sure the issuer is the target's authority, and is
 +        # itself a valid GID
 +        self.verify_issuer(trusted_cert_objects)
 +        return True
 +
 +    ##
 +    # Creates a list of the credential and its parents, with the root 
 +    # (original delegated credential) as the last item in the list
 +    def get_credential_list(self):    
 +        cur_cred = self
 +        list = []
 +        while cur_cred:
 +            list.append(cur_cred)
 +            if cur_cred.parent:
 +                cur_cred = cur_cred.parent
 +            else:
 +                cur_cred = None
 +        return list
 +    
 +    ##
 +    # Make sure the credential's target gid (a) was signed by or (b)
 +    # is the same as the entity that signed the original credential,
 +    # or (c) is an authority over the target's namespace.
 +    # Also ensure that the credential issuer / signer itself has a valid
 +    # GID signature chain (signed by an authority with namespace rights).
 +    def verify_issuer(self, trusted_gids):
 +        root_cred = self.get_credential_list()[-1]
 +        root_target_gid = root_cred.get_gid_object()
 +        root_cred_signer = root_cred.get_signature().get_issuer_gid()
 +
 +        # Case 1:
 +        # Allow non authority to sign target and cred about target.
 +        #
 +        # Why do we need to allow non authorities to sign?
 +        # If in the target gid validation step we correctly
 +        # checked that the target is only signed by an authority,
 +        # then this is just a special case of case 3.
 +        # This short-circuit is the common case currently -
 +        # and cause GID validation doesn't check 'authority',
 +        # this allows users to generate valid slice credentials.
 +        if root_target_gid.is_signed_by_cert(root_cred_signer):
 +            # cred signer matches target signer, return success
 +            return
 +
 +        # Case 2:
 +        # Allow someone to sign credential about themeselves. Used?
 +        # If not, remove this.
 +        #root_target_gid_str = root_target_gid.save_to_string()
 +        #root_cred_signer_str = root_cred_signer.save_to_string()
 +        #if root_target_gid_str == root_cred_signer_str:
 +        #    # cred signer is target, return success
 +        #    return
 +
 +        # Case 3:
 +
 +        # root_cred_signer is not the target_gid
 +        # So this is a different gid that we have not verified.
 +        # xmlsec1 verified the cert chain on this already, but
 +        # it hasn't verified that the gid meets the HRN namespace
 +        # requirements.
 +        # Below we'll ensure that it is an authority.
 +        # But we haven't verified that it is _signed by_ an authority
 +        # We also don't know if xmlsec1 requires that cert signers
 +        # are marked as CAs.
 +
 +        # Note that if verify() gave us no trusted_gids then this
 +        # call will fail. So skip it if we have no trusted_gids
 +        if trusted_gids and len(trusted_gids) > 0:
 +            root_cred_signer.verify_chain(trusted_gids)
 +        else:
 +            logger.debug("No trusted gids. Cannot verify that cred signer is signed by a trusted authority. Skipping that check.")
 +
 +        # See if the signer is an authority over the domain of the target.
 +        # There are multiple types of authority - accept them all here
 +        # Maybe should be (hrn, type) = urn_to_hrn(root_cred_signer.get_urn())
 +        root_cred_signer_type = root_cred_signer.get_type()
 +        if (root_cred_signer_type.find('authority') == 0):
 +            #logger.debug('Cred signer is an authority')
 +            # signer is an authority, see if target is in authority's domain
 +            signerhrn = root_cred_signer.get_hrn()
 +            if hrn_authfor_hrn(signerhrn, root_target_gid.get_hrn()):
 +                return
 +
 +        # We've required that the credential be signed by an authority
 +        # for that domain. Reasonable and probably correct.
 +        # A looser model would also allow the signer to be an authority
 +        # in my control framework - eg My CA or CH. Even if it is not
 +        # the CH that issued these, eg, user credentials.
 +
 +        # Give up, credential does not pass issuer verification
 +
 +        raise CredentialNotVerifiable("Could not verify credential owned by %s for object %s. Cred signer %s not the trusted authority for Cred target %s" % (self.gidCaller.get_urn(), self.gidObject.get_urn(), root_cred_signer.get_hrn(), root_target_gid.get_hrn()))
 +
 +
 +    ##
 +    # -- For Delegates (credentials with parents) verify that:
 +    # . The privileges must be a subset of the parent credentials
 +    # . The privileges must have "can_delegate" set for each delegated privilege
 +    # . The target gid must be the same between child and parents
 +    # . The expiry time on the child must be no later than the parent
 +    # . The signer of the child must be the owner of the parent        
 +    def verify_parent(self, parent_cred):
 +        # make sure the rights given to the child are a subset of the
 +        # parents rights (and check delegate bits)
 +        if not parent_cred.get_privileges().is_superset(self.get_privileges()):
 +            raise ChildRightsNotSubsetOfParent(("Parent cred ref %s rights " % parent_cred.get_refid()) +
 +                self.parent.get_privileges().save_to_string() + (" not superset of delegated cred %s ref %s rights " % (self.get_summary_tostring(), self.get_refid())) +
 +                self.get_privileges().save_to_string())
 +
 +        # make sure my target gid is the same as the parent's
 +        if not parent_cred.get_gid_object().save_to_string() == \
 +           self.get_gid_object().save_to_string():
 +            raise CredentialNotVerifiable("Delegated cred %s: Target gid not equal between parent and child. Parent %s" % (self.get_summary_tostring(), parent_cred.get_summary_tostring()))
 +
 +        # make sure my expiry time is <= my parent's
 +        if not parent_cred.get_expiration() >= self.get_expiration():
 +            raise CredentialNotVerifiable("Delegated credential %s expires after parent %s" % (self.get_summary_tostring(), parent_cred.get_summary_tostring()))
 +
 +        # make sure my signer is the parent's caller
 +        if not parent_cred.get_gid_caller().save_to_string(False) == \
 +           self.get_signature().get_issuer_gid().save_to_string(False):
 +            raise CredentialNotVerifiable("Delegated credential %s not signed by parent %s's caller" % (self.get_summary_tostring(), parent_cred.get_summary_tostring()))
 +                
 +        # Recurse
 +        if parent_cred.parent:
 +            parent_cred.verify_parent(parent_cred.parent)
 +
 +
 +    def delegate(self, delegee_gidfile, caller_keyfile, caller_gidfile):
 +        """
 +        Return a delegated copy of this credential, delegated to the 
 +        specified gid's user.    
 +        """
 +        # get the gid of the object we are delegating
 +        object_gid = self.get_gid_object()
 +        object_hrn = object_gid.get_hrn()        
 + 
 +        # the hrn of the user who will be delegated to
 +        delegee_gid = GID(filename=delegee_gidfile)
 +        delegee_hrn = delegee_gid.get_hrn()
 +  
 +        #user_key = Keypair(filename=keyfile)
 +        #user_hrn = self.get_gid_caller().get_hrn()
 +        subject_string = "%s delegated to %s" % (object_hrn, delegee_hrn)
 +        dcred = Credential(subject=subject_string)
 +        dcred.set_gid_caller(delegee_gid)
 +        dcred.set_gid_object(object_gid)
 +        dcred.set_parent(self)
 +        dcred.set_expiration(self.get_expiration())
 +        dcred.set_privileges(self.get_privileges())
 +        dcred.get_privileges().delegate_all_privileges(True)
 +        #dcred.set_issuer_keys(keyfile, delegee_gidfile)
 +        dcred.set_issuer_keys(caller_keyfile, caller_gidfile)
 +        dcred.encode()
 +        dcred.sign()
 +
 +        return dcred
 +
 +    # only informative
 +    def get_filename(self):
 +        return getattr(self,'filename',None)
 +
 +    ##
 +    # Dump the contents of a credential to stdout in human-readable format
 +    #
 +    # @param dump_parents If true, also dump the parent certificates
 +    def dump (self, *args, **kwargs):
 +        print self.dump_string(*args, **kwargs)
 +
 +
 +    def dump_string(self, dump_parents=False):
 +        result=""
 +        result += "CREDENTIAL %s\n" % self.get_subject()
 +        filename=self.get_filename()
 +        if filename: result += "Filename %s\n"%filename
 +        result += "      privs: %s\n" % self.get_privileges().save_to_string()
 +        gidCaller = self.get_gid_caller()
 +        if gidCaller:
 +            result += "  gidCaller:\n"
 +            result += gidCaller.dump_string(8, dump_parents)
 +
 +        if self.get_signature():
 +            print "  gidIssuer:"
 +            self.get_signature().get_issuer_gid().dump(8, dump_parents)
 +
 +        gidObject = self.get_gid_object()
 +        if gidObject:
 +            result += "  gidObject:\n"
 +            result += gidObject.dump_string(8, dump_parents)
 +
 +        if self.parent and dump_parents:
 +            result += "\nPARENT"
 +            result += self.parent.dump_string(True)
 +
 +        return result
diff --cc sfa/util/xrn.py
 -#----------------------------------------------------------------------\r
 -# Copyright (c) 2008 Board of Trustees, Princeton University\r
 -#\r
 -# Permission is hereby granted, free of charge, to any person obtaining\r
 -# a copy of this software and/or hardware specification (the "Work") to\r
 -# deal in the Work without restriction, including without limitation the\r
 -# rights to use, copy, modify, merge, publish, distribute, sublicense,\r
 -# and/or sell copies of the Work, and to permit persons to whom the Work\r
 -# is furnished to do so, subject to the following conditions:\r
 -#\r
 -# The above copyright notice and this permission notice shall be\r
 -# included in all copies or substantial portions of the Work.\r
 -#\r
 -# THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS \r
 -# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF \r
 -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND \r
 -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT \r
 -# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, \r
 -# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, \r
 -# OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS \r
 -# IN THE WORK.\r
 -#----------------------------------------------------------------------\r
 -\r
 -import re\r
 -\r
 -from sfa.util.faults import SfaAPIError\r
 -\r
 -# for convenience and smoother translation - we should get rid of these functions eventually \r
 -def get_leaf(hrn): return Xrn(hrn).get_leaf()\r
 -def get_authority(hrn): return Xrn(hrn).get_authority_hrn()\r
 -def urn_to_hrn(urn): xrn=Xrn(urn); return (xrn.hrn, xrn.type)\r
 -def hrn_to_urn(hrn,type): return Xrn(hrn, type=type).urn\r
 -def hrn_authfor_hrn(parenthrn, hrn): return Xrn.hrn_is_auth_for_hrn(parenthrn, hrn)\r
 -\r
 -def urn_to_sliver_id(urn, slice_id, node_id, index=0, authority=None):\r
 -    return Xrn(urn).get_sliver_id(slice_id, node_id, index, authority)\r
 -\r
 -class Xrn:\r
 -\r
 -    ########## basic tools on HRNs\r
 -    # split a HRN-like string into pieces\r
 -    # this is like split('.') except for escaped (backslashed) dots\r
 -    # e.g. hrn_split ('a\.b.c.d') -> [ 'a\.b','c','d']\r
 -    @staticmethod\r
 -    def hrn_split(hrn):\r
 -        return [ x.replace('--sep--','\\.') for x in hrn.replace('\\.','--sep--').split('.') ]\r
 -\r
 -    # e.g. hrn_leaf ('a\.b.c.d') -> 'd'\r
 -    @staticmethod\r
 -    def hrn_leaf(hrn): return Xrn.hrn_split(hrn)[-1]\r
 -\r
 -    # e.g. hrn_auth_list ('a\.b.c.d') -> ['a\.b', 'c']\r
 -    @staticmethod\r
 -    def hrn_auth_list(hrn): return Xrn.hrn_split(hrn)[0:-1]\r
 -    \r
 -    # e.g. hrn_auth ('a\.b.c.d') -> 'a\.b.c'\r
 -    @staticmethod\r
 -    def hrn_auth(hrn): return '.'.join(Xrn.hrn_auth_list(hrn))\r
 -    \r
 -    # e.g. escape ('a.b') -> 'a\.b'\r
 -    @staticmethod\r
 -    def escape(token): return re.sub(r'([^\\])\.', r'\1\.', token)\r
 -\r
 -    # e.g. unescape ('a\.b') -> 'a.b'\r
 -    @staticmethod\r
 -    def unescape(token): return token.replace('\\.','.')\r
 -\r
 -    # Return the HRN authority chain from top to bottom.\r
 -    # e.g. hrn_auth_chain('a\.b.c.d') -> ['a\.b', 'a\.b.c']\r
 -    @staticmethod\r
 -    def hrn_auth_chain(hrn):\r
 -        parts = Xrn.hrn_auth_list(hrn)\r
 -        chain = []\r
 -        for i in range(len(parts)):\r
 -            chain.append('.'.join(parts[:i+1]))\r
 -        # Include the HRN itself?\r
 -        #chain.append(hrn)\r
 -        return chain\r
 -\r
 -    # Is the given HRN a true authority over the namespace of the other\r
 -    # child HRN?\r
 -    # A better alternative than childHRN.startswith(parentHRN)\r
 -    # e.g. hrn_is_auth_for_hrn('a\.b', 'a\.b.c.d') -> True,\r
 -    # but hrn_is_auth_for_hrn('a', 'a\.b.c.d') -> False\r
 -    # Also hrn_is_auth_for_hrn('a\.b.c.d', 'a\.b.c.d') -> True\r
 -    @staticmethod\r
 -    def hrn_is_auth_for_hrn(parenthrn, hrn):\r
 -        if parenthrn == hrn:\r
 -            return True\r
 -        for auth in Xrn.hrn_auth_chain(hrn):\r
 -            if parenthrn == auth:\r
 -                return True\r
 -        return False\r
 -\r
 -    ########## basic tools on URNs\r
 -    URN_PREFIX = "urn:publicid:IDN"\r
 -    URN_PREFIX_lower = "urn:publicid:idn"\r
 -\r
 -    @staticmethod\r
 -    def is_urn (text):\r
 -        return text.lower().startswith(Xrn.URN_PREFIX_lower)\r
 -\r
 -    @staticmethod\r
 -    def urn_full (urn):\r
 -        if Xrn.is_urn(urn): return urn\r
 -        else: return Xrn.URN_PREFIX+urn\r
 -    @staticmethod\r
 -    def urn_meaningful (urn):\r
 -        if Xrn.is_urn(urn): return urn[len(Xrn.URN_PREFIX):]\r
 -        else: return urn\r
 -    @staticmethod\r
 -    def urn_split (urn):\r
 -        return Xrn.urn_meaningful(urn).split('+')\r
 -\r
 -    ####################\r
 -    # the local fields that are kept consistent\r
 -    # self.urn\r
 -    # self.hrn\r
 -    # self.type\r
 -    # self.path\r
 -    # provide either urn, or (hrn + type)\r
 -    def __init__ (self, xrn, type=None):\r
 -        if not xrn: xrn = ""\r
 -        # user has specified xrn : guess if urn or hrn\r
 -        if Xrn.is_urn(xrn):\r
 -            self.hrn=None\r
 -            self.urn=xrn\r
 -            self.urn_to_hrn()\r
 -        else:\r
 -            self.urn=None\r
 -            self.hrn=xrn\r
 -            self.type=type\r
 -            self.hrn_to_urn()\r
 -        self._normalize()\r
 -# happens all the time ..\r
 -#        if not type:\r
 -#            debug_logger.debug("type-less Xrn's are not safe")\r
 -\r
 -    def __repr__ (self):\r
 -        result="<XRN u=%s h=%s"%(self.urn,self.hrn)\r
 -        if hasattr(self,'leaf'): result += " leaf=%s"%self.leaf\r
 -        if hasattr(self,'authority'): result += " auth=%s"%self.authority\r
 -        result += ">"\r
 -        return result\r
 -\r
 -    def get_urn(self): return self.urn\r
 -    def get_hrn(self): return self.hrn\r
 -    def get_type(self): return self.type\r
 -    def get_hrn_type(self): return (self.hrn, self.type)\r
 -\r
 -    def _normalize(self):\r
 -        if self.hrn is None: raise SfaAPIError, "Xrn._normalize"\r
 -        if not hasattr(self,'leaf'): \r
 -            self.leaf=Xrn.hrn_split(self.hrn)[-1]\r
 -        # self.authority keeps a list\r
 -        if not hasattr(self,'authority'): \r
 -            self.authority=Xrn.hrn_auth_list(self.hrn)\r
 -\r
 -    def get_leaf(self):\r
 -        self._normalize()\r
 -        return self.leaf\r
 -\r
 -    def get_authority_hrn(self):\r
 -        self._normalize()\r
 -        return '.'.join( self.authority )\r
 -    \r
 -    def get_authority_urn(self): \r
 -        self._normalize()\r
 -        return ':'.join( [Xrn.unescape(x) for x in self.authority] )\r
 -\r
 -    def get_sliver_id(self, slice_id, node_id=None, index=0, authority=None):\r
 -        self._normalize()\r
 -        urn = self.get_urn()\r
 -        if authority:\r
 -            authority_hrn = self.get_authority_hrn()\r
 -            if not authority_hrn.startswith(authority):\r
 -                hrn = ".".join([authority,authority_hrn, self.get_leaf()])\r
 -            else:\r
 -                hrn = ".".join([authority_hrn, self.get_leaf()])\r
 -            urn = Xrn(hrn, self.get_type()).get_urn()\r
 -        parts = [part for part in [urn, slice_id, node_id, index] if part is not None]\r
 -        return ":".join(map(str, [parts]))\r
 -\r
 -    def urn_to_hrn(self):\r
 -        """\r
 -        compute tuple (hrn, type) from urn\r
 -        """\r
 -        \r
 -#        if not self.urn or not self.urn.startswith(Xrn.URN_PREFIX):\r
 -        if not Xrn.is_urn(self.urn):\r
 -            raise SfaAPIError, "Xrn.urn_to_hrn"\r
 -\r
 -        parts = Xrn.urn_split(self.urn)\r
 -        type=parts.pop(2)\r
 -        # Remove the authority name (e.g. '.sa')\r
 -        if type == 'authority':\r
 -            name = parts.pop()\r
 -            # Drop the sa. This is a bad hack, but its either this\r
 -            # or completely change how record types are generated/stored   \r
 -            if name != 'sa':\r
 -                type = type + "+" + name\r
 -            name =""\r
 -        else:\r
 -            name = parts.pop(len(parts)-1)\r
 -        # convert parts (list) into hrn (str) by doing the following\r
 -        # 1. remove blank parts\r
 -        # 2. escape dots inside parts\r
 -        # 3. replace ':' with '.' inside parts\r
 -        # 3. join parts using '.'\r
 -        hrn = '.'.join([Xrn.escape(part).replace(':','.') for part in parts if part])\r
 -        # dont replace ':' in the name section\r
 -        if name:\r
 -            hrn += '.%s' % Xrn.escape(name) \r
 -\r
 -        self.hrn=str(hrn)\r
 -        self.type=str(type)\r
 -    \r
 -    def hrn_to_urn(self):\r
 -        """\r
 -        compute urn from (hrn, type)\r
 -        """\r
 -\r
 -#        if not self.hrn or self.hrn.startswith(Xrn.URN_PREFIX):\r
 -        if Xrn.is_urn(self.hrn):\r
 -            raise SfaAPIError, "Xrn.hrn_to_urn, hrn=%s"%self.hrn\r
 -\r
 -        if self.type and self.type.startswith('authority'):\r
 -            self.authority = Xrn.hrn_auth_list(self.hrn)\r
 -            leaf = self.get_leaf()\r
 -            #if not self.authority:\r
 -            #    self.authority = [self.hrn]\r
 -            type_parts = self.type.split("+")\r
 -            self.type = type_parts[0]\r
 -            name = 'sa'\r
 -            if len(type_parts) > 1:\r
 -                name = type_parts[1]\r
 -            auth_parts = [part for part in [self.get_authority_urn(), leaf] if part]\r
 -            authority_string = ":".join(auth_parts)\r
 -        else:\r
 -            self.authority = Xrn.hrn_auth_list(self.hrn)\r
 -            name = Xrn.hrn_leaf(self.hrn)\r
 -            authority_string = self.get_authority_urn()\r
 -\r
 -        if self.type == None:\r
 -            urn = "+".join(['',authority_string,Xrn.unescape(name)])\r
 -        else:\r
 -            urn = "+".join(['',authority_string,self.type,Xrn.unescape(name)])\r
 -        \r
 -        self.urn = Xrn.URN_PREFIX + urn\r
 -\r
 -    def dump_string(self):\r
 -        result="-------------------- XRN\n"\r
 -        result += "URN=%s\n"%self.urn\r
 -        result += "HRN=%s\n"%self.hrn\r
 -        result += "TYPE=%s\n"%self.type\r
 -        result += "LEAF=%s\n"%self.get_leaf()\r
 -        result += "AUTH(hrn format)=%s\n"%self.get_authority_hrn()\r
 -        result += "AUTH(urn format)=%s\n"%self.get_authority_urn()\r
 -        return result\r
 -        \r
 +#----------------------------------------------------------------------
 +# Copyright (c) 2008 Board of Trustees, Princeton University
 +#
 +# Permission is hereby granted, free of charge, to any person obtaining
 +# a copy of this software and/or hardware specification (the "Work") to
 +# deal in the Work without restriction, including without limitation the
 +# rights to use, copy, modify, merge, publish, distribute, sublicense,
 +# and/or sell copies of the Work, and to permit persons to whom the Work
 +# is furnished to do so, subject to the following conditions:
 +#
 +# The above copyright notice and this permission notice shall be
 +# included in all copies or substantial portions of the Work.
 +#
 +# THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
 +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
 +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
 +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
 +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 +# OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS 
 +# IN THE WORK.
 +#----------------------------------------------------------------------
 +
 +import re
- import sys
++
 +from sfa.util.faults import SfaAPIError
 +
 +# for convenience and smoother translation - we should get rid of these functions eventually 
 +def get_leaf(hrn): return Xrn(hrn).get_leaf()
 +def get_authority(hrn): return Xrn(hrn).get_authority_hrn()
 +def urn_to_hrn(urn): xrn=Xrn(urn); return (xrn.hrn, xrn.type)
 +def hrn_to_urn(hrn,type): return Xrn(hrn, type=type).urn
 +def hrn_authfor_hrn(parenthrn, hrn): return Xrn.hrn_is_auth_for_hrn(parenthrn, hrn)
 +
 +def urn_to_sliver_id(urn, slice_id, node_id, index=0, authority=None):
 +    return Xrn(urn).get_sliver_id(slice_id, node_id, index, authority)
 +
 +class Xrn:
 +
 +    ########## basic tools on HRNs
 +    # split a HRN-like string into pieces
 +    # this is like split('.') except for escaped (backslashed) dots
 +    # e.g. hrn_split ('a\.b.c.d') -> [ 'a\.b','c','d']
 +    @staticmethod
 +    def hrn_split(hrn):
 +        return [ x.replace('--sep--','\\.') for x in hrn.replace('\\.','--sep--').split('.') ]
 +
 +    # e.g. hrn_leaf ('a\.b.c.d') -> 'd'
 +    @staticmethod
 +    def hrn_leaf(hrn): return Xrn.hrn_split(hrn)[-1]
 +
 +    # e.g. hrn_auth_list ('a\.b.c.d') -> ['a\.b', 'c']
 +    @staticmethod
 +    def hrn_auth_list(hrn): return Xrn.hrn_split(hrn)[0:-1]
 +    
 +    # e.g. hrn_auth ('a\.b.c.d') -> 'a\.b.c'
 +    @staticmethod
 +    def hrn_auth(hrn): return '.'.join(Xrn.hrn_auth_list(hrn))
 +    
 +    # e.g. escape ('a.b') -> 'a\.b'
 +    @staticmethod
 +    def escape(token): return re.sub(r'([^\\])\.', r'\1\.', token)
 +
 +    # e.g. unescape ('a\.b') -> 'a.b'
 +    @staticmethod
 +    def unescape(token): return token.replace('\\.','.')
 +
 +    # Return the HRN authority chain from top to bottom.
 +    # e.g. hrn_auth_chain('a\.b.c.d') -> ['a\.b', 'a\.b.c']
 +    @staticmethod
 +    def hrn_auth_chain(hrn):
 +        parts = Xrn.hrn_auth_list(hrn)
 +        chain = []
 +        for i in range(len(parts)):
 +            chain.append('.'.join(parts[:i+1]))
 +        # Include the HRN itself?
 +        #chain.append(hrn)
 +        return chain
 +
 +    # Is the given HRN a true authority over the namespace of the other
 +    # child HRN?
 +    # A better alternative than childHRN.startswith(parentHRN)
 +    # e.g. hrn_is_auth_for_hrn('a\.b', 'a\.b.c.d') -> True,
 +    # but hrn_is_auth_for_hrn('a', 'a\.b.c.d') -> False
-     # Also hrn_is_uauth_for_hrn('a\.b.c.d', 'a\.b.c.d') -> True
++    # Also hrn_is_auth_for_hrn('a\.b.c.d', 'a\.b.c.d') -> True
 +    @staticmethod
 +    def hrn_is_auth_for_hrn(parenthrn, hrn):
 +        if parenthrn == hrn:
 +            return True
 +        for auth in Xrn.hrn_auth_chain(hrn):
 +            if parenthrn == auth:
 +                return True
 +        return False
 +
 +    ########## basic tools on URNs
 +    URN_PREFIX = "urn:publicid:IDN"
 +    URN_PREFIX_lower = "urn:publicid:idn"
 +
 +    @staticmethod
 +    def is_urn (text):
 +        return text.lower().startswith(Xrn.URN_PREFIX_lower)
 +
 +    @staticmethod
 +    def urn_full (urn):
 +        if Xrn.is_urn(urn): return urn
 +        else: return Xrn.URN_PREFIX+urn
 +    @staticmethod
 +    def urn_meaningful (urn):
 +        if Xrn.is_urn(urn): return urn[len(Xrn.URN_PREFIX):]
 +        else: return urn
 +    @staticmethod
 +    def urn_split (urn):
 +        return Xrn.urn_meaningful(urn).split('+')
 +
 +    ####################
 +    # the local fields that are kept consistent
 +    # self.urn
 +    # self.hrn
 +    # self.type
 +    # self.path
 +    # provide either urn, or (hrn + type)
 +    def __init__ (self, xrn, type=None):
 +        if not xrn: xrn = ""
-        
 +        # user has specified xrn : guess if urn or hrn
 +        if Xrn.is_urn(xrn):
 +            self.hrn=None
 +            self.urn=xrn
 +            self.urn_to_hrn()
 +        else:
 +            self.urn=None
 +            self.hrn=xrn
 +            self.type=type
 +            self.hrn_to_urn()
++        self._normalize()
 +# happens all the time ..
 +#        if not type:
 +#            debug_logger.debug("type-less Xrn's are not safe")
 +
 +    def __repr__ (self):
 +        result="<XRN u=%s h=%s"%(self.urn,self.hrn)
 +        if hasattr(self,'leaf'): result += " leaf=%s"%self.leaf
 +        if hasattr(self,'authority'): result += " auth=%s"%self.authority
 +        result += ">"
 +        return result
 +
 +    def get_urn(self): return self.urn
 +    def get_hrn(self): return self.hrn
 +    def get_type(self): return self.type
 +    def get_hrn_type(self): return (self.hrn, self.type)
 +
 +    def _normalize(self):
 +        if self.hrn is None: raise SfaAPIError, "Xrn._normalize"
 +        if not hasattr(self,'leaf'): 
 +            self.leaf=Xrn.hrn_split(self.hrn)[-1]
 +        # self.authority keeps a list
 +        if not hasattr(self,'authority'): 
 +            self.authority=Xrn.hrn_auth_list(self.hrn)
-        
-        
++
 +    def get_leaf(self):
 +        self._normalize()
 +        return self.leaf
 +
 +    def get_authority_hrn(self):
 +        self._normalize()
 +        return '.'.join( self.authority )
 +    
 +    def get_authority_urn(self): 
 +        self._normalize()
 +        return ':'.join( [Xrn.unescape(x) for x in self.authority] )
-    
-     def get_sliver_id(self, slice_id, node_id, index=0, authority=None):
++
++    def get_sliver_id(self, slice_id, node_id=None, index=0, authority=None):
 +        self._normalize()
 +        urn = self.get_urn()
 +        if authority:
 +            authority_hrn = self.get_authority_hrn()
 +            if not authority_hrn.startswith(authority):
-                 hrn = ".".join([authority,self.get_authority_hrn(), self.get_leaf()])
++                hrn = ".".join([authority,authority_hrn, self.get_leaf()])
 +            else:
-                 hrn = ".".join([self.get_authority_hrn(), self.get_leaf()])
++                hrn = ".".join([authority_hrn, self.get_leaf()])
 +            urn = Xrn(hrn, self.get_type()).get_urn()
-         return ":".join(map(str, [urn, slice_id, node_id, index])) 
-  
++        parts = [part for part in [urn, slice_id, node_id, index] if part is not None]
++        return ":".join(map(str, [parts]))
++
 +    def urn_to_hrn(self):
 +        """
 +        compute tuple (hrn, type) from urn
 +        """
 +        
 +#        if not self.urn or not self.urn.startswith(Xrn.URN_PREFIX):
 +        if not Xrn.is_urn(self.urn):
 +            raise SfaAPIError, "Xrn.urn_to_hrn"
 +
 +        parts = Xrn.urn_split(self.urn)
 +        type=parts.pop(2)
 +        # Remove the authority name (e.g. '.sa')
 +        if type == 'authority':
 +            name = parts.pop()
 +            # Drop the sa. This is a bad hack, but its either this
 +            # or completely change how record types are generated/stored   
 +            if name != 'sa':
 +                type = type + "+" + name
 +            name =""
 +        else:
 +            name = parts.pop(len(parts)-1)
 +        # convert parts (list) into hrn (str) by doing the following
 +        # 1. remove blank parts
 +        # 2. escape dots inside parts
 +        # 3. replace ':' with '.' inside parts
 +        # 3. join parts using '.'
 +        hrn = '.'.join([Xrn.escape(part).replace(':','.') for part in parts if part])
 +        # dont replace ':' in the name section
 +        if name:
 +            hrn += '.%s' % Xrn.escape(name) 
 +
 +        self.hrn=str(hrn)
 +        self.type=str(type)
 +    
 +    def hrn_to_urn(self):
 +        """
 +        compute urn from (hrn, type)
 +        """
 +
 +#        if not self.hrn or self.hrn.startswith(Xrn.URN_PREFIX):
 +        if Xrn.is_urn(self.hrn):
 +            raise SfaAPIError, "Xrn.hrn_to_urn, hrn=%s"%self.hrn
 +
 +        if self.type and self.type.startswith('authority'):
 +            self.authority = Xrn.hrn_auth_list(self.hrn)
-             leaf = self.get_leaf() 
++            leaf = self.get_leaf()
 +            #if not self.authority:
 +            #    self.authority = [self.hrn]
 +            type_parts = self.type.split("+")
 +            self.type = type_parts[0]
 +            name = 'sa'
 +            if len(type_parts) > 1:
 +                name = type_parts[1]
-             auth_parts = [part for part in [self.get_authority_urn(), leaf] if part]  
++            auth_parts = [part for part in [self.get_authority_urn(), leaf] if part]
 +            authority_string = ":".join(auth_parts)
 +        else:
 +            self.authority = Xrn.hrn_auth_list(self.hrn)
 +            name = Xrn.hrn_leaf(self.hrn)
 +            authority_string = self.get_authority_urn()
 +
 +        if self.type == None:
 +            urn = "+".join(['',authority_string,Xrn.unescape(name)])
 +        else:
 +            urn = "+".join(['',authority_string,self.type,Xrn.unescape(name)])
 +        
 +        self.urn = Xrn.URN_PREFIX + urn
 +
 +    def dump_string(self):
 +        result="-------------------- XRN\n"
 +        result += "URN=%s\n"%self.urn
 +        result += "HRN=%s\n"%self.hrn
 +        result += "TYPE=%s\n"%self.type
 +        result += "LEAF=%s\n"%self.get_leaf()
 +        result += "AUTH(hrn format)=%s\n"%self.get_authority_hrn()
 +        result += "AUTH(urn format)=%s\n"%self.get_authority_urn()
 +        return result
 +