source: TI12-security/trunk/NDGSecurity/python/ndg_security_server/ndg/security/server/wsgi/wssecurity.py @ 6586

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg-security/TI12-security/trunk/NDGSecurity/python/ndg_security_server/ndg/security/server/wsgi/wssecurity.py@6586
Revision 6586, 9.1 KB checked in by pjkersha, 11 years ago (diff)

Started ESG Authorisation Service implementation ndg.security.server.wsgi.authorizationservice - SAML SOAP based interface to a Policy Decision Point enabling centralised policy for a range of services.

Line 
1"""WSGI Middleware for WS-Security
2
3Implements Digital Signature handling based around ZSI
4
5NERC Data Grid Project"""
6__author__ = "P J Kershaw"
7__date__ = "11/06/08"
8__copyright__ = "(C) 2009 Science and Technology Facilities Council"
9__license__ = "BSD - see LICENSE file in top-level directory"
10__contact__ = "Philip.Kershaw@stfc.ac.uk"
11__revision__ = '$Id: $'
12
13import logging
14log = logging.getLogger(__name__)
15
16from ZSI.parse import ParsedSoap
17
18from ZSI.writer import SoapWriter
19from ndg.security.common.wssecurity.signaturehandler.foursuite import \
20    SignatureHandler
21from ndg.security.server.wsgi.zsi import ZSIMiddleware, ZSIMiddlewareError
22
23class WSSecurityFilterError(ZSIMiddlewareError):
24    """Base exception class for WS-Security WSGI Filter"""
25    _log = log
26   
27class WSSecurityFilterConfigError(WSSecurityFilterError):
28    """WS-Security Filter Config Error"""
29 
30class WSSecurityFilter(ZSIMiddleware):
31    """Base class for WS-Security filters
32   
33    Overload pathMatch lambda so that it is more inclusive: the default is
34    for all paths to be processed by the handlers"""
35    def pathMatch(self, environ): 
36        if environ['PATH_INFO'].endswith('/'):
37            path == environ['PATH_INFO']
38        else:
39            path = environ['PATH_INFO'] + '/'
40           
41        return path == self.path
42
43
44class SignatureFilter(WSSecurityFilter):
45    """Base class for WS-Security signature and signature verification filters
46    """
47    WSSE_CFG_FILEPATH_OPTNAME = 'wsseCfgFilePath'
48    WSSE_CFG_FILE_SECTION_OPTNAME = 'wsseCfgFileSection'
49    WSSE_CFG_FILE_PREFIX_OPTNAME = 'wsseCfgFilePrefix'
50   
51    def __init__(self, app):
52        super(SignatureFilter, self).__init__(app)
53        self.__signatureHandler = None
54       
55    def initialise(self, global_conf, prefix='', **app_conf):
56        """Set-up Signature filter attributes using a Paste app factory
57        pattern. 
58       
59        @type global_conf: dict       
60        @param global_conf: PasteDeploy global configuration dictionary
61        @type prefix: basestring
62        @param prefix: prefix for configuration items
63        @type app_conf: dict       
64        @param app_conf: PasteDeploy application specific configuration
65        dictionary
66        """
67        super(SignatureFilter, self).initialise(global_conf, **app_conf)
68
69        # Where possible remove keywords not applicable to SignatureHandler
70        wsseCfgFilePath = app_conf.pop(
71                        prefix+SignatureFilter.WSSE_CFG_FILEPATH_OPTNAME, 
72                        None)
73        wsseCfgFileSection = app_conf.pop(
74                        prefix+SignatureFilter.WSSE_CFG_FILE_SECTION_OPTNAME, 
75                        None)
76        wsseCfgFilePrefix = app_conf.pop(
77                        prefix+SignatureFilter.WSSE_CFG_FILE_PREFIX_OPTNAME, 
78                        None)
79       
80        self.signatureHandler = SignatureHandler(cfg=wsseCfgFilePath,
81                                            cfgFileSection=wsseCfgFileSection,
82                                            cfgFilePrefix=wsseCfgFilePrefix,
83                                            **app_conf)
84
85    def _getSignatureHandler(self):
86        return self.__signatureHandler
87
88    def _setSignatureHandler(self, value):
89        if not isinstance(value, SignatureHandler):
90            raise TypeError('Expecting %r for "signatureHandler"; got %r' %
91                            (SignatureHandler, type(value)))
92        self.__signatureHandler = value
93
94    signatureHandler = property(_getSignatureHandler, 
95                                _setSignatureHandler, 
96                                doc="Signature Handler Class")
97           
98   
99class ApplySignatureFilter(SignatureFilter):
100    '''Apply WS-Security digital signature to SOAP message'''
101    WSSE_SIGNATURE_VERIFICATION_FILTERID_OPTNAME = \
102        'wsseSignatureVerificationFilterID'
103       
104    def __init__(self, app):
105        super(ApplySignatureFilter, self).__init__(app)
106        self.__wsseSignatureVerificationFilterID = None
107       
108    def initialise(self, global_conf, **app_conf):
109        """Set-up Signature filter attributes using a Paste app factory
110        pattern. 
111       
112        @type global_conf: dict       
113        @param global_conf: PasteDeploy global configuration dictionary
114        @type prefix: basestring
115        @param prefix: prefix for configuration items
116        @type app_conf: dict       
117        @param app_conf: PasteDeploy application specific configuration
118        dictionary
119        """
120        super(ApplySignatureFilter, self).initialise(global_conf, **app_conf)
121        self.wsseSignatureVerificationFilterID = app_conf.pop(
122            ApplySignatureFilter.WSSE_SIGNATURE_VERIFICATION_FILTERID_OPTNAME,
123            '')
124
125    def _getWsseSignatureVerificationFilterID(self):
126        return self.__wsseSignatureVerificationFilterID
127
128    def _setWsseSignatureVerificationFilterID(self, value):
129        if not isinstance(value, basestring):
130            raise TypeError('Expecting string for '
131                            '"wsseSignatureVerificationFilterID"; got %r' %
132                            type(value))
133        self.__wsseSignatureVerificationFilterID = value
134
135    wsseSignatureVerificationFilterID = property(
136                                        _getWsseSignatureVerificationFilterID, 
137                                        _setWsseSignatureVerificationFilterID, 
138                                        doc="Keyword to reference the "
139                                            "Signature Verification filter if "
140                                            "any in the WSGI environ")
141
142    def __call__(self, environ, start_response):
143        '''Sign message'''
144        if not ApplySignatureFilter.isSOAPMessage(environ) or \
145           not self.pathMatch(environ):
146            log.debug("ApplySignatureFilter.__call__: Non-SOAP request or "
147                      "path doesn't match SOAP endpoint specified - skipping "
148                      "signature verification")
149            return self._app(environ, start_response)
150       
151        log.debug('Signing outbound message ...')
152        if ApplySignatureFilter.isSOAPFaultSet(environ):
153            # TODO: If the Signature handler is signing any sub-elements in the
154            # message body this is going to run into problems because the
155            # fault content is obviously going to be different.
156            # TODO: Should SOAP faults be signed at all?
157            log.warning("Attempting to sign a SOAP fault message...")
158         
159        # The following is broken into two try blocks so that exceptions
160        # raised from the 1st can still returned as signed SOAP faults back to
161        # the client
162        try:
163            sw = ApplySignatureFilter.getSOAPWriter(environ)
164           
165            # Copy signature value in order to apply signature confirmation
166            if self.signatureHandler.applySignatureConfirmation:
167                filter = environ.get(self.wsseSignatureVerificationFilterID)
168                if filter is None:
169                    raise WSSecurityFilterConfigError(
170                        'SignatureHandler "applySignatureConfirmation" flag '
171                        'is set to True but no Signature Verification Filter '
172                        'has been set in the environ: check that the '
173                        '"wsseSignatureVerificationFilterID" property is set '
174                        'and that it references the "filterID" set for the '
175                        'verification filter')
176                   
177                self.signatureHandler.b64EncSignatureValue = \
178                                filter.signatureHandler.b64EncSignatureValue
179        except Exception, e:
180            sw = self.exception2SOAPFault(environ, e)
181            ApplySignatureFilter.setSOAPWriter(environ, sw)
182         
183        try:
184            self.signatureHandler.sign(sw)
185        except Exception, e:
186            sw = self.exception2SOAPFault(environ, e)
187            ApplySignatureFilter.setSOAPWriter(environ, sw)
188       
189        return self.writeResponse(environ, start_response)
190   
191
192class SignatureVerificationFilter(SignatureFilter):
193    '''Verify WS-Security digital signature in SOAP message'''
194   
195    def __call__(self, environ, start_response):
196        '''Verify message signature'''
197        if (not SignatureVerificationFilter.isSOAPMessage(environ) or 
198           not self.pathMatch(environ)):
199            log.debug("SignatureVerificationFilter.__call__: Non-SOAP "
200                      "request or path doesn't match SOAP endpoint specified "
201                      "- skipping signature verification")
202            return self._app(environ, start_response)
203
204        log.debug("Verifying inbound message signature...")
205
206        # Add a reference to this filter in environ so that other middleware
207        # can reference it
208        self.addFilter2Environ(environ)
209       
210        try:
211            ps = self.parseRequest(environ)
212            self.signatureHandler.verify(ps)
213        except Exception, e:
214            sw = self.exception2SOAPFault(environ, e)
215            SignatureVerificationFilter.setSOAPWriter(environ, sw)
216           
217        return self.writeResponse(environ, start_response)
Note: See TracBrowser for help on using the repository browser.