Changeset 5622


Ignore:
Timestamp:
17/08/09 15:00:14 (10 years ago)
Author:
pjkersha
Message:

Working test_samlinterface unit tests. Fixed bug in ESG XSGroupRoleAttribute parsing and successfully parsed output from Luca's ESG test Attribute Service.

Location:
TI12-security/trunk/python
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • TI12-security/trunk/python/ndg.security.saml/saml/xml/etree.py

    r5621 r5622  
    362362        @type elem: ElementTree.Element 
    363363        @param elem: ElementTree element containing the assertion 
     364        @type attributeValueElementTreeFactoryKw: dict 
     365        @param attributeValueElementTreeFactoryKw: keywords for AttributeValue 
    364366        @rtype: saml.saml2.core.Assertion 
    365367        @return: Assertion object""" 
     
    525527            raise TypeError("Expecting %r type got: %r"%(Attribute, attribute)) 
    526528         
    527         tag = str(QName.fromGeneric(Attribute.DEFAULT_ELEMENT_NAME))     
     529        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))     
    528530        elem = ElementTree.Element(tag) 
    529531        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI 
     
    572574        attribute = Attribute() 
    573575             
     576        # Name is mandatory in the schema 
    574577        name = elem.attrib.get(cls.NAME_ATTRIB_NAME) 
    575         if name is not None: 
    576             attribute.name = name 
     578        if name is None: 
     579            raise XMLTypeParseError('No "%s" attribute found in the "%s" ' 
     580                                    'element' % 
     581                                    (cls.NAME_ATTRIB_NAME, 
     582                                     cls.DEFAULT_ELEMENT_LOCAL_NAME)) 
     583        attribute.name = name 
    577584             
    578585        friendlyName = elem.attrib.get(cls.FRIENDLY_NAME_ATTRIB_NAME) 
     
    730737        ElementTree._namespace_map[attributeValue.namespaceURI 
    731738                                   ] = attributeValue.namespacePrefix 
    732          
    733         elem.set(cls.GROUP_ATTRIB_NAME, attributeValue.group) 
    734         elem.set(cls.ROLE_ATTRIB_NAME, attributeValue.role) 
    735  
     739                                    
     740        tag = str(QName.fromGeneric(cls.TYPE_NAME))     
     741        groupRoleElem = ElementTree.Element(tag) 
     742        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI 
     743                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix  
     744         
     745        groupRoleElem.set(cls.GROUP_ATTRIB_NAME, attributeValue.group) 
     746        groupRoleElem.set(cls.ROLE_ATTRIB_NAME, attributeValue.role) 
     747 
     748        elem.append(groupRoleElem) 
     749         
    736750        return elem 
    737751 
     
    754768            raise XMLTypeParseError("No \"%s\" element found" % 
    755769                                    cls.DEFAULT_ELEMENT_LOCAL_NAME) 
    756  
    757          
    758         # Update namespace map as an XSI type has been referenced.  This will 
     770                                    
     771        # Check for group/role child element 
     772        if len(elem) == 0: 
     773            raise XMLTypeParseError('Expecting "%s" child element to "%s" ' 
     774                                    'element' % (cls.TYPE_LOCAL_NAME, 
     775                                               cls.DEFAULT_ELEMENT_LOCAL_NAME)) 
     776         
     777        childElem = elem[0] 
     778        childLocalName = QName.getLocalPart(childElem.tag) 
     779        if childLocalName != cls.TYPE_LOCAL_NAME: 
     780            raise XMLTypeParseError("No \"%s\" element found" % 
     781                                    cls.TYPE_LOCAL_NAME) 
     782 
     783         
     784        # Update namespace map for the Group/Role type referenced.  This will 
    759785        # ensure the correct prefix is applied if it is re-serialised. 
    760         ElementTree._namespace_map[SAMLConstants.XSI_NS 
    761                                    ] = SAMLConstants.XSI_PREFIX 
     786        ElementTree._namespace_map[cls.DEFAULT_NS] = cls.DEFAULT_PREFIX 
    762787                                       
    763788        attributeValue = XSGroupRoleAttributeValue() 
    764         groupName = elem.attrib.get(cls.GROUP_ATTRIB_NAME) 
     789        groupName = childElem.attrib.get(cls.GROUP_ATTRIB_NAME) 
    765790        if groupName is None: 
    766791            raise XMLTypeParseError('No "%s" attribute found in Group/Role ' 
     
    769794        attributeValue.group = groupName 
    770795         
    771         roleName = elem.attrib.get(cls.ROLE_ATTRIB_NAME) 
     796        roleName = childElem.attrib.get(cls.ROLE_ATTRIB_NAME) 
    772797        if roleName is None: 
    773798            raise XMLTypeParseError('No "%s" attribute found in Group/Role ' 
     
    782807        """Match function used by AttributeValueElementTreeFactory to 
    783808        determine whether the given attribute is XSGroupRole type 
    784         """ 
    785         if cls.GROUP_ATTRIB_NAME in elem.attrib and \ 
    786            cls.ROLE_ATTRIB_NAME in elem.attrib: 
     809         
     810        @type elem: ElementTree.Element 
     811        @param elem: Attribute value as ElementTree XML element 
     812        @rtype: saml.saml2.core.XSGroupRoleAttributeValue or None 
     813        @return: SAML ESG Group/Role Attribute Value class if elem is an 
     814        Group/role type element or None if if doesn't match this type  
     815        """ 
     816         
     817        # Group/role element is a child of the AttributeValue element 
     818        if len(elem) == 0: 
     819            return None 
     820         
     821        childLocalName = QName.getLocalPart(elem[0].tag) 
     822        if childLocalName != cls.TYPE_LOCAL_NAME: 
     823            raise XMLTypeParseError('No "%s" child element found in ' 
     824                                    'AttributeValue' % cls.TYPE_LOCAL_NAME) 
     825                
     826        if cls.GROUP_ATTRIB_NAME in elem[0].attrib and \ 
     827           cls.ROLE_ATTRIB_NAME in elem[0].attrib: 
    787828            return cls 
    788829 
     
    920961            raise TypeError("Expecting %r class got %r" % (Issuer,  
    921962                                                           type(issuer))) 
    922         attrib = { 
    923             cls.FORMAT_ATTRIB_NAME: issuer.format 
    924         } 
     963             
     964        # Issuer format may be omitted from a response: saml-profiles-2.0-os, 
     965        # Section 4.1.4.2 
     966        attrib = {} 
     967        if issuer.format is not None: 
     968            attrib[cls.FORMAT_ATTRIB_NAME] = issuer.format 
     969         
    925970        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME)) 
    926971        elem = ElementTree.Element(tag, **attrib) 
     
    949994             
    950995        issuerFormat = elem.attrib.get(cls.FORMAT_ATTRIB_NAME) 
    951         if issuerFormat is None: 
    952             raise XMLTypeParseError('No "%s" attribute found in "%s" ' 
    953                                       'element' % 
    954                                       (issuerFormat, 
    955                                        cls.DEFAULT_ELEMENT_LOCAL_NAME)) 
    956996        issuer = Issuer() 
    957         issuer.format = issuerFormat 
     997         
     998        # Issuer format may be omitted from a response: saml-profiles-2.0-os, 
     999        # Section 4.1.4.2 
     1000        if issuerFormat is not None: 
     1001            issuer.format = issuerFormat 
     1002         
    9581003        issuer.value = elem.text.strip()  
    9591004         
     
    13101355            raise TypeError("Expecting %r class, got %r" % (Response,  
    13111356                                                            type(response))) 
    1312              
     1357          
     1358        if response.id is None: 
     1359            raise TypeError("SAML Response id is not set") 
     1360           
     1361        if response.issueInstant is None: 
     1362            raise TypeError("SAML Response issueInstant is not set") 
     1363         
     1364        # TODO: Does inResponseTo have to be set?  This implementation  
     1365        # currently enforces this ... 
     1366        if response.inResponseTo is None: 
     1367            raise TypeError("SAML Response inResponseTo identifier is not set") 
     1368         
    13131369        issueInstant = SAMLDateTime.toString(response.issueInstant) 
    13141370        attrib = { 
     
    13261382        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI 
    13271383                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix 
    1328              
    1329         issuerElem = IssuerElementTree.toXML(response.issuer) 
    1330         elem.append(issuerElem) 
     1384 
     1385        # Issuer may be omitted: saml-profiles-2.0-os Section 4.1.4.2 
     1386        if response.issuer is not None:  
     1387            issuerElem = IssuerElementTree.toXML(response.issuer) 
     1388            elem.append(issuerElem) 
    13311389 
    13321390        statusElem = StatusElementTree.toXML(response.status)        
     
    13421400 
    13431401    @classmethod 
    1344     def fromXML(cls, elem): 
     1402    def fromXML(cls, elem, **attributeValueElementTreeFactoryKw): 
    13451403        """Parse ElementTree element into a SAML Response object 
    13461404         
    13471405        @type elem: ElementTree.Element 
    13481406        @param elem: XML element containing the Response 
     1407        @type attributeValueElementTreeFactoryKw: dict 
     1408        @param attributeValueElementTreeFactoryKw: keywords for AttributeValue 
    13491409        @rtype: saml.saml2.core.Response 
    13501410        @return: Response object 
     
    14021462             
    14031463            elif localName == Assertion.DEFAULT_ELEMENT_LOCAL_NAME: 
    1404                 response.assertions.append( 
    1405                                     AssertionElementTree.fromXML(childElem)) 
     1464                assertion = AssertionElementTree.fromXML(childElem, 
     1465                                        **attributeValueElementTreeFactoryKw) 
     1466                response.assertions.append(assertion) 
    14061467            else: 
    14071468                raise XMLTypeParseError('Unrecognised Response child ' 
  • TI12-security/trunk/python/ndg.security.test/ndg/security/test/unit/saml/test_samlinterface.py

    r5601 r5622  
    1313import unittest 
    1414 
    15 from datetime import datetime 
     15from datetime import datetime, timedelta 
    1616import base64  
    1717import os 
     
    1919import paste.fixture 
    2020from cStringIO import StringIO 
    21      
     21from xml.etree import ElementTree 
     22 
    2223from ndg.security.common.soap.etree import SOAPEnvelope 
    23 from ndg.security.common.utils.etree import QName 
    24  
    25 from saml.saml2.core import Assertion, Attribute, AttributeValue, \ 
     24from ndg.security.common.utils.etree import QName, prettyPrint 
     25 
     26from saml.saml2.core import Response, Assertion, Attribute, AttributeValue, \ 
    2627    AttributeStatement, SAMLVersion, Subject, NameID, Issuer, AttributeQuery, \ 
    27     XSStringAttributeValue 
     28    XSStringAttributeValue, XSGroupRoleAttributeValue, Conditions, Status, \ 
     29    StatusCode 
    2830from saml.xml import XMLConstants 
    2931from saml.xml.etree import AssertionElementTree, AttributeQueryElementTree, \ 
    30     ResponseElementTree 
     32    ResponseElementTree, XSGroupRoleAttributeValueElementTree 
    3133 
    3234 
     
    4244        soapRequest.parse(soapRequestStream) 
    4345        attributeQueryElem = soapRequest.body.elem[0] 
    44         attributeQuery = AttributeQueryElementTree.parse(attributeQueryElem) 
     46        attributeQuery = AttributeQueryElementTree.fromXML(attributeQueryElem) 
    4547         
    4648        print("Received request from client:\n") 
    4749        print soapRequest.prettyPrint() 
    4850         
     51        samlResponse = Response() 
     52         
     53        samlResponse.issueInstant = datetime.utcnow() 
     54        samlResponse.id = str(uuid4()) 
     55        samlResponse.issuer = Issuer() 
     56         
     57        # SAML 2.0 spec says fromat must be omitted 
     58        #samlResponse.issuer.format = Issuer.X509_SUBJECT 
     59        samlResponse.issuer.value = \ 
     60                        "/O=NDG/OU=BADC/CN=attributeauthority.badc.rl.ac.uk" 
     61         
     62        samlResponse.inResponseTo = attributeQuery.id 
     63         
    4964        assertion = Assertion() 
     65         
    5066        assertion.version = SAMLVersion(SAMLVersion.VERSION_20) 
    5167        assertion.id = str(uuid4()) 
    52         assertion.issueInstant = datetime.utcnow() 
     68        assertion.issueInstant = samlResponse.issueInstant 
     69         
     70        assertion.conditions = Conditions() 
     71        assertion.conditions.notBefore = assertion.issueInstant 
     72        assertion.conditions.notOnOrAfter = assertion.conditions.notBefore + \ 
     73            timedelta(seconds=60*60*8) 
     74         
     75        assertion.subject = Subject()   
     76        assertion.subject.nameID = NameID() 
     77        assertion.subject.nameID.format = attributeQuery.subject.nameID.format 
     78        assertion.subject.nameID.value = attributeQuery.subject.nameID.value 
     79 
    5380        assertion.attributeStatements.append(AttributeStatement()) 
    54         attributes = [] 
    5581         
    5682        for attribute in attributeQuery.attributes: 
     
    6692                fnAttribute.attributeValues.append(firstName) 
    6793     
    68                 attributes.append(fnAttribute) 
     94                assertion.attributeStatements[0].attributes.append(fnAttribute) 
    6995             
    7096            elif attribute.name == "urn:esg:last:name": 
     
    78104                lnAttribute.attributeValues.append(lastName) 
    79105     
    80                 attributes.append(lnAttribute) 
     106                assertion.attributeStatements[0].attributes.append(lnAttribute) 
    81107                
    82108            elif attribute.name == "urn:esg:email:address": 
     
    90116                emailAddressAttribute.attributeValues.append(emailAddress) 
    91117     
    92                 attributes.append(emailAddressAttribute) 
    93                  
    94             assertion.attributeStatements[0].attributes = attributes 
    95              
     118                assertion.attributeStatements[0].attributes.append( 
     119                                                        emailAddressAttribute) 
     120         
     121        samlResponse.assertions.append(assertion) 
     122         
     123        # Add mapping for ESG Group/Role Attribute Value to enable ElementTree 
     124        # Attribute Value factory to render the XML output 
     125        toXMLTypeMap = { 
     126            XSGroupRoleAttributeValue: XSGroupRoleAttributeValueElementTree 
     127        } 
     128 
     129         
     130        samlResponse.status = Status() 
     131        samlResponse.status.statusCode = StatusCode() 
     132        samlResponse.status.statusCode.value = StatusCode.SUCCESS_URI         
     133 
     134         
     135        # Convert to ElementTree representation to enable attachment to SOAP 
     136        # response body 
     137        samlResponseElem = ResponseElementTree.toXML(samlResponse, 
     138                                            customToXMLTypeMap=toXMLTypeMap) 
     139        xml = ElementTree.tostring(samlResponseElem) 
     140         
     141        # Create SOAP response and attach the SAML Response payload 
    96142        soapResponse = SOAPEnvelope() 
    97143        soapResponse.create() 
     144        soapResponse.body.elem.append(samlResponseElem) 
     145         
    98146        response = soapResponse.serialize() 
     147         
    99148        start_response("200 OK", 
    100149                       [('Content-length', str(len(response))), 
     
    157206        attributeQuery.attributes.append(emailAddressAttribute)                                    
    158207         
    159         elem = AttributeQueryElementTree.create(attributeQuery) 
     208        elem = AttributeQueryElementTree.toXML(attributeQuery) 
    160209        soapRequest = SOAPEnvelope() 
    161210        soapRequest.create() 
     
    185234        print("Parsed response ...") 
    186235        print(soapResponse.serialize()) 
     236#        print(prettyPrint(soapResponse.elem)) 
     237         
     238        response = ResponseElementTree.fromXML(soapResponse.body.elem[0]) 
     239        self.assert_(response.status.statusCode.value==StatusCode.SUCCESS_URI) 
     240        self.assert_(response.inResponseTo == attributeQuery.id) 
     241        self.assert_(response.assertions[0].subject.nameID.value == \ 
     242                     attributeQuery.subject.nameID.value) 
    187243       
    188244    def test02AttributeQueryWithSOAPClient(self): 
     
    211267        attributeQuery.issuer.value = \ 
    212268                        "/O=NDG/OU=BADC/CN=attributeauthority.badc.rl.ac.uk" 
    213                          
    214                          
     269 
    215270        attributeQuery.subject = Subject()   
    216271        attributeQuery.subject.nameID = NameID() 
     
    244299        attributeQuery.attributes.append(emailAddressAttribute)                                    
    245300         
    246         attributeQueryElem = AttributeQueryElementTree.create(attributeQuery) 
     301        attributeQueryElem = AttributeQueryElementTree.toXML(attributeQuery) 
    247302 
    248303        # Attach query to SOAP body 
     
    266321            self.fail('Expecting "Response" element in SOAP body') 
    267322             
    268         response = ResponseElementTree.parse(response.envelope.body.elem[0]) 
     323        toSAMLTypeMap = [XSGroupRoleAttributeValueElementTree.factoryMatchFunc] 
     324        response = ResponseElementTree.fromXML(response.envelope.body.elem[0], 
     325                                            customToSAMLTypeMap=toSAMLTypeMap) 
     326        self.assert_(response) 
     327         
     328    def test03ParseResponse(self): 
     329        response = '''<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"> 
     330   <SOAP-ENV:Body> 
     331      <samlp:Response ID="05680cb2-4973-443d-9d31-7bc99bea87c1" InResponseTo="e3183380-ae82-4285-8827-8c40613842de" IssueInstant="2009-08-17T12:28:37.325Z" Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"> 
     332         <saml:Issuer Format="urn:esg:issuer" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">ESG-NCAR</saml:Issuer> 
     333         <samlp:Status> 
     334            <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" /> 
     335         </samlp:Status> 
     336         <saml:Assertion ID="192c67d9-f9cd-457a-9242-999e7b943166" IssueInstant="2009-08-17T12:28:37.347Z" Version="2.0" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"> 
     337            <saml:Issuer Format="urn:esg:issuer">ESG-NCAR</saml:Issuer> 
     338            <saml:Subject> 
     339               <saml:NameID Format="urn:esg:openid">https://esg.prototype.ucar.edu/myopenid/testUser</saml:NameID> 
     340            </saml:Subject> 
     341            <saml:Conditions NotBefore="2009-08-17T12:28:37.347Z" NotOnOrAfter="2009-08-18T12:28:37.347Z" /> 
     342            <saml:AttributeStatement> 
     343               <saml:Attribute FriendlyName="FirstName" Name="urn:esg:first:name" NameFormat="http://www.w3.org/2001/XMLSchema#string"> 
     344                  <saml:AttributeValue xsi:type="xs:string" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Test</saml:AttributeValue> 
     345               </saml:Attribute> 
     346               <saml:Attribute FriendlyName="LastName" Name="urn:esg:last:name" NameFormat="http://www.w3.org/2001/XMLSchema#string"> 
     347                  <saml:AttributeValue xsi:type="xs:string" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">User</saml:AttributeValue> 
     348               </saml:Attribute> 
     349               <saml:Attribute FriendlyName="EmailAddress" Name="urn:esg:email:address" NameFormat="http://www.w3.org/2001/XMLSchema#string"> 
     350                  <saml:AttributeValue xsi:type="xs:string" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">ejn@ucar.edu</saml:AttributeValue> 
     351               </saml:Attribute> 
     352               <saml:Attribute FriendlyName="GroupRole" Name="urn:esg:group:role" NameFormat="groupRole"> 
     353                  <saml:AttributeValue> 
     354                     <esg:groupRole group="CCSM" role="default" xmlns:esg="http://www.esg.org" /> 
     355                  </saml:AttributeValue> 
     356                  <saml:AttributeValue> 
     357                     <esg:groupRole group="Dynamical Core" role="default" xmlns:esg="http://www.esg.org" /> 
     358                  </saml:AttributeValue> 
     359                  <saml:AttributeValue> 
     360                     <esg:groupRole group="NARCCAP" role="default" xmlns:esg="http://www.esg.org" /> 
     361                  </saml:AttributeValue> 
     362               </saml:Attribute> 
     363            </saml:AttributeStatement> 
     364         </saml:Assertion> 
     365      </samlp:Response> 
     366   </SOAP-ENV:Body> 
     367</SOAP-ENV:Envelope>''' 
     368         
     369        soapResponse = SOAPEnvelope() 
     370         
     371        responseStream = StringIO() 
     372        responseStream.write(response) 
     373        responseStream.seek(0) 
     374         
     375        soapResponse.parse(responseStream) 
     376         
     377        print("Parsed response ...") 
     378        print(soapResponse.serialize()) 
     379         
     380        toSAMLTypeMap = [XSGroupRoleAttributeValueElementTree.factoryMatchFunc] 
     381        response = ResponseElementTree.fromXML(soapResponse.body.elem[0], 
     382                                            customToSAMLTypeMap=toSAMLTypeMap) 
     383        self.assert_(response) 
     384 
    269385             
    270386if __name__ == "__main__": 
Note: See TracChangeset for help on using the changeset viewer.