Ejemplo n.º 1
0
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)
Ejemplo n.º 3
0
    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)
Ejemplo n.º 4
0
    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)
Ejemplo n.º 5
0
    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 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)
Ejemplo n.º 10
0
    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
Ejemplo n.º 11
0
    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 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)
class TestUserRoles(AttributeInterface):
    """Test User Roles class dynamic import for Attribute Authority"""
    ATTRIBUTE_NAMES = ()
    ATTRIBUTE_VALUES = ()

    SAML_ATTRIBUTE_NAMES = ATTRIBUTE_NAMES + (
        ESGFSamlNamespaces.EMAILADDRESS_ATTRNAME,
        ESGFSamlNamespaces.FIRSTNAME_ATTRNAME,
        ESGFSamlNamespaces.LASTNAME_ATTRNAME, 'urn:esg:sitea:grouprole')

    SAML_ATTRIBUTE_VALUES = (ATTRIBUTE_VALUES, ('*****@*****.**', ),
                             ('Philip', ), ('Kershaw', ), (('siteagroup',
                                                            'default'), ))

    SAML_ATTRIBUTE_FRIENDLY_NAMES = ('', ) * len(ATTRIBUTE_NAMES) + (
        "EmailAddress", "FirstName", "LastName", "groupRole")
    SAML_ATTRIBUTE_FORMATS = (
        SAMLConstants.XSD_NS+"#"+XSStringAttributeValue.TYPE_LOCAL_NAME,) * (
        len(SAML_ATTRIBUTE_NAMES)-1) + \
        (ESGFGroupRoleAttributeValue.TYPE_LOCAL_NAME, )

    SAML_ATTRIBUTES = []

    name, val, vals, format, friendlyName = (None, None, None, None, None)
    for name, vals, format, friendlyName in zip(SAML_ATTRIBUTE_NAMES,
                                                SAML_ATTRIBUTE_VALUES,
                                                SAML_ATTRIBUTE_FORMATS,
                                                SAML_ATTRIBUTE_FRIENDLY_NAMES):
        SAML_ATTRIBUTES.append(Attribute())
        SAML_ATTRIBUTES[-1].name = name
        SAML_ATTRIBUTES[-1].nameFormat = format
        SAML_ATTRIBUTES[-1].friendlyName = friendlyName
        for val in vals:
            if isinstance(val, tuple):
                SAML_ATTRIBUTES[-1].attributeValues.append(
                    ESGFGroupRoleAttributeValue())
                SAML_ATTRIBUTES[-1].attributeValues[-1].value = val
            else:
                SAML_ATTRIBUTES[-1].attributeValues.append(
                    XSStringAttributeValue())
                SAML_ATTRIBUTES[-1].attributeValues[-1].value = val

    del name, val, vals, format, friendlyName

    # 8 hours validity for issued assertions
    SAML_ASSERTION_LIFETIME = 8 * 60 * 60

    VALID_USER_IDS = ("https://openid.localhost/philip.kershaw",
                      TestUserDatabase.OPENID_URI)
    VALID_REQUESTOR_IDS = BaseTestCase.VALID_REQUESTOR_IDS

    INSUFFICIENT_PRIVILEGES_REQUESTOR_ID = X500DN.from_string(
        "/O=Site B/CN=Authorisation Service")

    def __init__(self, propertiesFilePath=None):
        pass

    def getRoles(self, userId):
        return TestUserRoles.ATTRIBUTE_VALUES

    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)
Ejemplo n.º 14
0
    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)
Ejemplo n.º 15
0
 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
Ejemplo n.º 16
0
    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
Ejemplo n.º 17
0
    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
Ejemplo n.º 18
0
        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 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)