def build_query(query_class, user_identifier=None): """ Builds a SAML query. """ query = query_class() query.version = SAMLVersion(SAMLVersion.VERSION_20) query.id = str(uuid4()) query.issueInstant = datetime.utcnow() query.issuer = Issuer() query.issuer.format = Issuer.X509_SUBJECT query.issuer.value = ISSUER query.subject = Subject() query.subject.nameID = NameID() query.subject.nameID.format = NAMEID_FORMAT action = Action() action.value = Action.READ_ACTION query.actions.append(action) if user_identifier: query.subject.nameID.value = user_identifier return query
def _createAuthzDecisionQuery( self, issuer="/O=Site A/CN=PEP", subject="https://openid.localhost/philip.kershaw", resource=None, action=Action.HTTP_GET_ACTION, actionNs=Action.GHPP_NS_URI): query = AuthzDecisionQuery() query.version = SAMLVersion(SAMLVersion.VERSION_20) query.id = str(uuid4()) query.issueInstant = datetime.utcnow() query.issuer = Issuer() query.issuer.format = Issuer.X509_SUBJECT query.issuer.value = issuer query.subject = Subject() query.subject.nameID = NameID() query.subject.nameID.format = ESGFSamlNamespaces.NAMEID_FORMAT query.subject.nameID.value = subject if resource is None: query.resource = self.__class__.RESOURCE_URI else: query.resource = resource query.actions.append(Action()) query.actions[0].namespace = actionNs query.actions[0].value = action return query
def attribute_query(environ, attribute_requests, subject_id, subject_id_format='urn:esg:openid'): """ Make a query for a set of attributes Returns a dictionary of values for each attribute in the query @param attribute_requests: SAML Attribute instances @param subject_id: The ID of the subject, e.g. openid @param subject_id_format: optional format of the ID, default is openid """ # Grab info from the environ saml_trusted_ca_dir = environ.get('saml_trusted_ca_dir', '') attr_service_uri = environ.get('attr_service_uri', '') client_binding = AttributeQuerySslSOAPBinding() client_binding.sslCACertDir = saml_trusted_ca_dir client_binding.clockSkewTolerance = 1 # 1 second tolerance # Make a new query object query = AttributeQuery() query.version = SAMLVersion(SAMLVersion.VERSION_20) query.id = str(uuid4()) query.issueInstant = datetime.utcnow() query.issuer = Issuer() query.issuer.format = Issuer.X509_SUBJECT query.issuer.value = '/O=STFC/OU=SPBU/CN=test' query.subject = Subject() query.subject.nameID = NameID() query.subject.nameID.format = subject_id_format query.subject.nameID.value = subject_id # Specify what attributes to query for for attribute_request in attribute_requests: query.attributes.append(attribute_request) try: response = client_binding.send(query, uri=attr_service_uri) attribute_results = {} assertion = next(iter(response.assertions or []), None) if assertion: statement = next(iter(assertion.attributeStatements or []), None) if statement: for attribute in statement.attributes: values = [] for attribute_value in attribute.attributeValues: values.append(attribute_value.value) attribute_results[attribute.name] = values return attribute_results except (RequestResponseError, Error) as e: logger.error("Error processing SOAP query for {0}: {1}".format(id, e))
def _createAuthzDecisionQuery(self, issuer): """Constructs an XACMLAuthzDecisionQuery. """ query = XACMLAuthzDecisionQuery() query.version = SAMLVersion(SAMLVersion.VERSION_20) query.id = str(uuid4()) query.issueInstant = datetime.utcnow() query.issuer = Issuer() query.issuer.format = Issuer.X509_SUBJECT query.issuer.value = issuer return query
def authzDecisionQuery(query, response): now = datetime.utcnow() response.issueInstant = now # Make up a request ID that this response is responding to response.inResponseTo = query.id response.id = str(uuid4()) response.version = SAMLVersion(SAMLVersion.VERSION_20) response.status = Status() response.status.statusCode = StatusCode() response.status.statusCode.value = StatusCode.SUCCESS_URI response.status.statusMessage = StatusMessage() response.status.statusMessage.value = \ "Response created successfully" assertion = Assertion() assertion.version = SAMLVersion(SAMLVersion.VERSION_20) assertion.id = str(uuid4()) assertion.issueInstant = now authzDecisionStatement = AuthzDecisionStatement() authzDecisionStatement.decision = DecisionType.PERMIT authzDecisionStatement.resource = \ TestAuthorisationServiceMiddleware.RESOURCE_URI authzDecisionStatement.actions.append(Action()) authzDecisionStatement.actions[-1].namespace = Action.GHPP_NS_URI authzDecisionStatement.actions[-1].value = Action.HTTP_GET_ACTION assertion.authzDecisionStatements.append(authzDecisionStatement) # Add a conditions statement for a validity of 8 hours assertion.conditions = Conditions() assertion.conditions.notBefore = now assertion.conditions.notOnOrAfter = now + timedelta(seconds=60 * 60 * 8) assertion.subject = Subject() assertion.subject.nameID = NameID() assertion.subject.nameID.format = query.subject.nameID.format assertion.subject.nameID.value = query.subject.nameID.value assertion.issuer = Issuer() assertion.issuer.format = Issuer.X509_SUBJECT assertion.issuer.value = \ TestAuthorisationServiceMiddleware.ISSUER_DN response.assertions.append(assertion) return response
def _createAttributeQuery( self, issuer="/O=Site A/CN=Authorisation Service", subject="https://openid.localhost/philip.kershaw"): attributeQuery = AttributeQuery() attributeQuery.version = SAMLVersion(SAMLVersion.VERSION_20) attributeQuery.id = str(uuid4()) attributeQuery.issueInstant = datetime.utcnow() attributeQuery.issuer = Issuer() attributeQuery.issuer.format = Issuer.X509_SUBJECT attributeQuery.issuer.value = issuer attributeQuery.subject = Subject() attributeQuery.subject.nameID = NameID() attributeQuery.subject.nameID.format = ESGFSamlNamespaces.NAMEID_FORMAT attributeQuery.subject.nameID.value = subject # special case handling for 'FirstName' attribute fnAttribute = Attribute() fnAttribute.name = ESGFSamlNamespaces.FIRSTNAME_ATTRNAME fnAttribute.nameFormat = "http://www.w3.org/2001/XMLSchema#string" fnAttribute.friendlyName = "FirstName" attributeQuery.attributes.append(fnAttribute) # special case handling for 'LastName' attribute lnAttribute = Attribute() lnAttribute.name = ESGFSamlNamespaces.LASTNAME_ATTRNAME lnAttribute.nameFormat = "http://www.w3.org/2001/XMLSchema#string" lnAttribute.friendlyName = "LastName" attributeQuery.attributes.append(lnAttribute) # special case handling for 'LastName' attribute emailAddressAttribute = Attribute() emailAddressAttribute.name = ESGFSamlNamespaces.EMAILADDRESS_ATTRNAME emailAddressAttribute.nameFormat = XMLConstants.XSD_NS+"#"+\ XSStringAttributeValue.TYPE_LOCAL_NAME emailAddressAttribute.friendlyName = "emailAddress" attributeQuery.attributes.append(emailAddressAttribute) return attributeQuery
def authzDecisionQuery(query, response): """Authorisation Decision Query interface called by the next middleware in the stack the SAML SOAP Query interface middleware instance (ndg.saml.saml2.binding.soap.server.wsgi.queryinterface.SOAPQueryInterfaceMiddleware) """ now = datetime.utcnow() response.issueInstant = now # Make up a request ID that this response is responding to response.inResponseTo = query.id response.id = str(uuid4()) response.version = SAMLVersion(SAMLVersion.VERSION_20) response.status = Status() response.status.statusCode = StatusCode() response.status.statusCode.value = StatusCode.SUCCESS_URI response.status.statusMessage = StatusMessage() response.status.statusMessage.value = \ "Response created successfully" assertion = Assertion() assertion.version = SAMLVersion(SAMLVersion.VERSION_20) assertion.id = str(uuid4()) assertion.issueInstant = now authzDecisionStatement = AuthzDecisionStatement() # Make some simple logic to simulate a full access policy if query.resource == self.__class__.RESOURCE_URI: if query.actions[0].value == Action.HTTP_GET_ACTION: authzDecisionStatement.decision = DecisionType.PERMIT else: authzDecisionStatement.decision = DecisionType.DENY else: authzDecisionStatement.decision = DecisionType.INDETERMINATE authzDecisionStatement.resource = query.resource authzDecisionStatement.actions.append(Action()) authzDecisionStatement.actions[-1].namespace = Action.GHPP_NS_URI authzDecisionStatement.actions[-1].value = Action.HTTP_GET_ACTION assertion.authzDecisionStatements.append(authzDecisionStatement) # Add a conditions statement for a validity of 8 hours assertion.conditions = Conditions() assertion.conditions.notBefore = now assertion.conditions.notOnOrAfter = now + timedelta(seconds=60 * 60 * 8) assertion.subject = Subject() assertion.subject.nameID = NameID() assertion.subject.nameID.format = query.subject.nameID.format assertion.subject.nameID.value = query.subject.nameID.value assertion.issuer = Issuer() assertion.issuer.format = Issuer.X509_SUBJECT assertion.issuer.value = \ TestAuthorisationServiceMiddleware.ISSUER_DN response.assertions.append(assertion) return response
def test05SamlAttributeQuery(self): if self.skipTests: return # Prepare a client query attributeQuery = AttributeQuery() attributeQuery.version = SAMLVersion(SAMLVersion.VERSION_20) attributeQuery.id = str(uuid4()) attributeQuery.issueInstant = datetime.utcnow() attributeQuery.issuer = Issuer() attributeQuery.issuer.format = Issuer.X509_SUBJECT attributeQuery.issuer.value = '/O=ESG/OU=NCAR/CN=Gateway' attributeQuery.subject = Subject() attributeQuery.subject.nameID = NameID() attributeQuery.subject.nameID.format = ESGFSamlNamespaces.NAMEID_FORMAT attributeQuery.subject.nameID.value = TestUserDatabase.OPENID_URI emailAddressAttribute = Attribute() emailAddressAttribute.name = ESGFSamlNamespaces.EMAILADDRESS_ATTRNAME emailAddressAttribute.nameFormat = "InvalidFormat" emailAddressAttribute.friendlyName = "EmailAddress" attributeQuery.attributes.append(emailAddressAttribute) authzAttribute = Attribute() authzAttribute.name = TestUserDatabase.ATTRIBUTE_NAMES[0] authzAttribute.nameFormat = XSStringAttributeValue.DEFAULT_FORMAT authzAttribute.friendlyName = "authz" attributeQuery.attributes.append(authzAttribute) # Add the response - the interface will populate with an assertion as # appropriate samlResponse = Response() samlResponse.issueInstant = datetime.utcnow() samlResponse.id = str(uuid4()) samlResponse.issuer = Issuer() # Initialise to success status but reset on error samlResponse.status = Status() samlResponse.status.statusCode = StatusCode() samlResponse.status.statusMessage = StatusMessage() samlResponse.status.statusCode.value = StatusCode.SUCCESS_URI # Nb. SAML 2.0 spec says issuer format must be omitted samlResponse.issuer.value = "CEDA" samlResponse.inResponseTo = attributeQuery.id # Set up the interface object # Define queries for SAML attribute names samlAttribute2SqlQuery = { ESGFSamlNamespaces.FIRSTNAME_ATTRNAME: self.__class__.SAML_FIRSTNAME_SQLQUERY, ESGFSamlNamespaces.LASTNAME_ATTRNAME: self.__class__.SAML_LASTNAME_SQLQUERY, ESGFSamlNamespaces.EMAILADDRESS_ATTRNAME: self.__class__.SAML_EMAILADDRESS_SQLQUERY, TestUserDatabase.ATTRIBUTE_NAMES[0]: self.__class__.SAML_ATTRIBUTES_SQLQUERY } attributeInterface = SQLAlchemyAttributeInterface( samlAttribute2SqlQuery=samlAttribute2SqlQuery) attributeInterface.connectionString = TestUserDatabase.DB_CONNECTION_STR attributeInterface.samlValidRequestorDNs = ( '/O=STFC/OU=CEDA/CN=AuthorisationService', '/O=ESG/OU=NCAR/CN=Gateway') attributeInterface.setProperties(samlAssertionLifetime=28800.) attributeInterface.samlSubjectSqlQuery = ( SQLAlchemyAttributeInterfaceTestCase.SAML_SUBJECT_SQLQUERY) # Make the query attributeInterface.getAttributes(attributeQuery, samlResponse)
def getAttributes(self, attributeQuery, response): '''Test Attribute Authority SAML Attribute Query interface''' userId = attributeQuery.subject.nameID.value requestedAttributeNames = [ attribute.name for attribute in attributeQuery.attributes ] if attributeQuery.issuer.format != Issuer.X509_SUBJECT: raise InvalidRequestorId( 'Requestor issuer format "%s" is invalid' % attributeQuery.issuerFormat.value) requestorId = X500DN.fromString(attributeQuery.issuer.value) if userId not in TestUserRoles.VALID_USER_IDS: raise UserIdNotKnown('Subject Id "%s" is not known to this ' 'authority' % userId) if requestorId not in TestUserRoles.VALID_REQUESTOR_IDS: raise InvalidRequestorId('Requestor identity "%s" is invalid' % requestorId) unknownAttrNames = [ attrName for attrName in requestedAttributeNames if attrName not in TestUserRoles.SAML_ATTRIBUTE_NAMES ] if len(unknownAttrNames) > 0: raise AttributeNotKnownError("Unknown attributes requested: %r" % unknownAttrNames) if requestorId == TestUserRoles.INSUFFICIENT_PRIVILEGES_REQUESTOR_ID: raise AttributeReleaseDenied("Attribute release denied for the " 'requestor "%s"' % requestorId) # Create a new assertion to hold the attributes to be returned assertion = Assertion() assertion.version = SAMLVersion(SAMLVersion.VERSION_20) assertion.id = str(uuid4()) assertion.issueInstant = response.issueInstant assertion.issuer = Issuer() assertion.issuer.value = response.issuer.value assertion.issuer.format = Issuer.X509_SUBJECT assertion.conditions = Conditions() assertion.conditions.notBefore = assertion.issueInstant assertion.conditions.notOnOrAfter = assertion.conditions.notBefore + \ timedelta(seconds=TestUserRoles.SAML_ASSERTION_LIFETIME) assertion.subject = Subject() assertion.subject.nameID = NameID() assertion.subject.nameID.format = attributeQuery.subject.nameID.format assertion.subject.nameID.value = attributeQuery.subject.nameID.value attributeStatement = AttributeStatement() # Add test set of attributes for name in requestedAttributeNames: attributeFound = False for attribute in TestUserRoles.SAML_ATTRIBUTES: if attribute.name == name: attributeFound = True break if attributeFound: attributeStatement.attributes.append(attribute) else: raise AttributeNotKnownError( "Unknown attribute requested: %s" % name) assertion.attributeStatements.append(attributeStatement) response.assertions.append(assertion)
def getAttributes(self, attributeQuery, response): """Attribute Authority SAML AttributeQuery @type attributeQuery: saml.saml2.core.AttributeQuery @param userId: query containing requested attributes @type response: saml.saml2.core.Response @param response: add an assertion with the list of attributes for the given subject ID in the query or set an error Status code and message @raise AttributeInterfaceError: an error occured requesting attributes @raise AttributeReleaseDeniedError: Requestor was denied release of the requested attributes @raise AttributeNotKnownError: Requested attribute names are not known to this authority """ userId = attributeQuery.subject.nameID.value requestedAttributeNames = [ attribute.name for attribute in attributeQuery.attributes ] requestorDN = X500DN.from_string(attributeQuery.issuer.value) if not self._queryDbForSamlSubject(userId): raise UserIdNotKnown('Subject Id "%s" is not known to this ' 'authority' % userId) if (len(self.samlValidRequestorDNs) > 0 and requestorDN not in self.samlValidRequestorDNs): raise InvalidRequestorId('Requester identity "%s" is invalid' % requestorDN) unknownAttrNames = [ attrName for attrName in requestedAttributeNames if attrName not in self.__samlAttribute2SqlQuery ] if len(unknownAttrNames) > 0: raise AttributeNotKnownError("Unknown attributes requested: %r" % unknownAttrNames) # Create a new assertion to hold the attributes to be returned assertion = Assertion() assertion.version = SAMLVersion(SAMLVersion.VERSION_20) assertion.id = str(uuid4()) assertion.issueInstant = response.issueInstant # Assumes SAML response issuer details as set by - # ndg.security.server.wsgi.saml.SOAPQueryInterfaceMiddleware assertion.issuer = Issuer() assertion.issuer.value = response.issuer.value if response.issuer.format: assertion.issuer.format = response.issuer.format assertion.conditions = Conditions() assertion.conditions.notBefore = assertion.issueInstant assertion.conditions.notOnOrAfter = (assertion.conditions.notBefore + self.samlAssertionLifetime) assertion.subject = Subject() assertion.subject.nameID = NameID() assertion.subject.nameID.format = attributeQuery.subject.nameID.format assertion.subject.nameID.value = attributeQuery.subject.nameID.value attributeStatement = AttributeStatement() # Query the database for the requested attributes and return them # mapped to their attribute names as specified by the attributeNames # property for requestedAttribute in attributeQuery.attributes: attributeVals = self._queryDbForSamlAttributes( requestedAttribute.name, userId) # Make a new SAML attribute object to hold the values obtained attribute = Attribute() attribute.name = requestedAttribute.name attribute.nameFormat = requestedAttribute.nameFormat if requestedAttribute.friendlyName is not None: attribute.friendlyName = requestedAttribute.friendlyName # Call specific conversion utility to convert the retrieved field # to the correct SAML attribute value type try: field2SamlAttributeVal = self.__samlAttribute2SqlQuery[ requestedAttribute.name][-1] except (IndexError, TypeError) as e: raise AttributeInterfaceConfigError( 'Bad format for SAML ' 'attribute to SQL query ' 'look-up for attribute ' 'name %r: %s' % (requestedAttribute.name, e)) for val in attributeVals: attributeValue = field2SamlAttributeVal(val) attribute.attributeValues.append(attributeValue) attributeStatement.attributes.append(attribute) assertion.attributeStatements.append(attributeStatement) response.assertions.append(assertion)
def get_authz_decision(environ, url, remote_user): saml_trusted_ca_dir = environ.get('saml_trusted_ca_dir', '') authz_service_uri = environ.get('authz_service_uri', '') client_binding = AuthzDecisionQuerySslSOAPBinding() client_binding.sslCACertDir = saml_trusted_ca_dir client_binding.clockSkewTolerance = 1 # 1 second tolerance # Make a new query object query = AuthzDecisionQueryFactory.create() # Copy constant settings. These constants were set at # initialisation query.subject = Subject() query.subject.nameID = NameID() query.subject.nameID.format = 'urn:esg:openid' query.issuer = Issuer() query.issuer.format = Issuer.X509_SUBJECT query.issuer.value = 'O=NDG, OU=Security, CN=localhost' # Set dynamic settings particular to this individual request query.subject.nameID.value = remote_user query.resource = url try: saml_authz_response = client_binding.send(query, uri=authz_service_uri) except (UrlLib2SOAPClientError, URLError) as e: import traceback if isinstance(e, UrlLib2SOAPClientError): logger.error( "Error, HTTP %s response from authorisation " "service %r requesting access to %r: %s", e.urllib2Response.code, authz_service_uri, url, traceback.format_exc()) else: logger.error( "Error, calling authorisation service %r " "requesting access to %r: %s", authz_service_uri, url, traceback.format_exc()) assertions = saml_authz_response.assertions # Set HTTP 403 Forbidden response if any of the decisions returned are # deny or indeterminate status fail_decisions = ( DecisionType.DENY, #@UndefinedVariable DecisionType.INDETERMINATE) #@UndefinedVariable # Review decision statement(s) in assertions and enforce the decision assertion = None for assertion in assertions: for authz_decision_statement in assertion.authzDecisionStatements: assertion = authz_decision_statement.decision.value if assertion in fail_decisions: break if assertion is None: logger.error("No assertions set in authorisation decision response " "from {0}".format(authz_service_uri)) return assertion