def createAttributes(self): """Create SAML Attributes for use in an Assertion or AttributeQuery""" attributes = [] if self.firstName is not None: # special case handling for 'FirstName' attribute fnAttribute = Attribute() fnAttribute.name = "urn:esg:first:name" fnAttribute.nameFormat = SAMLUtil.XSSTRING_NS fnAttribute.friendlyName = "FirstName" firstName = XSStringAttributeValue() firstName.value = self.firstName fnAttribute.attributeValues.append(firstName) attributes.append(fnAttribute) if self.lastName is not None: # special case handling for 'LastName' attribute lnAttribute = Attribute() lnAttribute.name = "urn:esg:last:name" lnAttribute.nameFormat = SAMLUtil.XSSTRING_NS lnAttribute.friendlyName = "LastName" lastName = XSStringAttributeValue() lastName.value = self.lastName lnAttribute.attributeValues.append(lastName) attributes.append(lnAttribute) if self.emailAddress is not None: # special case handling for 'LastName' attribute emailAddressAttribute = Attribute() emailAddressAttribute.name = "urn:esg:email:address" emailAddressAttribute.nameFormat = SAMLConstants.XSD_NS+"#"+\ XSStringAttributeValue.TYPE_LOCAL_NAME emailAddressAttribute.friendlyName = "emailAddress" emailAddress = XSStringAttributeValue() emailAddress.value = self.emailAddress emailAddressAttribute.attributeValues.append(emailAddress) attributes.append(emailAddressAttribute) for name, value in self.__miscAttrList: attribute = Attribute() attribute.name = name attribute.nameFormat = SAMLUtil.XSSTRING_NS stringAttributeValue = XSStringAttributeValue() stringAttributeValue.value = value attribute.attributeValues.append(stringAttributeValue) attributes.append(attribute) return attributes
def get_user_details(environ, openid): """ Return a dictionary containing a user's first name, last name and email address """ attribute_names = [('first_name', ATTRIBUTE_FIRST_NAME), ('last_name', ATTRIBUTE_LAST_NAME), ('email_address', ATTRIBUTE_EMAIL_ADDRESS)] attributes = [] for _, name in attribute_names: attribute = Attribute() attribute.name = name attribute.nameFormat = 'http://www.w3.org/2001/XMLSchema#string' attributes.append(attribute) result = attribute_query(environ, attributes, openid) if result: user_details = {} for attribute_name in attribute_names: index, name = attribute_name values = result.get(name) value = values if values and len(values) == 1: value = values[0] user_details[index] = value return user_details
def test02SendQuery(self): query_binding = AttributeQuerySslSOAPBinding() attribute_query = AttributeQueryFactory.create() attribute_query.subject.nameID.format = self.__class__.SUBJECT_FORMAT attribute_query.subject.nameID.value = self.__class__.SUBJECT attribute_query.issuerName = '/O=Site A/CN=Authorisation Service' attribute = Attribute() attribute.name = 'urn:ndg:saml:emailaddress' attribute.friendlyName = 'emailAddress' attribute.nameFormat = 'http://www.w3.org/2001/XMLSchema' attribute_query.attributes.append(attribute) query_binding.clockSkewTolerance = 2. query_binding.sslCACertDir = self.__class__.CLIENT_CACERT_DIR query_binding.sslCertFilePath = self.__class__.CLIENT_CERT_FILEPATH query_binding.sslPriKeyFilePath = self.__class__.CLIENT_PRIKEY_FILEPATH query_binding.sslValidDNs = self.__class__.VALID_DNS response = query_binding.send(attribute_query, uri=self.__class__.SERVICE_URI) # Convert back to ElementTree instance read for string output samlResponseElem = ResponseElementTree.toXML(response) print("SAML Response ...") print(ElementTree.tostring(samlResponseElem)) print("Pretty print SAML Response ...") print(prettyPrint(samlResponseElem)) self.assert_(response.status.statusCode.value==StatusCode.SUCCESS_URI)
def test03InvalidAttributesRequested(self): attributeQuery = self._createAttributeQuery() # Add an unsupported Attribute name attribute = Attribute() attribute.name = "urn:my:attribute" attribute.nameFormat = XMLConstants.XSD_NS+"#"+\ XSStringAttributeValue.TYPE_LOCAL_NAME attribute.friendlyName = "myAttribute" attributeQuery.attributes.append(attribute) request = self._makeRequest(attributeQuery=attributeQuery) header = { 'soapAction': "http://www.oasis-open.org/committees/security", 'Content-length': str(len(request)), 'Content-type': 'text/xml' } response = self.app.post('/attributeauthority/saml', params=request, headers=header, status=200) print("Response status=%d" % response.status) samlResponse = self._getSAMLResponse(response.body) self.assert_(samlResponse.status.statusCode.value == \ StatusCode.INVALID_ATTR_NAME_VALUE_URI)
def test03InvalidAttributesRequested(self): attributeQuery = self._createAttributeQuery() # Add an unsupported Attribute name attribute = Attribute() attribute.name = "urn:my:attribute" attribute.nameFormat = XMLConstants.XSD_NS+"#"+\ XSStringAttributeValue.TYPE_LOCAL_NAME attribute.friendlyName = "myAttribute" attributeQuery.attributes.append(attribute) request = self._makeRequest(attributeQuery=attributeQuery) header = { 'soapAction': "http://www.oasis-open.org/committees/security", 'Content-length': str(len(request)), 'Content-type': 'text/xml' } response = self.app.post(self.__class__.SERVICE_URI, params=request, headers=header, status=200) print(("Response status=%d" % response.status)) samlResponse = self._getSAMLResponse(response.body) self.assertTrue(samlResponse.status.statusCode.value == \ StatusCode.INVALID_ATTR_NAME_VALUE_URI)
def _createAttributeQuery(self, issuer="/O=Site A/CN=Authorisation Service", subject="https://openid.localhost/philip.kershaw"): """Helper to create a 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 = issuer attributeQuery.subject = Subject() attributeQuery.subject.nameID = NameID() attributeQuery.subject.nameID.format = "urn:ndg:saml:test:openid" attributeQuery.subject.nameID.value = subject # special case handling for 'FirstName' attribute fnAttribute = Attribute() fnAttribute.name = TestAttributeServiceMiddleware.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 = TestAttributeServiceMiddleware.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 = \ TestAttributeServiceMiddleware.EMAILADDRESS_ATTRNAME emailAddressAttribute.nameFormat = XMLConstants.XSD_NS+"#"+\ XSStringAttributeValue.TYPE_LOCAL_NAME emailAddressAttribute.friendlyName = "emailAddress" attributeQuery.attributes.append(emailAddressAttribute) return attributeQuery
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 create_attribute_query(self): attr_query = AttributeQuery() self._set_query_common_attrs(attr_query) for i, name in enumerate(self.attribute_names): attribute = Attribute() attribute.name = name if self.attribute_friendly_names[i] is not None: attribute.friendlyName = self.attribute_friendly_names[i] attribute.nameFormat = self.attribute_formats[i] attr_query.attributes.append(attribute) return attr_query
def attributeQuery(query, response): """Attribute 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) """ response.issueInstant = datetime.utcnow() response.id = str(uuid4()) response.inResponseTo = query.id if query.issuer.value not in self.__class__.VALID_QUERY_ISSUERS: response.status.statusCode.value = \ StatusCode.REQUEST_DENIED_URI response.status.statusMessage.value = 'Invalid issuer' return response if query.subject.nameID.value not in self.__class__.VALID_SUBJECTS: response.status.statusCode.value = \ StatusCode.UNKNOWN_PRINCIPAL_URI response.status.statusMessage.value = 'Unknown user' return response assertion = Assertion() assertion.version = SAMLVersion(SAMLVersion.VERSION_20) assertion.id = str(uuid4()) assertion.issueInstant = response.issueInstant assertion.conditions = Conditions() assertion.conditions.notBefore = assertion.issueInstant assertion.conditions.notOnOrAfter = \ assertion.conditions.notBefore + 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.attributeStatements.append(AttributeStatement()) for attribute in query.attributes: if attribute.name == self.__class__.FIRSTNAME_ATTRNAME: # special case handling for 'FirstName' attribute fnAttribute = Attribute() fnAttribute.name = attribute.name fnAttribute.nameFormat = attribute.nameFormat fnAttribute.friendlyName = attribute.friendlyName firstName = XSStringAttributeValue() firstName.value = self.firstName fnAttribute.attributeValues.append(firstName) assertion.attributeStatements[0].attributes.append( fnAttribute) elif attribute.name == self.__class__.LASTNAME_ATTRNAME: lnAttribute = Attribute() lnAttribute.name = attribute.name lnAttribute.nameFormat = attribute.nameFormat lnAttribute.friendlyName = attribute.friendlyName lastName = XSStringAttributeValue() lastName.value = self.lastName lnAttribute.attributeValues.append(lastName) assertion.attributeStatements[0].attributes.append( lnAttribute) elif (attribute.name == self.__class__.EMAILADDRESS_ATTRNAME and query.issuer.value == self.__class__.VALID_QUERY_ISSUERS[0]): emailAddressAttribute = Attribute() emailAddressAttribute.name = attribute.name emailAddressAttribute.nameFormat = attribute.nameFormat emailAddressAttribute.friendlyName = attribute.friendlyName emailAddress = XSStringAttributeValue() emailAddress.value = self.emailAddress emailAddressAttribute.attributeValues.append(emailAddress) assertion.attributeStatements[0].attributes.append( emailAddressAttribute) else: response.status.statusCode.value = \ StatusCode.INVALID_ATTR_NAME_VALUE_URI return response response.assertions.append(assertion) response.status.statusCode.value = StatusCode.SUCCESS_URI return response
def attributeQuery(self, context, attributeDesignator): """Query this PIP for the given request context attribute specified by the attribute designator. Nb. this implementation is only intended to accept queries for a given *subject* in the request @param context: the request context @type context: ndg.xacml.core.context.request.Request @param designator: @type designator: ndg.xacml.core.attributedesignator.SubjectAttributeDesignator @rtype: ndg.xacml.utils.TypedList(<attributeDesignator.dataType>) / None @return: attribute values found for query subject or None if none could be found @raise PIPConfigException: if attribute ID -> Attribute Authority mapping is empty """ # Check the attribute designator type - this implementation takes # queries for request context subjects only if not isinstance(attributeDesignator, SubjectAttributeDesignator): log.debug('This PIP query interface can only accept subject ' 'attribute designator related queries') return None attributeFormat = attributeDesignator.dataType attributeId = attributeDesignator.attributeId if not isinstance(context, XacmlRequestCtx): raise TypeError('Expecting %r type for context input; got %r' % (XacmlRequestCtx, type(context))) # Look up mapping from request attribute ID to Attribute Authority to # query if len(self.__attributeId2AttributeAuthorityMap) == 0: raise PIPConfigException('No entries found in attribute ID to ' 'Attribute Authority mapping') attributeAuthorityURI = self.__attributeId2AttributeAuthorityMap.get( attributeId, None) if attributeAuthorityURI is None: log.debug("No matching attribute authority endpoint found in " "mapping file %r for input attribute ID %r", self.mappingFilePath, attributeId) return None # Get subject from the request context subject = None subjectId = None for subject in context.subjects: for attribute in subject.attributes: if attribute.attributeId == self.subjectAttributeId: if len(attribute.attributeValues) != 1: raise PIPRequestCtxException("Expecting a single " "attribute value " "for query subject ID") subjectId = attribute.attributeValues[0].value break if subjectId is None: raise PIPRequestCtxException('No subject found of type %r in ' 'request context' % self.subjectAttributeId) elif not subjectId: # Empty string return None else: # Keep a reference to the matching Subject instance xacmlCtxSubject = subject # Check for cached attributes for this subject (i.e. user) # If none found send a query to the attribute authority assertions = None attributeIdFoundInCache = False if self.cacheSessions: attributeIdFoundInCache = False sessionCache = SessionCache(subjectId, data_dir=self.sessionCacheDataDir, timeout=self.sessionCacheTimeout, assertionClockSkewTolerance=\ self.sessionCacheAssertionClockSkewTol) assertions = sessionCache.retrieve(attributeAuthorityURI) if assertions is not None: # Check for attributes matching the requested ID for assertion in assertions: for statement in assertion.attributeStatements: for attribute in statement.attributes: if attribute.name == attributeId: attributeIdFoundInCache = True break if not attributeIdFoundInCache: # No cached assertions are available for this Attribute Authority, # for the required attribute ID - make a fresh call to the # Attribute Authority # Initialise the attribute to be queried for and add it to the # SAML query samlAttribute = SamlAttribute() samlAttribute.name = attributeId samlAttribute.nameFormat = attributeFormat self.attributeQueryBinding.subjectIdFormat = \ self.subjectAttributeId query = self.attributeQueryBinding.makeQuery() query.attributes.append(samlAttribute) # Dispatch query try: self.attributeQueryBinding.setQuerySubjectId(query, subjectId) response = self.attributeQueryBinding.send(query, uri=attributeAuthorityURI) log.debug('Retrieved response from attribute service %r', attributeAuthorityURI) except Exception: log.exception('Error querying Attribute service %r with ' 'subject %r', attributeAuthorityURI, subjectId) raise finally: # !Ensure relevant query attributes are reset ready for any # subsequent query! self.attributeQueryBinding.subjectIdFormat = '' if assertions is None: assertions = SamlTypedList(SamlAssertion) assertions.extend(response.assertions) if self.cacheSessions: sessionCache.add(assertions, attributeAuthorityURI) # Unpack SAML assertion attribute corresponding to the name # format specified and copy into XACML attributes xacmlAttribute = XacmlAttribute() xacmlAttribute.attributeId = attributeId xacmlAttribute.dataType = attributeFormat # Create XACML class from SAML type identifier factory = self.__class__.XACML_ATTR_VAL_CLASS_FACTORY xacmlAttrValClass = factory(attributeFormat) for assertion in assertions: for statement in assertion.attributeStatements: for attribute in statement.attributes: if attribute.nameFormat == attributeFormat: # Convert SAML Attribute values to XACML equivalent # types for samlAttrVal in attribute.attributeValues: # Instantiate and initial new XACML value xacmlAttrVal = xacmlAttrValClass( value=samlAttrVal.value) xacmlAttribute.attributeValues.append(xacmlAttrVal) # Update the XACML request context subject with the new attributes matchFound = False for attr in xacmlCtxSubject.attributes: matchFound = attr.attributeId == attributeId if matchFound: # Weed out duplicates newAttrVals = [attrVal for attrVal in xacmlAttribute.attributeValues if attrVal not in attr.attributeValues] attr.attributeValues.extend(newAttrVals) break if not matchFound: xacmlCtxSubject.attributes.append(xacmlAttribute) # Return the attributes to the caller to comply with the interface return xacmlAttribute.attributeValues
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 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 = \ SQLAlchemyAttributeInterfaceTestCase.OPENID_URI emailAddressAttribute = Attribute() emailAddressAttribute.name = ESGFSamlNamespaces.EMAILADDRESS_ATTRNAME emailAddressAttribute.nameFormat = "InvalidFormat" emailAddressAttribute.friendlyName = "EmailAddress" attributeQuery.attributes.append(emailAddressAttribute) authzAttribute = Attribute() authzAttribute.name = \ SQLAlchemyAttributeInterfaceTestCase.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: SQLAlchemyAttributeInterfaceTestCase.SAML_FIRSTNAME_SQLQUERY, ESGFSamlNamespaces.LASTNAME_ATTRNAME: SQLAlchemyAttributeInterfaceTestCase.SAML_LASTNAME_SQLQUERY, ESGFSamlNamespaces.EMAILADDRESS_ATTRNAME: SQLAlchemyAttributeInterfaceTestCase.SAML_EMAILADDRESS_SQLQUERY, SQLAlchemyAttributeInterfaceTestCase.ATTRIBUTE_NAMES[0]: SQLAlchemyAttributeInterfaceTestCase.SAML_ATTRIBUTES_SQLQUERY } attributeInterface = SQLAlchemyAttributeInterface( samlAttribute2SqlQuery=samlAttribute2SqlQuery) attributeInterface.connectionString = \ SQLAlchemyAttributeInterfaceTestCase.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 try: attributeInterface.getAttributes(attributeQuery, samlResponse) except InvalidAttributeFormat: print("PASSED: caught InvalidAttributeFormat exception") else: self.fail("Expecting InvalidAttributeFormat exception")
def __call__(self, environ, start_response): soapRequestStream = environ['wsgi.input'] soapRequest = SOAPEnvelope() soapRequest.parse(soapRequestStream) attributeQueryElem = soapRequest.body.elem[0] attributeQuery = AttributeQueryElementTree.fromXML(attributeQueryElem) print("Received request from client:\n") print soapRequest.prettyPrint() samlResponse = Response() samlResponse.issueInstant = datetime.utcnow() samlResponse.id = str(uuid4()) samlResponse.issuer = Issuer() # SAML 2.0 spec says format must be omitted #samlResponse.issuer.format = Issuer.X509_SUBJECT samlResponse.issuer.value = \ "/O=NDG/OU=BADC/CN=attributeauthority.badc.rl.ac.uk" samlResponse.inResponseTo = attributeQuery.id assertion = Assertion() assertion.version = SAMLVersion(SAMLVersion.VERSION_20) assertion.id = str(uuid4()) assertion.issueInstant = samlResponse.issueInstant assertion.conditions = Conditions() assertion.conditions.notBefore = assertion.issueInstant assertion.conditions.notOnOrAfter = assertion.conditions.notBefore + \ timedelta(seconds=60*60*8) assertion.subject = Subject() assertion.subject.nameID = NameID() assertion.subject.nameID.format = attributeQuery.subject.nameID.format assertion.subject.nameID.value = attributeQuery.subject.nameID.value assertion.attributeStatements.append(AttributeStatement()) for attribute in attributeQuery.attributes: if attribute.name == SamlSoapBindingApp.FIRSTNAME_ATTRNAME: # special case handling for 'FirstName' attribute fnAttribute = Attribute() fnAttribute.name = attribute.name fnAttribute.nameFormat = attribute.nameFormat fnAttribute.friendlyName = attribute.friendlyName firstName = XSStringAttributeValue() firstName.value = self.firstName fnAttribute.attributeValues.append(firstName) assertion.attributeStatements[0].attributes.append(fnAttribute) elif attribute.name == SamlSoapBindingApp.LASTNAME_ATTRNAME: lnAttribute = Attribute() lnAttribute.name = attribute.name lnAttribute.nameFormat = attribute.nameFormat lnAttribute.friendlyName = attribute.friendlyName lastName = XSStringAttributeValue() lastName.value = self.lastName lnAttribute.attributeValues.append(lastName) assertion.attributeStatements[0].attributes.append(lnAttribute) elif attribute.name == SamlSoapBindingApp.EMAILADDRESS_ATTRNAME: emailAddressAttribute = Attribute() emailAddressAttribute.name = attribute.name emailAddressAttribute.nameFormat = attribute.nameFormat emailAddressAttribute.friendlyName = attribute.friendlyName emailAddress = XSStringAttributeValue() emailAddress.value = self.emailAddress emailAddressAttribute.attributeValues.append(emailAddress) assertion.attributeStatements[0].attributes.append( emailAddressAttribute) samlResponse.assertions.append(assertion) samlResponse.status = Status() samlResponse.status.statusCode = StatusCode() samlResponse.status.statusCode.value = StatusCode.SUCCESS_URI # Convert to ElementTree representation to enable attachment to SOAP # response body samlResponseElem = ResponseElementTree.toXML(samlResponse) xml = ElementTree.tostring(samlResponseElem) log.debug('Sending response to query:\n%s', xml) # Create SOAP response and attach the SAML Response payload soapResponse = SOAPEnvelope() soapResponse.create() soapResponse.body.elem.append(samlResponseElem) response = soapResponse.serialize() start_response("200 OK", [('Content-length', str(len(response))), ('Content-type', 'text/xml')]) return [response]
def test01AttributeQuery(self): 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=NDG/OU=BADC/CN=attributeauthority.badc.rl.ac.uk" attributeQuery.subject = Subject() attributeQuery.subject.nameID = NameID() attributeQuery.subject.nameID.format = SamlSoapBindingApp.NAMEID_FORMAT attributeQuery.subject.nameID.value = \ "https://openid.localhost/philip.kershaw" # special case handling for 'FirstName' attribute fnAttribute = Attribute() fnAttribute.name = SamlSoapBindingApp.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 = SamlSoapBindingApp.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 = SamlSoapBindingApp.EMAILADDRESS_ATTRNAME emailAddressAttribute.nameFormat = XMLConstants.XSD_NS+"#"+\ XSStringAttributeValue.TYPE_LOCAL_NAME emailAddressAttribute.friendlyName = "emailAddress" attributeQuery.attributes.append(emailAddressAttribute) elem = AttributeQueryElementTree.toXML(attributeQuery) soapRequest = SOAPEnvelope() soapRequest.create() soapRequest.body.elem.append(elem) request = soapRequest.serialize() header = { 'soapAction': "http://www.oasis-open.org/committees/security", 'Content-length': str(len(request)), 'Content-type': 'text/xml' } response = self.app.post('/attributeauthority', params=request, headers=header, status=200) print("Response status=%d" % response.status) soapResponse = SOAPEnvelope() responseStream = StringIO() responseStream.write(response.body) responseStream.seek(0) soapResponse.parse(responseStream) print("Parsed response ...") print(soapResponse.serialize()) # print(prettyPrint(soapResponse.elem)) response = ResponseElementTree.fromXML(soapResponse.body.elem[0]) self.assert_(response.status.statusCode.value==StatusCode.SUCCESS_URI) self.assert_(response.inResponseTo == attributeQuery.id) self.assert_(response.assertions[0].subject.nameID.value == \ attributeQuery.subject.nameID.value)
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 attributeQuery(self, context, attributeDesignator): """Query this PIP for the given request context attribute specified by the attribute designator. Nb. this implementation is only intended to accept queries for a given *subject* in the request @param context: the request context @type context: ndg.xacml.core.context.request.Request @param designator: @type designator: ndg.xacml.core.attributedesignator.SubjectAttributeDesignator @rtype: ndg.xacml.utils.TypedList(<attributeDesignator.dataType>) / None @return: attribute values found for query subject or None if none could be found @raise PIPConfigException: if attribute ID -> Attribute Authority mapping is empty """ # Check the attribute designator type - this implementation takes # queries for request context subjects only if not isinstance(attributeDesignator, SubjectAttributeDesignator): log.debug('This PIP query interface can only accept subject ' 'attribute designator related queries') return None attributeFormat = attributeDesignator.dataType attributeId = attributeDesignator.attributeId exptd_attribute_id = self.attribute_query.subject.nameID.format if not isinstance(context, XacmlRequestCtx): raise TypeError('Expecting %r type for context input; got %r' % (XacmlRequestCtx, type(context))) # Look up mapping from request attribute ID to Attribute Authority to # query if len(self.__attributeId2AttributeAuthorityMap) == 0: raise PIPConfigException('No entries found in attribute ID to ' 'Attribute Authority mapping') attributeAuthorityURI = self.__attributeId2AttributeAuthorityMap.get( attributeId, None) if attributeAuthorityURI is None: log.debug( "No matching attribute authority endpoint found in " "mapping file %r for input attribute ID %r", self.mappingFilePath, attributeId) return None # Get subject from the request context subject = None subjectId = None for subject in context.subjects: for attribute in subject.attributes: if attribute.attributeId == exptd_attribute_id: if len(attribute.attributeValues) != 1: raise PIPRequestCtxException("Expecting a single " "attribute value " "for query subject ID") subjectId = attribute.attributeValues[0].value break if subjectId is None: raise PIPRequestCtxException('No subject found of type %r in ' 'request context' % exptd_attribute_id) elif not subjectId: # Empty string return None else: # Keep a reference to the matching Subject instance xacmlCtxSubject = subject # Check for cached attributes for this subject (i.e. user) # If none found send a query to the attribute authority assertions = None attributeIdFoundInCache = False if self.cacheSessions: attributeIdFoundInCache = False sessionCache = SessionCache(subjectId, data_dir=self.sessionCacheDataDir, timeout=self.sessionCacheTimeout, assertionClockSkewTolerance=\ self.sessionCacheAssertionClockSkewTol) assertions = sessionCache.retrieve(attributeAuthorityURI) if assertions is not None: # Check for attributes matching the requested ID for assertion in assertions: for statement in assertion.attributeStatements: for attribute in statement.attributes: if attribute.name == attributeId: attributeIdFoundInCache = True break if not attributeIdFoundInCache: # No cached assertions are available for this Attribute Authority, # for the required attribute ID - make a fresh call to the # Attribute Authority # Initialise the attribute to be queried for and add it to the # SAML query samlAttribute = SamlAttribute() samlAttribute.name = attributeId samlAttribute.nameFormat = attributeFormat # Copy attributes for this query from constants set at # initialisation query = AttributeQueryFactory.create() query.subject.nameID.value = subjectId query.subject.nameID.format = exptd_attribute_id query.issuer.value = self.attribute_query.issuer.value query.issuer.format = self.attribute_query.issuer.format query.attributes.append(samlAttribute) # Dispatch query try: response = self.attribute_query_binding.send( query, uri=attributeAuthorityURI) log.debug('Retrieved response from attribute service %r', attributeAuthorityURI) except Exception: log.exception( 'Error querying Attribute service %r with ' 'subject %r', attributeAuthorityURI, subjectId) raise if assertions is None: assertions = SamlTypedList(SamlAssertion) assertions.extend(response.assertions) if self.cacheSessions: sessionCache.add(assertions, attributeAuthorityURI) # Unpack SAML assertion attribute corresponding to the name # format specified and copy into XACML attributes xacmlAttribute = XacmlAttribute() xacmlAttribute.attributeId = attributeId xacmlAttribute.dataType = attributeFormat # Create XACML class from SAML type identifier factory = self.__class__.XACML_ATTR_VAL_CLASS_FACTORY xacmlAttrValClass = factory(attributeFormat) for assertion in assertions: for statement in assertion.attributeStatements: for attribute in statement.attributes: if attribute.nameFormat == attributeFormat: # Convert SAML Attribute values to XACML equivalent # types for samlAttrVal in attribute.attributeValues: # Instantiate and initial new XACML value xacmlAttrVal = xacmlAttrValClass( value=samlAttrVal.value) xacmlAttribute.attributeValues.append(xacmlAttrVal) # Update the XACML request context subject with the new attributes matchFound = False for attr in xacmlCtxSubject.attributes: matchFound = attr.attributeId == attributeId if matchFound: # Weed out duplicates newAttrVals = [ attrVal for attrVal in xacmlAttribute.attributeValues if attrVal not in attr.attributeValues ] attr.attributeValues.extend(newAttrVals) break if not matchFound: xacmlCtxSubject.attributes.append(xacmlAttribute) # Return the attributes to the caller to comply with the interface return xacmlAttribute.attributeValues
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 test04SamlAttributeQuery(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 fnAttribute = Attribute() fnAttribute.name = ESGFSamlNamespaces.FIRSTNAME_ATTRNAME fnAttribute.nameFormat = XSStringAttributeValue.DEFAULT_FORMAT fnAttribute.friendlyName = "FirstName" attributeQuery.attributes.append(fnAttribute) lnAttribute = Attribute() lnAttribute.name = ESGFSamlNamespaces.LASTNAME_ATTRNAME lnAttribute.nameFormat = XSStringAttributeValue.DEFAULT_FORMAT lnAttribute.friendlyName = "LastName" attributeQuery.attributes.append(lnAttribute) emailAddressAttribute = Attribute() emailAddressAttribute.name = ESGFSamlNamespaces.EMAILADDRESS_ATTRNAME emailAddressAttribute.nameFormat = XSStringAttributeValue.DEFAULT_FORMAT 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) self.assert_( samlResponse.status.statusCode.value == StatusCode.SUCCESS_URI) self.assert_(samlResponse.inResponseTo == attributeQuery.id) self.assert_(samlResponse.assertions[0].subject.nameID.value == \ attributeQuery.subject.nameID.value) self.assert_( samlResponse.assertions[0].attributeStatements[0].attributes[1 ].attributeValues[0].value == 'Kershaw') self.assert_( len(samlResponse.assertions[0].attributeStatements[0].attributes[3 ].attributeValues) == TestUserDatabase.N_ATTRIBUTE_VALUES)