--- /dev/null
+#----------------------------------------------------------------------\r
+# Copyright (c) 2014 Raytheon BBN Technologies\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
+from sfa.trust.credential import Credential, append_sub\r
+from sfa.util.sfalogging import logger\r
+\r
+from StringIO import StringIO\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
+# This module defines a subtype of sfa.trust,credential.Credential\r
+# called an ABACCredential. An ABAC credential is a signed statement\r
+# asserting a role representing the relationship between a subject and target\r
+# or between a subject and a class of targets (all those satisfying a role).\r
+#\r
+# An ABAC credential is like a normal SFA credential in that it has\r
+# a validated signature block and is checked for expiration. \r
+# It does not, however, have 'privileges'. Rather it contains a 'head' and\r
+# list of 'tails' of elements, each of which represents a principal and\r
+# role.\r
+\r
+# A special case of an ABAC credential is a speaks_for credential. Such\r
+# a credential is simply an ABAC credential in form, but has a single \r
+# tail and fixed role 'speaks_for'. In ABAC notation, it asserts\r
+# AGENT.speaks_for(AGENT)<-CLIENT, or "AGENT asserts that CLIENT may speak\r
+# for AGENT". The AGENT in this case is the head and the CLIENT is the\r
+# tail and 'speaks_for_AGENT' is the role on the head. These speaks-for\r
+# Credentials are used to allow a tool to 'speak as' itself but be recognized\r
+# as speaking for an individual and be authorized to the rights of that\r
+# individual and not to the rights of the tool itself.\r
+\r
+# For more detail on the semantics and syntax and expected usage patterns\r
+# of ABAC credentials, see http://groups.geni.net/geni/wiki/TIEDABACCredential.\r
+\r
+\r
+# An ABAC element contains a principal (keyid and optional mnemonic)\r
+# and optional role and linking_role element\r
+class ABACElement:\r
+ def __init__(self, principal_keyid, principal_mnemonic=None, \\r
+ role=None, linking_role=None):\r
+ self._principal_keyid = principal_keyid\r
+ self._principal_mnemonic = principal_mnemonic\r
+ self._role = role\r
+ self._linking_role = linking_role\r
+\r
+ def get_principal_keyid(self): return self._principal_keyid\r
+ def get_principal_mnemonic(self): return self._principal_mnemonic\r
+ def get_role(self): return self._role\r
+ def get_linking_role(self): return self._linking_role\r
+\r
+ def __str__(self):\r
+ ret = self._principal_keyid\r
+ if self._principal_mnemonic:\r
+ ret = "%s (%s)" % (self._principal_mnemonic, self._principal_keyid)\r
+ if self._linking_role:\r
+ ret += ".%s" % self._linking_role\r
+ if self._role:\r
+ ret += ".%s" % self._role\r
+ return ret\r
+\r
+# Subclass of Credential for handling ABAC credentials\r
+# They have a different cred_type (geni_abac vs. geni_sfa)\r
+# and they have a head and tail and role (as opposed to privileges)\r
+class ABACCredential(Credential):\r
+\r
+ ABAC_CREDENTIAL_TYPE = 'geni_abac'\r
+\r
+ def __init__(self, create=False, subject=None, \r
+ string=None, filename=None):\r
+ self.head = None # An ABACElemenet\r
+ self.tails = [] # List of ABACElements\r
+ super(ABACCredential, self).__init__(create=create, \r
+ subject=subject, \r
+ string=string, \r
+ filename=filename)\r
+ self.cred_type = ABACCredential.ABAC_CREDENTIAL_TYPE\r
+\r
+ def get_head(self) : \r
+ if not self.head: \r
+ self.decode()\r
+ return self.head\r
+\r
+ def get_tails(self) : \r
+ if len(self.tails) == 0:\r
+ self.decode()\r
+ return self.tails\r
+\r
+ def decode(self):\r
+ super(ABACCredential, self).decode()\r
+ # Pull out the ABAC-specific info\r
+ doc = parseString(self.xml)\r
+ rt0s = doc.getElementsByTagName('rt0')\r
+ if len(rt0s) != 1:\r
+ raise CredentialNotVerifiable("ABAC credential had no rt0 element")\r
+ rt0_root = rt0s[0]\r
+ heads = self._get_abac_elements(rt0_root, 'head')\r
+ if len(heads) != 1:\r
+ raise CredentialNotVerifiable("ABAC credential should have exactly 1 head element, had %d" % len(heads))\r
+\r
+ self.head = heads[0]\r
+ self.tails = self._get_abac_elements(rt0_root, 'tail')\r
+\r
+ def _get_abac_elements(self, root, label):\r
+ abac_elements = []\r
+ elements = root.getElementsByTagName(label)\r
+ for elt in elements:\r
+ keyids = elt.getElementsByTagName('keyid')\r
+ if len(keyids) != 1:\r
+ raise CredentialNotVerifiable("ABAC credential element '%s' should have exactly 1 keyid, had %d." % (label, len(keyids)))\r
+ keyid_elt = keyids[0]\r
+ keyid = keyid_elt.childNodes[0].nodeValue.strip()\r
+\r
+ mnemonic = None\r
+ mnemonic_elts = elt.getElementsByTagName('mnemonic')\r
+ if len(mnemonic_elts) > 0:\r
+ mnemonic = mnemonic_elts[0].childNodes[0].nodeValue.strip()\r
+\r
+ role = None\r
+ role_elts = elt.getElementsByTagName('role')\r
+ if len(role_elts) > 0:\r
+ role = role_elts[0].childNodes[0].nodeValue.strip()\r
+\r
+ linking_role = None\r
+ linking_role_elts = elt.getElementsByTagName('linking_role')\r
+ if len(linking_role_elts) > 0:\r
+ linking_role = linking_role_elts[0].childNodes[0].nodeValue.strip()\r
+\r
+ abac_element = ABACElement(keyid, mnemonic, role, linking_role)\r
+ abac_elements.append(abac_element)\r
+\r
+ return abac_elements\r
+\r
+ def dump_string(self, dump_parents=False, show_xml=False):\r
+ result = "ABAC Credential\n"\r
+ filename=self.get_filename()\r
+ if filename: result += "Filename %s\n"%filename\r
+ if self.expiration:\r
+ result += "\texpiration: %s \n" % self.expiration.isoformat()\r
+\r
+ result += "\tHead: %s\n" % self.get_head() \r
+ for tail in self.get_tails():\r
+ result += "\tTail: %s\n" % tail\r
+ if self.get_signature():\r
+ result += " gidIssuer:\n"\r
+ result += self.get_signature().get_issuer_gid().dump_string(8, dump_parents)\r
+ if show_xml and HAVELXML:\r
+ try:\r
+ tree = etree.parse(StringIO(self.xml))\r
+ aside = etree.tostring(tree, pretty_print=True)\r
+ result += "\nXML:\n\n"\r
+ result += aside\r
+ result += "\nEnd XML\n"\r
+ except:\r
+ import traceback\r
+ print "exc. Credential.dump_string / XML"\r
+ traceback.print_exc()\r
+ return result\r
+\r
+ # sounds like this should be __repr__ instead ??\r
+ # Produce the ABAC assertion. Something like [ABAC cred: Me.role<-You] or similar\r
+ def get_summary_tostring(self):\r
+ result = "[ABAC cred: " + str(self.get_head())\r
+ for tail in self.get_tails():\r
+ result += "<-%s" % str(tail)\r
+ result += "]"\r
+ return result\r
+\r
+ def createABACElement(self, doc, tagName, abacObj):\r
+ kid = abacObj.get_principal_keyid()\r
+ mnem = abacObj.get_principal_mnemonic() # may be None\r
+ role = abacObj.get_role() # may be None\r
+ link = abacObj.get_linking_role() # may be None\r
+ ele = doc.createElement(tagName)\r
+ prin = doc.createElement('ABACprincipal')\r
+ ele.appendChild(prin)\r
+ append_sub(doc, prin, "keyid", kid)\r
+ if mnem:\r
+ append_sub(doc, prin, "mnemonic", mnem)\r
+ if role:\r
+ append_sub(doc, ele, "role", role)\r
+ if link:\r
+ append_sub(doc, ele, "linking_role", link)\r
+ return ele\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.geni.net/resources/credential/2/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", "abac")\r
+\r
+ # Stub fields\r
+ append_sub(doc, cred, "serial", "8")\r
+ append_sub(doc, cred, "owner_gid", '')\r
+ append_sub(doc, cred, "owner_urn", '')\r
+ append_sub(doc, cred, "target_gid", '')\r
+ append_sub(doc, cred, "target_urn", '')\r
+ append_sub(doc, cred, "uuid", "")\r
+\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
+ if self.expiration.tzinfo is not None and self.expiration.tzinfo.utcoffset(self.expiration) is not None:\r
+ # TZ aware. Make sure it is UTC\r
+ self.expiration = self.expiration.astimezone(tz.tzutc())\r
+ append_sub(doc, cred, "expires", self.expiration.strftime('%Y-%m-%dT%H:%M:%SZ')) # RFC3339\r
+\r
+ abac = doc.createElement("abac")\r
+ rt0 = doc.createElement("rt0")\r
+ abac.appendChild(rt0)\r
+ cred.appendChild(abac)\r
+ append_sub(doc, rt0, "version", "1.1")\r
+ head = self.createABACElement(doc, "head", self.get_head())\r
+ rt0.appendChild(head)\r
+ for tail in self.get_tails():\r
+ tailEle = self.createABACElement(doc, "tail", tail)\r
+ rt0.appendChild(tailEle)\r
+\r
+ # Create the <signatures> tag\r
+ signatures = doc.createElement("signatures")\r
+ signed_cred.appendChild(signatures)\r
+\r
+ # Get the finished product\r
+ self.xml = doc.toxml("utf-8")\r