Changeset 4185 for TI12-security/trunk


Ignore:
Timestamp:
09/09/08 15:19:47 (11 years ago)
Author:
pjkersha
Message:

Refined SOAP and WS-Security WSGI middleware. TODO: pass all Attribute Authority unit tests and integrate Session Manager.

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

Legend:

Unmodified
Added
Removed
  • TI12-security/trunk/python/Tests/pylonsAttributeAuthority/ndgsecurity/development.ini

    r4171 r4185  
    1717port = 5000 
    1818 
    19 [app:main1] 
     19[app:mainApp] 
    2020use = egg:ndgsecurity 
    2121full_stack = true 
     
    2424beaker.session.secret = somesecret 
    2525 
     26# Chain of SOAP Middleware filters 
    2627[pipeline:main] 
    27 pipeline = wsseSignatureVerificationFilter AttributeAuthorityFilter wsseSignatureFilter main1 
     28pipeline = wsseSignatureVerificationFilter AttributeAuthorityFilter wsseSignatureFilter mainApp 
    2829 
    2930 
    3031[filter:AttributeAuthorityFilter] 
    31 paste.filter_app_factory = ndg.security.server.wsgi.soap:SOAPMiddleware 
     32paste.filter_app_factory = ndg.security.server.wsgi.soap:SOAPBindingMiddleware 
    3233ServiceSOAPBindingClass = ndg.security.server.zsi.attributeauthority.AttributeAuthorityWS 
    33 path = /AttributeAuthority 
     34path = /AttributeAuthority/ 
    3435enableWSDLQuery = True 
    3536charset = utf-8 
    3637 
    3738[filter:wsseSignatureVerificationFilter] 
    38 paste.filter_app_factory = ndg.security.server.wsgi.wssecurity:makeSignatureVerificationFilter 
     39paste.filter_app_factory = ndg.security.server.wsgi.wssecurity:SignatureVerificationFilter 
    3940 
    4041[filter:wsseSignatureFilter] 
    41 paste.filter_app_factory = ndg.security.server.wsgi.wssecurity:makeSignatureFilter 
     42paste.filter_app_factory = ndg.security.server.wsgi.wssecurity:ApplySignatureFilter 
     43# Last filter in chain SOAP handlers writes the response 
     44writeResponse = True 
    4245 
    4346# If you'd like to fine-tune the individual locations of the cache data dirs 
  • TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/soap.py

    r4172 r4185  
    3333    soapWriterKey = 'ZSI.writer.SoapWriter' 
    3434    parsedSOAPKey = 'ZSI.parse.ParsedSoap' 
     35    soapFaultSetKey = 'ndg.security.server.wsgi.soap.soapFault' 
    3536     
    3637    def __init__(self, app, app_conf, **kw): 
     
    4142         
    4243        if 'charset' in self.app_conf: 
    43             self.app_conf['charset'] = '; ' + self.app_conf['charset'] 
     44            self.app_conf['charset'] = '; charset=' + self.app_conf['charset'] 
    4445        else: 
    4546            self.app_conf['charset'] = '; charset=utf-8' 
    4647 
    47              
    48         if 'path' not in self.app_conf: 
     48        if 'path' in self.app_conf: 
     49            if self.app_conf['path'] != '/': 
     50                self.app_conf['path'] = self.app_conf['path'].rstrip('/') 
     51        else: 
    4952            self.app_conf['path'] = '/' 
    50         
     53 
     54        # This flag if set to True causes this handler to call the  
     55        # start_response method and output the SOAP response 
     56        self.writeResponseSet = bool(self.app_conf.get('writeResponse', False)) 
     57 
     58 
    5159    def __call__(self, environ, start_response): 
    5260        log.debug("SOAPMiddleware.__call__") 
    53          
    54         charset_ = self.app_conf['charset'] 
    55         def start_response_wrapper(status, response_hdrs, exc_info=None): 
    56             '''Ensure text/xml content type and set content length''' 
    57              
    58             log.debug("Altering content-type to text/xml...") 
    59             contentKeys = ('content-type', 'content-length') 
    60             response_hdrs_alt = [(name, val) for name, val in response_hdrs\ 
    61                                  if name.lower() not in contentKeys] 
    62              
    63             response_hdrs_alt += [('content-type', 'text/xml'+charset_)] 
    64  
    65                              
    66             return start_response(status, response_hdrs_alt, exc_info) 
    67                  
     61                         
    6862        # Apply filter for calls 
    69         if not self.isSOAPMessage(environ) or not self.pathMatch(environ): 
    70             return self.app(environ, start_response) 
    71  
    72         self.parse(environ) 
    73  
    74         # Derived class must implement SOAP Response to overloaded version of 
     63        if not self.isSOAPMessage(environ): 
     64            log.debug("SOAPMiddleware.__call__: skipping non-SOAP call") 
     65            return self.app(environ, start_response) 
     66         
     67        elif not self.pathMatch(environ): 
     68            log.debug("SOAPMiddleware.__call__: path doesn't match SOAP " 
     69                      "service endpoint") 
     70            return self.app(environ, start_response) 
     71         
     72        elif self.isSOAPFaultSet(environ): 
     73            # This MUST be checked in a overloaded version especially in  
     74            # consideration of security: e.g. an upstream signature  
     75            # verification may have found an error in a signature 
     76            log.debug("SOAPMiddleware.__call__: SOAP fault set by previous " 
     77                      "SOAP middleware filter") 
     78            return self.app(environ, start_response) 
     79 
     80        self.parseRequest(environ) 
     81 
     82        # Derived class must implement SOAP Response via overloaded version of 
    7583        # this method.  ParsedSoap object is available as a key in environ via 
    76         # the parse method 
    77          
    78         return self.app(environ, start_response_wrapper) 
    79  
    80     @staticmethod 
    81     def exception2SOAPFault(e): 
     84        # the parseRequest method 
     85         
     86        return self.writeResponse(environ, start_response) 
     87 
     88    @classmethod 
     89    def exception2SOAPFault(cls, environ, exception): 
    8290        '''Convert an exception into a SOAP fault message''' 
    83         soapFault = fault.FaultFromException(e, None) 
     91        soapFault = fault.FaultFromException(exception, None) 
    8492        sw = SoapWriter() 
    8593        soapFault.serialize(sw) 
     94        environ[cls.soapFaultSetKey] = 'True' 
    8695        return sw 
    8796     
    88     pathMatch = lambda self, environ: \ 
    89                         environ['PATH_INFO']==self.app_conf['path'].rstrip('/') 
     97    pathMatch = lambda self,environ:environ['PATH_INFO']==self.app_conf['path'] 
    9098         
    9199    @staticmethod 
    92     def isSOAPMessage(cls, environ): 
    93         '''Generic method to filter out non-soap messages''' 
     100    def isSOAPMessage(environ): 
     101        '''Generic method to filter out non-soap messages 
     102         
     103        TODO: is HTTP_SOAPACTION only set for WSDL doc-literal wrapped style 
     104        generated content? - If so this test should be moved''' 
    94105        return environ.get('REQUEST_METHOD', '') == 'POST' and \ 
    95106               environ.get('HTTP_SOAPACTION') is not None 
    96          
    97     @classmethod 
    98     def parse(cls, environ): 
     107 
     108    @classmethod 
     109    def isSOAPFaultSet(cls, environ): 
     110        '''Check environment for SOAP fault flag set.  This variable is set 
     111        from exception2SOAPFault''' 
     112        return bool(environ.get(cls.soapFaultSetKey, False)) == True 
     113     
     114    @classmethod 
     115    def parseRequest(cls, environ): 
    99116        '''Parse SOAP message from environ['wsgi.input'] 
    100117         
     
    130147             
    131148        return environ[cls.parsedSOAPKey] 
    132      
     149 
     150 
     151    def writeResponse(self, environ, start_response, errorCode=None): 
     152        '''This method serializes the SOAP output and sets the response header. 
     153        It's the final step and should be called in the last SOAP handler in  
     154        a chain of handlers or else specify it in the ini file as the last 
     155        SOAP handler''' 
     156         
     157        # This flag must be set to True to write out the final response from 
     158        # this handler 
     159        if self.writeResponseSet == False: 
     160            return self.app(environ, start_response) 
     161         
     162        sw = self.getSOAPWriter(environ) 
     163        soapOut = str(sw) 
     164        charset = self.app_conf['charset'] 
     165         
     166        if errorCode is None: 
     167            if self.isSOAPFaultSet(environ): 
     168                errorCode = "500 Internal Server Error" 
     169            else: 
     170                errorCode = "200 OK" 
     171                 
     172        start_response(errorCode, 
     173                       [('content-type', 'text/xml'+charset), 
     174                        ('content-length', str(len(soapOut)))]) 
     175        return soapOut 
     176 
    133177    @classmethod 
    134178    def getSOAPWriter(cls, environ): 
     
    142186        return sw 
    143187     
    144       
    145 def makeFilter(app, app_conf):   
    146     return SOAPMiddleware(app, app_conf) 
     188    @classmethod 
     189    def setSOAPWriter(cls, environ, sw): 
     190        '''Set SoapWriter object in environment'''    
     191        environ[SOAPMiddleware.soapWriterKey] = sw 
    147192 
    148193 
     
    151196          
    152197    def __init__(self, *arg, **kw): 
    153         super(SOAPMiddleware, self).__init__(*arg, **kw) 
     198        super(SOAPBindingMiddleware, self).__init__(*arg, **kw) 
    154199         
    155200        # Check for Service binding in config 
     
    159204             
    160205            module = __import__(modName, globals(), locals(), [className]) 
    161             serviceSOAPBindingClass = eval('module.' + className) 
     206            serviceSOAPBindingClass = getattr(module, className) 
    162207            
    163208            # Check class inherits from ServiceSOAPBinding 
     
    178223    def __call__(self, environ, start_response): 
    179224        log.debug("SOAPBindingMiddleware.__call__") 
    180          
    181         charset_ = self.app_conf['charset'] 
    182         def start_response_wrapper(status, response_hdrs, exc_info=None): 
    183             '''Ensure text/xml content type and set content length''' 
    184              
    185             log.debug("Altering content-type to text/xml...") 
    186             contentKeys = ('content-type', 'content-length') 
    187             response_hdrs_alt = [(name, val) for name, val in response_hdrs\ 
    188                                  if name.lower() not in contentKeys] 
    189              
    190             response_hdrs_alt += [('content-type', 'text/xml'+charset_), 
    191                                   ('content-length', str(len(soapOut_)))] 
    192  
    193                              
    194             return start_response(status, response_hdrs_alt, exc_info) 
    195          
     225                 
    196226        if environ.get('REQUEST_METHOD') == 'GET' and \ 
    197227           environ.get('QUERY_STRING') == 'wsdl': 
     
    204234                 
    205235        # Apply filter for calls 
    206         if not self.isSOAPMessage(environ) or not self.pathMatch(environ): 
     236        if not self.isSOAPMessage(environ) or \ 
     237           not self.pathMatch(environ) or \ 
     238           self.isSOAPFaultSet(environ): 
    207239            return self.app(environ, start_response) 
    208240 
    209241        try: 
    210             ps = self.parse(environ) 
     242            ps = self.parseRequest(environ) 
    211243             
    212244            # Map SOAP Action to method in binding class 
     
    214246             
    215247            method = getattr(self.serviceSOAPBinding, soapMethodName) 
     248             
    216249            # TODO: change method to return response only: request, response  
    217250            # tuple is carry over from Twisted based code 
    218251            req, resp = method(ps) 
    219252        except Exception, e: 
    220             sw = self.exception2SOAPFault(e) 
     253            sw = self.exception2SOAPFault(environ, e) 
    221254        else:  
    222255            # Serialize output using SOAP writer class 
     
    225258         
    226259        # Make SoapWriter object available to any SOAP filters that follow 
    227         environ[SOAPMiddleware.soapWriterKey] = sw 
    228         self.soapOut = str(sw) 
    229          
     260        self.setSOAPWriter(environ, sw) 
     261         
     262        soapOut = str(sw) 
     263        charset = self.app_conf['charset'] 
     264                 
    230265        log.debug("SOAP Response") 
    231266        log.debug("_"*80) 
    232         log.debug(self.soapOut) 
     267        log.debug(soapOut) 
    233268        log.debug("_"*80) 
    234269 
    235         return self.app(environ, start_response_wrapper) 
     270        return self.writeResponse(environ, start_response) 
  • TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/wssecurity.py

    r4172 r4185  
    2929     
    3030class WSSecurityFilter(SOAPMiddleware): 
    31     """Base class for WS-Security filters""" 
    32     def __init__(self, app, app_conf): 
    33         self.app = app 
    34         self.app_conf = app_conf 
     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    pathMatch = lambda self, environ: \ 
     36                        environ['PATH_INFO'].startswith(self.app_conf['path']) 
    3537 
    36 class WSSecuritySignatureFilter(WSSecurityFilter): 
     38class SignatureFilter(WSSecurityFilter): 
    3739    """Base class for WS-Security signature and signature verification filters 
    3840    """ 
    39     def __init__(self, app, app_conf): 
    40         super(WSSecuritySignatureFilter).__init__(app, app_conf) 
     41    def __init__(self, app, app_conf, **kw): 
     42        super(SignatureFilter, self).__init__(app, app_conf, **kw) 
    4143         
    4244        wsseCfgFilePath = self.app_conf.get('wsseCfgFilePath') 
     
    4749            
    4850     
    49 class SignatureFilter(WSSecuritySignatureFilter): 
     51class ApplySignatureFilter(SignatureFilter): 
    5052    '''Apply WS-Security digital signature to SOAP message''' 
    5153    def __call__(self, environ, start_response): 
    52         if not self.isSOAPMessage(environ): 
     54        '''Sign message''' 
     55        if not self.isSOAPMessage(environ) or \ 
     56           not self.pathMatch(environ): 
     57            log.debug("ApplySignatureFilter.__call__: Non-SOAP " 
     58                      "request or path doesn't match SOAP endpoint specified " 
     59                      "- skipping signature verification") 
    5360            return self.app(environ, start_response) 
    5461         
    5562        log.debug('Signing outbound message ...') 
    56         app = self.app(environ, start_response) 
    57  
    58         sw = self.getSOAPWriter(environ) 
    59         self.signatureHandler.sign(sw) 
    60         soapOut_ = str(sw) 
     63        if self.isSOAPFaultSet(environ): 
     64            # TODO: If the Signature handler is signing any sub-elements in the 
     65            # message body this is going to run into problems because the  
     66            # fault content is obviously going to be different. 
     67            # TODO: Should SOAP faults be signed at all? 
     68            log.warning("Attempting to sign a SOAP fault message...") 
     69             
     70        try: 
     71            sw = self.getSOAPWriter(environ) 
     72        except Exception, e: 
     73            sw = self.exception2SOAPFault(environ, e) 
     74            self.setSOAPWriter(environ, sw) 
     75             
     76        try: 
     77            self.signatureHandler.sign(sw) 
     78        except Exception, e: 
     79            sw = self.exception2SOAPFault(environ, e) 
     80            self.setSOAPWriter(environ, sw) 
    6181         
    62         def start_response_wrapper(status, response_hdrs, exc_info=None): 
    63             '''Ensure correct content length''' 
    64              
    65             log.debug("Altering content-length to allow for signature...") 
    66             contentKeys = ('content-length', ) 
    67             response_hdrs_alt = [(name, val) for name, val in response_hdrs\ 
    68                                  if name.lower() not in contentKeys] 
    69              
    70             response_hdrs_alt += [('content-length', str(len(soapOut_)))] 
    71       
    72             return start_response(status, response_hdrs_alt, exc_info) 
    73  
    74         def app(environ, start_response_wrapper): 
    75             return soapOut_ 
    76          
    77         return app 
     82        return self.writeResponse(environ, start_response) 
    7883     
    7984 
    80 class SignatureVerificationFilter(WSSecuritySignatureFilter): 
     85class SignatureVerificationFilter(SignatureFilter): 
    8186    '''Verify WS-Security digital signature in SOAP message''' 
    8287     
    8388    def __call__(self, environ, start_response): 
    84         if not self.isSOAPMessage(environ): 
    85             log.debug("Non-SOAP request: Skipping signature verification") 
     89        '''Verify message signature''' 
     90        if not self.isSOAPMessage(environ) or \ 
     91           not self.pathMatch(environ): 
     92            log.debug("SignatureVerificationFilter.__call__: Non-SOAP " 
     93                      "request or path doesn't match SOAP endpoint specified " 
     94                      "- skipping signature verification") 
    8695            return self.app(environ, start_response) 
    8796 
    8897        log.debug("Verifying inbound message signature...") 
    89         
    90         ps = self.parse(environ) 
    91         self.signatureHandler.verify(ps) 
    92          
    93         # Pass on in environment as an efficiency measure for any following 
    94         # SOAP Middleware 
    95         return self.app(environ, start_response) 
    9698 
    97  
    98 def makeSignatureVerificationFilter(app, global_conf): 
    99     return SignatureVerificationFilter(app, global_conf)  
    100  
    101 def makeSignatureFilter(app, global_conf): 
    102     return SignatureFilter(app, global_conf) 
     99        try: 
     100            ps = self.parseRequest(environ) 
     101            self.signatureHandler.verify(ps) 
     102        except Exception, e: 
     103            sw = self.exception2SOAPFault(environ, e) 
     104            self.setSOAPWriter(environ, sw) 
     105             
     106        return self.writeResponse(environ, start_response) 
Note: See TracChangeset for help on using the changeset viewer.