def isValidCredential(self, assertion): """Validate SAML assertion time validity""" utcNow = datetime.utcnow() if utcNow < assertion.conditions.notBefore - self.clockSkewTolerance: msg = ('The current clock time [%s] is before the SAML Attribute ' 'Response assertion conditions not before time [%s] ' '(with clock skew tolerance = %s)' % (SAMLDateTime.toString(utcNow), assertion.conditions.notBefore, self.clockSkewTolerance)) log.warning(msg) return False if (utcNow >= assertion.conditions.notOnOrAfter + self.clockSkewTolerance): msg = ('The current clock time [%s] is on or after the SAML ' 'Attribute Response assertion conditions not on or after ' 'time [%s] (with clock skew tolerance = %s)' % (SAMLDateTime.toString(utcNow), assertion.conditions.notOnOrAfter, self.clockSkewTolerance)) log.warning(msg) return False return True
def test03ParseResponse(self): utcNow = datetime.utcnow() respDict = { 'issueInstant': SAMLDateTime.toString(utcNow), 'assertionIssueInstant': SAMLDateTime.toString(utcNow), 'notBefore': SAMLDateTime.toString(utcNow), 'notOnOrAfter': SAMLDateTime.toString(utcNow + timedelta( seconds=60*60*8)) } responseStr = self.__class__.RESPONSE % respDict response = self._parseResponse(responseStr) self.assert_(response)
def _verifyQueryTimeConditions(self, query, response): """Checking incoming query issue instant :type query: saml.saml2.core.SubjectQuery :param query: SAML subject query to be checked :type: saml.saml2.core.Response :param: SAML Response :raise QueryIssueInstantInvalid: for invalid issue instant """ if not self.verifyTimeConditions: log.debug("Skipping verification of SAML query time conditions") return utcNow = datetime.utcnow() nowPlusSkew = utcNow + self.clockSkewTolerance if query.issueInstant > nowPlusSkew: msg = ('SAML Attribute Query issueInstant [%s] is after ' 'the clock time [%s] (skewed +%s)' % (query.issueInstant, SAMLDateTime.toString(nowPlusSkew), self.clockSkewTolerance)) samlRespError = QueryIssueInstantInvalid(msg) samlRespError.response = response raise samlRespError
def fromXML(cls, elem): """Parse ElementTree element into a SAML XACMLAuthzDecisionQuery object @type elem: ElementTree.Element @param elem: XML element containing the AuthzDecisionQuery @rtype: saml.saml2.core.AuthzDecisionQuery @return: AuthzDecisionQuery object """ if not ElementTree.iselement(elem): raise TypeError("Expecting %r input type for parsing; got %r" % (ElementTree.Element, elem)) if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME: raise XMLTypeParseError("No \"%s\" element found" % cls.DEFAULT_ELEMENT_LOCAL_NAME) # Unpack attributes from top-level element attributeValues = [] for attributeName in (cls.VERSION_ATTRIB_NAME, cls.ISSUE_INSTANT_ATTRIB_NAME, cls.ID_ATTRIB_NAME): attributeValue = elem.attrib.get(attributeName) if attributeValue is None: raise XMLTypeParseError('No "%s" attribute found in "%s" ' 'element' % (attributeName, cls.DEFAULT_ELEMENT_LOCAL_NAME)) attributeValues.append(attributeValue) authzDecisionQuery = XACMLAuthzDecisionQuery() authzDecisionQuery.version = SAMLVersion(attributeValues[0]) if authzDecisionQuery.version != SAMLVersion.VERSION_20: raise NotImplementedError("Parsing for %r is implemented for " "SAML version %s only; version %s is " "not supported" % (cls, SAMLVersion(SAMLVersion.VERSION_20), SAMLVersion(authzDecisionQuery.version))) authzDecisionQuery.issueInstant = SAMLDateTime.fromString( attributeValues[1]) authzDecisionQuery.id = attributeValues[2] for childElem in elem: localName = QName.getLocalPart(childElem.tag) if localName == Issuer.DEFAULT_ELEMENT_LOCAL_NAME: # Parse Issuer authzDecisionQuery.issuer = IssuerElementTree.fromXML(childElem) elif localName == Request.ELEMENT_LOCAL_NAME: # Create XACML context request from Request element. authzDecisionQuery.xacmlContextRequest = \ RequestElementTree.fromXML(childElem) else: raise XMLTypeParseError("Unrecognised XACMLAuthzDecisionQuery child " "element \"%s\"" % localName) return authzDecisionQuery
def test06NotBeforeConditionInvalid(self): utcNow = datetime.utcnow() respDict = { 'issueInstant': SAMLDateTime.toString(utcNow), 'assertionIssueInstant': SAMLDateTime.toString(utcNow), 'notBefore': SAMLDateTime.toString(utcNow + timedelta(seconds=1)), 'notOnOrAfter': SAMLDateTime.toString(utcNow + timedelta( seconds=60*60*8)) } responseStr = self.__class__.RESPONSE % respDict response = self._parseResponse(responseStr) binding = SubjectQuerySOAPBinding() try: binding._verifyTimeConditions(response) self.fail("Expecting issue instant timestamp error") except AssertionConditionNotBeforeInvalid, e: print("PASSED: %s" % e)
def test17SAMLDatetime(self): # Test parsing of Datetimes following # http://www.w3.org/TR/xmlschema-2/#dateTime # No seconds fraction self.assertTrue(SAMLDateTime.fromString('2010-10-20T14:49:50Z')) self.assertRaises(TypeError, SAMLDateTime.fromString, None)
def test04AssertionConditionExpired(self): # issued 9 hours ago issueInstant = datetime.utcnow() - timedelta(seconds=60*60*9) respDict = { 'issueInstant': SAMLDateTime.toString(issueInstant), 'assertionIssueInstant': SAMLDateTime.toString(issueInstant), 'notBefore': SAMLDateTime.toString(issueInstant), # It lasts for 8 hours so it's expired by one hour 'notOnOrAfter': SAMLDateTime.toString(issueInstant + timedelta( seconds=60*60*8)) } responseStr = self.__class__.RESPONSE % respDict response = self._parseResponse(responseStr) binding = SubjectQuerySOAPBinding() try: binding._verifyTimeConditions(response) self.fail("Expecting not on or after timestamp error") except AssertionConditionNotOnOrAfterInvalid, e: print("PASSED: %s" % e)
def _createAssertion(self, timeNow=None, validityDuration=60*60*8, issuerName=BaseTestCase.SITEA_SAML_ISSUER_NAME): if timeNow is None: timeNow = datetime.utcnow() timeExpires = timeNow + timedelta(seconds=validityDuration) assertionStr = Template( self.__class__.ASSERTION_STR).substitute( dict( issuerName=issuerName, timeNow=SAMLDateTime.toString(timeNow), timeExpires=SAMLDateTime.toString(timeExpires) ) ) assertionStream = StringIO() assertionStream.write(assertionStr) assertionStream.seek(0) assertionElem = ElementTree.parse(assertionStream).getroot() return AssertionElementTree.fromXML(assertionElem)
def test07ClockSkewCorrectedAssertionIssueInstantInvalid(self): utcNow = datetime.utcnow() respDict = { 'issueInstant': SAMLDateTime.toString(utcNow), 'assertionIssueInstant': SAMLDateTime.toString(utcNow + timedelta( seconds=1)), 'notBefore': SAMLDateTime.toString(utcNow), 'notOnOrAfter': SAMLDateTime.toString(utcNow + timedelta( seconds=60*60*8)) } responseStr = self.__class__.RESPONSE % respDict response = self._parseResponse(responseStr) binding = SubjectQuerySOAPBinding() # Set a skew to correct the error binding.clockSkewTolerance = 1 try: binding._verifyTimeConditions(response) except AssertionIssueInstantInvalid, e: self.fail("issue instant timestamp error should be corrected for")
def test08ClockSkewCorrectedAssertionConditionExpired(self): # Issued 9 hours ago issueInstant = datetime.utcnow() - timedelta(seconds=60*60*9) respDict = { 'issueInstant': SAMLDateTime.toString(issueInstant), 'assertionIssueInstant': SAMLDateTime.toString(issueInstant), 'notBefore': SAMLDateTime.toString(issueInstant), # Assertion lasts 8 hours so it has expired by one hour 'notOnOrAfter': SAMLDateTime.toString(issueInstant + timedelta( seconds=60*60*8)) } responseStr = self.__class__.RESPONSE % respDict response = self._parseResponse(responseStr) binding = SubjectQuerySOAPBinding() # Set a skew of over one hour to correct for the assertion expiry binding.clockSkewTolerance = 60*60 + 3 try: binding._verifyTimeConditions(response) except AssertionConditionNotOnOrAfterInvalid: self.fail("Not on or after timestamp error should be corrected for")
def toXML(cls, xacmlAuthzDecisionQuery): """Create an XML representation of the input SAML Authorization Decision Query object @type xacmlAuthzDecisionQuery: saml.saml2.core.AuthzDecisionQuery @param xacmlAuthzDecisionQuery: SAML Authorization Decision Query @rtype: ElementTree.Element @return: Attribute Query as ElementTree XML element """ if not isinstance(xacmlAuthzDecisionQuery, XACMLAuthzDecisionQuery): raise TypeError("Expecting %r class got %r" % (XACMLAuthzDecisionQuery, type(xacmlAuthzDecisionQuery))) if not xacmlAuthzDecisionQuery.xacmlContextRequest: raise AttributeError("No xacmlContextRequest has been set for the " "XACMLAuthzDecisionQuery") issueInstant = SAMLDateTime.toString(xacmlAuthzDecisionQuery.issueInstant) attrib = { cls.ID_ATTRIB_NAME: xacmlAuthzDecisionQuery.id, cls.ISSUE_INSTANT_ATTRIB_NAME: issueInstant, # Nb. Version is a SAMLVersion instance and requires explicit cast cls.VERSION_ATTRIB_NAME: str(xacmlAuthzDecisionQuery.version), } tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME)) elem = etree.makeEtreeElement(tag, cls.DEFAULT_ELEMENT_NAME.prefix, cls.DEFAULT_ELEMENT_NAME.namespaceURI, **attrib) issuerElem = IssuerElementTree.toXML(xacmlAuthzDecisionQuery.issuer) elem.append(issuerElem) requestElem = RequestElementTree.toXML( xacmlAuthzDecisionQuery.xacmlContextRequest) elem.append(requestElem) return elem
def _verifyTimeConditions(self, response): """Verify time conditions set in a response :param response: SAML Response returned from remote service :type response: ndg.saml.saml2.core.Response :raise RequestResponseError: if a timestamp is invalid """ if not self.verifyTimeConditions: log.debug("Skipping verification of SAML Response time conditions") utcNow = datetime.utcnow() nowMinusSkew = utcNow - self.clockSkewTolerance nowPlusSkew = utcNow + self.clockSkewTolerance if response.issueInstant > nowPlusSkew: msg = ('SAML Attribute Response issueInstant [%s] is after ' 'the clock time [%s] (skewed +%s)' % (response.issueInstant, SAMLDateTime.toString(nowPlusSkew), self.clockSkewTolerance)) samlRespError = ResponseIssueInstantInvalid(msg) samlRespError.response = response raise samlRespError for assertion in response.assertions: if assertion.issueInstant is None: samlRespError = AssertionIssueInstantInvalid("No issueInstant " "set in response " "assertion") samlRespError.response = response raise samlRespError elif nowPlusSkew < assertion.issueInstant: msg = ('The clock time [%s] (skewed +%s) is before the ' 'SAML Attribute Response assertion issue instant [%s]' % (SAMLDateTime.toString(utcNow), self.clockSkewTolerance, assertion.issueInstant)) samlRespError = AssertionIssueInstantInvalid(msg) samlRespError.response = response raise samlRespError if assertion.conditions is not None: if nowPlusSkew < assertion.conditions.notBefore: msg = ('The clock time [%s] (skewed +%s) is before the ' 'SAML Attribute Response assertion conditions not ' 'before time [%s]' % (SAMLDateTime.toString(utcNow), self.clockSkewTolerance, assertion.conditions.notBefore)) samlRespError = AssertionConditionNotBeforeInvalid(msg) samlRespError.response = response raise samlRespError if nowMinusSkew >= assertion.conditions.notOnOrAfter: msg = ('The clock time [%s] (skewed -%s) is on or after ' 'the SAML Attribute Response assertion conditions ' 'not on or after time [%s]' % (SAMLDateTime.toString(utcNow), self.clockSkewTolerance, assertion.conditions.notOnOrAfter)) samlRespError = AssertionConditionNotOnOrAfterInvalid(msg) samlRespError.response = response raise samlRespError