Changeset 4172 for TI12-security


Ignore:
Timestamp:
03/09/08 21:47:50 (11 years ago)
Author:
pjkersha
Message:

Refactoring of SOAP and WS-Security middleware for better error handling and abstraction of functions

Location:
TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/soap.py

    r4171 r4172  
    4848        if 'path' not in self.app_conf: 
    4949            self.app_conf['path'] = '/' 
    50          
     50        
     51    def __call__(self, environ, start_response): 
     52        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                 
     68        # 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 
     75        # 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): 
     82        '''Convert an exception into a SOAP fault message''' 
     83        soapFault = fault.FaultFromException(e, None) 
     84        sw = SoapWriter() 
     85        soapFault.serialize(sw) 
     86        return sw 
     87     
     88    pathMatch = lambda self, environ: \ 
     89                        environ['PATH_INFO']==self.app_conf['path'].rstrip('/') 
     90         
     91    @staticmethod 
     92    def isSOAPMessage(cls, environ): 
     93        '''Generic method to filter out non-soap messages''' 
     94        return environ.get('REQUEST_METHOD', '') == 'POST' and \ 
     95               environ.get('HTTP_SOAPACTION') is not None 
     96         
     97    @classmethod 
     98    def parse(cls, environ): 
     99        '''Parse SOAP message from environ['wsgi.input'] 
     100         
     101        Reading from environ['wsgi.input'] may be a destructive process so the 
     102        content is saved in a ZSI.parse.ParsedSoap object for use by SOAP 
     103        handlers which follow in the chain 
     104         
     105        environ['ZSI.parse.ParsedSoap'] may be set to a ParsedSoap object 
     106        parsed by a SOAP handler ahead of the current one in the chain.  In 
     107        this case, don't re-parse.  If NOT parsed, parse and set 
     108        'ZSI.parse.ParsedSoap' environ key''' 
     109         
     110        # Check for ParsedSoap object set in environment, if not present, 
     111        # make one 
     112        ps = environ.get(cls.parsedSOAPKey) 
     113        if ps is None: 
     114            # TODO: allow for chunked data 
     115            contentLength = int(environ['CONTENT_LENGTH']) 
     116            soapIn = environ['wsgi.input'].read(contentLength) 
     117            if len(soapIn) < contentLength: 
     118                raise SOAPMiddlewareReadError("Expecting %s content length; " 
     119                                              "received %d instead." % \ 
     120                                              (environ['CONTENT_LENGTH'], 
     121                                               len(soapIn))) 
     122             
     123            log.debug("SOAP Request for handler %r" % cls) 
     124            log.debug("_"*80) 
     125            log.debug(soapIn) 
     126            log.debug("_"*80) 
     127             
     128            ps = ParsedSoap(soapIn) 
     129            environ[cls.parsedSOAPKey] = ps 
     130             
     131        return environ[cls.parsedSOAPKey] 
     132     
     133    @classmethod 
     134    def getSOAPWriter(cls, environ): 
     135        '''Access SoapWriter object set in environment by this classes' call 
     136        method''' 
     137         
     138        sw = environ.get(SOAPMiddleware.soapWriterKey) 
     139        if sw is None: 
     140            raise KeyError("Expecting '%s' key in environ: missing call to " 
     141                           "SOAPMiddleware?" % SOAPMiddleware.soapWriterKey) 
     142        return sw 
     143     
     144      
     145def makeFilter(app, app_conf):   
     146    return SOAPMiddleware(app, app_conf) 
     147 
     148 
     149class SOAPBindingMiddleware(SOAPMiddleware):   
     150    '''Apply a ZSI ServiceSOAPBinding type SOAP service''' 
     151          
     152    def __init__(self, *arg, **kw): 
     153        super(SOAPMiddleware, self).__init__(*arg, **kw) 
     154         
     155        # Check for Service binding in config 
    51156        if 'ServiceSOAPBindingClass' in self.app_conf: 
    52157            modName, dot, className = \ 
    53                 self.app_conf['ServiceSOAPBindingClass'].rpartition('.') 
     158                    self.app_conf['ServiceSOAPBindingClass'].rpartition('.') 
    54159             
    55160            module = __import__(modName, globals(), locals(), [className]) 
     
    65170                  
    66171        self.serviceSOAPBinding = serviceSOAPBindingClass() 
     172         
     173        # Flag to enable display of WSDL via wsdl query arg in a GET request 
    67174        self.enableWSDLQuery = self.app_conf.get('enableWSDLQuery', False) and\ 
    68175                                hasattr(self.serviceSOAPBinding, '_wsdl') 
    69176 
    70         
     177 
    71178    def __call__(self, environ, start_response): 
    72         log.debug("SOAPMiddleware.__call__") 
     179        log.debug("SOAPBindingMiddleware.__call__") 
    73180         
    74181        charset_ = self.app_conf['charset'] 
     
    77184             
    78185            log.debug("Altering content-type to text/xml...") 
    79             contentKeys = ('content-type',)#('content-type', 'content-length') 
     186            contentKeys = ('content-type', 'content-length') 
    80187            response_hdrs_alt = [(name, val) for name, val in response_hdrs\ 
    81188                                 if name.lower() not in contentKeys] 
    82189             
    83             response_hdrs_alt += [('content-type', 'text/xml'+charset_)] 
     190            response_hdrs_alt += [('content-type', 'text/xml'+charset_), 
     191                                  ('content-length', str(len(soapOut_)))] 
    84192 
    85193                             
     
    96204                 
    97205        # Apply filter for calls 
    98         if not self.isSOAPMessage(environ) or \ 
    99            not environ['PATH_INFO'].startswith(self.app_conf['path']): 
     206        if not self.isSOAPMessage(environ) or not self.pathMatch(environ): 
    100207            return self.app(environ, start_response) 
    101208 
    102         ps = self.parse(environ) 
    103              
    104         # Map SOAP Action to method in binding class 
    105         method = getattr(self.serviceSOAPBinding,  
    106                          'soap_%s' % environ['HTTP_SOAPACTION'].strip('"')) 
    107          
    108         # TODO: change method to return response only: request, response tuple 
    109         # is carry over from Twisted based code 
    110209        try: 
     210            ps = self.parse(environ) 
     211             
     212            # Map SOAP Action to method in binding class 
     213            soapMethodName = 'soap_%s' % environ['HTTP_SOAPACTION'].strip('"') 
     214             
     215            method = getattr(self.serviceSOAPBinding, soapMethodName) 
     216            # TODO: change method to return response only: request, response  
     217            # tuple is carry over from Twisted based code 
    111218            req, resp = method(ps) 
    112219        except Exception, e: 
     
    127234 
    128235        return self.app(environ, start_response_wrapper) 
    129  
    130  
    131     def exception2SOAPFault(self, e): 
    132         '''Convert an exception into a SOAP fault message''' 
    133         soapFault = fault.FaultFromException(e, None) 
    134         sw = SoapWriter() 
    135         soapFault.serialize(sw) 
    136         return sw 
    137      
    138          
    139     @classmethod 
    140     def isSOAPMessage(cls, environ): 
    141         '''Generic method to filter out non-soap messages''' 
    142         return environ.get('REQUEST_METHOD', '') == 'POST' and \ 
    143                environ.get('HTTP_SOAPACTION') is not None 
    144          
    145     @classmethod 
    146     def parse(cls, environ): 
    147         '''Parse SOAP message from environ['wsgi.input'] 
    148          
    149         Reading from environ['wsgi.input'] may be a destructive process so the 
    150         content is saved in a ZSI.parse.ParsedSoap object for use by SOAP 
    151         handlers which follow in the chain 
    152          
    153         environ['ZSI.parse.ParsedSoap'] may be set to a ParsedSoap object 
    154         parsed by a SOAP handler ahead of the current one in the chain.  In 
    155         this case, don't re-parse.  If NOT parsed, parse and set 
    156         'ZSI.parse.ParsedSoap' environ key''' 
    157          
    158         # Check for ParsedSoap object set in environment, if not present, 
    159         # make one 
    160         ps = environ.get(SOAPMiddleware.parsedSOAPKey) 
    161         if ps is None: 
    162             # TODO: allow for chunked data 
    163             contentLength = int(environ['CONTENT_LENGTH']) 
    164             soapIn = environ['wsgi.input'].read(contentLength) 
    165             if len(soapIn) < contentLength: 
    166                 raise SOAPMiddlewareReadError("Expecting %s content length; " 
    167                                               "received %d instead." % \ 
    168                                               (environ['CONTENT_LENGTH'], 
    169                                                len(soapIn))) 
    170              
    171             log.debug("SOAP Request for handler %r" % cls) 
    172             log.debug("_"*80) 
    173             log.debug(soapIn) 
    174             log.debug("_"*80) 
    175              
    176             ps = ParsedSoap(soapIn) 
    177             environ[SOAPMiddleware.parsedSOAPKey] = ps 
    178              
    179         return environ[SOAPMiddleware.parsedSOAPKey] 
    180      
    181     @classmethod 
    182     def getSOAPWriter(cls, environ): 
    183         '''Access SoapWriter object set in environment by this classes' call 
    184         method''' 
    185          
    186         sw = environ.get(SOAPMiddleware.soapWriterKey) 
    187         if sw is None: 
    188             raise KeyError("Expecting '%s' key in environ: missing call to " 
    189                            "SOAPMiddleware?" % SOAPMiddleware.soapWriterKey) 
    190         return sw 
    191      
    192       
    193 def makeFilter(app, app_conf):   
    194     return SOAPMiddleware(app, app_conf) 
  • TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/wssecurity.py

    r4171 r4172  
    2929     
    3030class WSSecurityFilter(SOAPMiddleware): 
    31      
     31    """Base class for WS-Security filters""" 
    3232    def __init__(self, app, app_conf): 
    3333        self.app = app 
    34         wsseCfgFilePath = app_conf.get('wsseCfgFilePath') 
     34        self.app_conf = app_conf 
     35 
     36class WSSecuritySignatureFilter(WSSecurityFilter): 
     37    """Base class for WS-Security signature and signature verification filters 
     38    """ 
     39    def __init__(self, app, app_conf): 
     40        super(WSSecuritySignatureFilter).__init__(app, app_conf) 
     41         
     42        wsseCfgFilePath = self.app_conf.get('wsseCfgFilePath') 
    3543        if not wsseCfgFilePath: 
    3644            raise WSSecurityFilterConfigError("No configuration file set") 
    3745         
    3846        self.signatureHandler = SignatureHandler(cfg=wsseCfgFilePath) 
     47            
    3948     
    40      
    41 class SignatureFilter(WSSecurityFilter): 
     49class SignatureFilter(WSSecuritySignatureFilter): 
    4250    '''Apply WS-Security digital signature to SOAP message''' 
    4351    def __call__(self, environ, start_response): 
     
    5058        sw = self.getSOAPWriter(environ) 
    5159        self.signatureHandler.sign(sw) 
    52         self.soapOut = str(sw) 
     60        soapOut_ = str(sw) 
    5361         
    54 #        try: 
    55 #            start_response('200 OK',  
    56 #                           [('Content-Length', str(len(self.soapOut))), 
    57 #                            ('Content-type', 'text/xml')]) 
    58 #        except Exception, e: 
    59 #            raise 
    60         return self.soapOut 
     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 
    6178     
    6279 
    63 class SignatureVerificationFilter(WSSecurityFilter): 
     80class SignatureVerificationFilter(WSSecuritySignatureFilter): 
    6481    '''Verify WS-Security digital signature in SOAP message''' 
    6582     
Note: See TracChangeset for help on using the changeset viewer.