source: TI12-security/trunk/python/ndg_security_server/ndg/security/server/wsgi/saml.py @ 5656

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg_security_server/ndg/security/server/wsgi/saml.py@5656
Revision 5656, 7.2 KB checked in by pjkersha, 10 years ago (diff)
  • Added factory methods to ndg.security.server.attributeauthority.AttributeAuthority? in order to create getAttCert and samlAttributeQuery wrapper functions. These can then be added to the WSGI environ to be referenced by other middleware.
  • ndg.security.test.unit.saml.test_soapattributeinterface: started work on unit tests for SAML 2.0 SOAP binding to attribute query interface.
Line 
1"""WSGI SAML Module for SAML 2.0 Assertion Query/Request Profile implementation
2
3NERC DataGrid Project
4"""
5__author__ = "P J Kershaw"
6__date__ = "17/08/2009"
7__copyright__ = "(C) 2009 Science and Technology Facilities Council"
8__contact__ = "Philip.Kershaw@stfc.ac.uk"
9__revision__ = "$Id$"
10__license__ = "BSD - see LICENSE file in top-levle directory"
11import logging
12log = logging.getLogger(__name__)
13from cStringIO import StringIO
14from uuid import uuid4
15from xml.etree import ElementTree
16
17from saml.saml2.core import Response, Assertion, Attribute, AttributeValue, \
18    AttributeStatement, SAMLVersion, Subject, NameID, Issuer, AttributeQuery, \
19    XSStringAttributeValue, XSGroupRoleAttributeValue, Conditions, Status, \
20    StatusCode
21   
22from saml.xml import SAMLConstants
23from saml.xml.etree import AssertionElementTree, AttributeQueryElementTree, \
24    ResponseElementTree, XSGroupRoleAttributeValueElementTree
25
26from ndg.security.common.soap.etree import SOAPEnvelope
27from ndg.security.common.utils.etree import QName, prettyPrint
28from ndg.security.server.wsgi.soap import SOAPMiddleware
29
30class SOAPAttributeInterfaceMiddlewareError(Exception):
31    """Base class for WSGI SAML 2.0 SOAP Attribute Interface Errors"""
32
33
34class SOAPAttributeInterfaceMiddlewareConfigError(Exception):
35    """WSGI SAML 2.0 SOAP Attribute Interface Configuration problem"""
36
37 
38class SOAPAttributeInterfaceMiddleware(SOAPMiddleware):
39    """Implementation of SAML 2.0 SOAP Binding for Assertion Query/Request
40    Profile"""
41    log = logging.getLogger('SOAPAttributeInterfaceMiddleware')
42    QUERY_INTERFACE_KEYNAME_OPTNAME = "queryInterfaceKeyName"
43    DEFAULT_QUERY_INTERFACE_KEYNAME = ("ndg.security.server.wsgi.saml."
44                            "SOAPAttributeInterfaceMiddleware.queryInterface")
45   
46    def __init__(self, app, global_conf, prefix='', **app_conf):
47        '''
48        @type app: callable following WSGI interface
49        @param app: next middleware application in the chain     
50        @type global_conf: dict       
51        @param global_conf: PasteDeploy global configuration dictionary
52        @type prefix: basestring
53        @param prefix: prefix for configuration items
54        @type app_conf: dict       
55        @param app_conf: PasteDeploy application specific configuration
56        dictionary
57        '''
58        self._app = app
59        self.__queryInterfaceKeyName = None
60       
61        self.queryInterfaceKeyName = app_conf.get(prefix + \
62            SOAPAttributeInterfaceMiddleware.QUERY_INTERFACE_KEYNAME_OPTNAME,
63            prefix + \
64            SOAPAttributeInterfaceMiddleware.DEFAULT_QUERY_INTERFACE_KEYNAME)
65
66    def _getQueryInterfaceKeyName(self):
67        return self.__queryInterfaceKeyName
68
69    def _setQueryInterfaceKeyName(self, value):
70        if not isinstance(value, basestring):
71            raise TypeError('Expecting string type for "queryInterfaceKeyName"'
72                            ' got %r' % value)
73           
74        self.__queryInterfaceKeyName = value
75
76    queryInterfaceKeyName = property(fget=_getQueryInterfaceKeyName, 
77                                     fset=_setQueryInterfaceKeyName, 
78                                     doc="environ keyname for Attribute Query "
79                                         "interface")
80
81    def _getIssuerName(self):
82        return self.__issuerName
83
84    def _setIssuerName(self, value):
85        self.__issuerName = value
86
87    issuerName = property(fget=_getIssuerName, 
88                          fset=_setIssuerName, 
89                          doc="Name of assertion issuing authority")
90       
91    def __call__(self, environ, start_response):
92        """Check for and parse a SOAP SAML Attribute Query and return a
93        SAML Response
94       
95        @type environ: dict
96        @param environ: WSGI environment variables dictionary
97        @type start_response: function
98        @param start_response: standard WSGI start response function
99        """
100           
101        # Ignore non-SOAP requests
102        if not self.isSOAPMessage(environ):
103            return self._app(environ, start_response)
104       
105        soapRequestStream = environ.get('wsgi.input')
106        if soapRequestStream is None:
107            raise SOAPAttributeInterfaceMiddlewareError('No "wsgi.input" in '
108                                                        'environ')
109       
110        # TODO: allow for chunked data
111        contentLength = environ.get('CONTENT_LENGTH')
112        if contentLength is None:
113            raise SOAPAttributeInterfaceMiddlewareError('No "CONTENT_LENGTH" '
114                                                        'in environ')
115
116        contentLength = int(contentLength)       
117        soapRequestTxt = soapRequestStream.read(contentLength)
118       
119        # Parse into a SOAP envelope object
120        soapRequest = SOAPEnvelope()
121        soapRequest.parse(StringIO(soapRequestTxt))
122       
123        # Filter based on SOAP Body content - expecting an AttributeQuery
124        # element
125        if not SOAPAttributeInterfaceMiddleware.isAttributeQuery(
126                                                            soapRequest.body):
127            # Reset wsgi.input for middleware and app downstream
128            environ['wsgi.input'] = StringIO(soapRequestTxt)
129            return self._app(environ, start_response)
130       
131        log.debug("SOAPAttributeInterfaceMiddleware.__call__: received SAML "
132                  "SOAP AttributeQuery ...")
133       
134        attributeQueryElem = soapRequest.body.elem[0]
135        attributeQuery = AttributeQueryElementTree.fromXML(attributeQueryElem)
136       
137        # Check for Query Interface in environ
138        queryInterface = environ.get(self.queryInterfaceKeyName)
139        if queryInterface is None:
140            raise SOAPAttributeInterfaceMiddlewareConfigError(
141                                'No query interface "%s" key found in environ'%
142                                self.queryInterfaceKeyName)
143       
144        # Call query interface       
145        samlResponse = queryInterface(attributeQuery)
146       
147        # Convert to ElementTree representation to enable attachment to SOAP
148        # response body
149        samlResponseElem = ResponseElementTree.toXML(samlResponse,
150                                            customToXMLTypeMap=toXMLTypeMap)
151        xml = ElementTree.tostring(samlResponseElem)
152       
153        # Create SOAP response and attach the SAML Response payload
154        soapResponse = SOAPEnvelope()
155        soapResponse.create()
156        soapResponse.body.elem.append(samlResponseElem)
157       
158        response = soapResponse.serialize()
159       
160        start_response("200 OK",
161                       [('Content-length', str(len(response))),
162                        ('Content-type', 'text/xml')])
163        return [response]
164   
165    @classmethod
166    def isAttributeQuery(cls, soapBody):
167        """Check for AttributeQuery in the SOAP Body"""
168       
169        if len(soapBody.elem) != 1:
170            # TODO: Change to a SOAP Fault?
171            raise SOAPAttributeInterfaceMiddlewareError("Expecting single "
172                                                        "child element in the "
173                                                        "request SOAP "
174                                                        "Envelope body")
175       
176        return QName(soapBody.elem.tag) == AttributeQuery.DEFAULT_ELEMENT_NAME
Note: See TracBrowser for help on using the repository browser.