Merge branch 'master' of ssh://git.planet-lab.org/git/sfa
[sfa.git] / sfa / trust / credential.py
index 3480a72..a18019d 100644 (file)
@@ -180,7 +180,11 @@ class Signature(object):
         self.gid = gid\r
 \r
     def decode(self):\r
-        doc = parseString(self.xml)\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
@@ -205,17 +209,19 @@ class Signature(object):
 # 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):\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() == caller_hrn:\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
@@ -269,7 +275,16 @@ class Credential(object):
     def get_subject(self):\r
         if not self.gidObject:\r
             self.decode()\r
-        return self.gidObject.get_subject()   \r
+        return self.gidObject.get_printable_subject()\r
+\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
@@ -672,13 +687,19 @@ class Credential(object):
 \r
         # Is this a signed-cred or just a cred?\r
         if len(signed_cred) > 0:\r
-            cred = signed_cred[0].getElementsByTagName("credential")[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
-            cred = doc.getElementsByTagName("credential")[0]\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
@@ -765,7 +786,7 @@ class Credential(object):
                 xmlschema = etree.XMLSchema(schema_doc)\r
                 if not xmlschema.validate(tree):\r
                     error = xmlschema.error_log.last_error\r
-                    message = "%s (line %s)" % (error.message, error.line)\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
@@ -798,7 +819,7 @@ class Credential(object):
         \r
         # make sure it is not expired\r
         if self.get_expiration() < datetime.datetime.utcnow():\r
-            raise CredentialNotVerifiable("Credential expired at %s" % self.expiration.isoformat())\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
@@ -838,7 +859,7 @@ class Credential(object):
                     mstart = mstart + 4\r
                     mend = verified.find('\\', mstart)\r
                     msg = verified[mstart:mend]\r
-                raise CredentialNotVerifiable("xmlsec1 error verifying cred using Signature ID %s: %s %s" % (ref, msg, verified.strip()))\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
@@ -909,7 +930,13 @@ class Credential(object):
         # 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
-        root_cred_signer.verify_chain(trusted_gids)\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
@@ -944,23 +971,23 @@ class Credential(object):
         # 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 " % self.parent.get_refid()) + \r
-                self.parent.get_privileges().save_to_string() + (" not superset of delegated cred ref %s rights " % self.get_refid()) +\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("Target gid not equal between parent and child")\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 expires after parent")\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 not signed by parent caller")\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