Changeset 4838 for TI12-security


Ignore:
Timestamp:
19/01/09 12:00:54 (11 years ago)
Author:
pjkersha
Message:
  • Added LICENSE file to all egg top-level directories - contains the STFC copyirght statement and BSD license.
  • ndg.security.server.wsgi.authn - WSGI authentication module contains HTTP Basic Authentication middleware class
  • MyProxyClient? package - modified license from LGPL to BSD
  • ndg.security.server.wsgi.pep, ndg.security.server.wsgi.ssl: implementation for protection of pyDAP deployment with SSL Client AuthN.
  • ndg.security.common.X509: added Parse class method to X500DN class.
Location:
TI12-security/trunk
Files:
6 added
12 edited

Legend:

Unmodified
Added
Removed
  • TI12-security/trunk/python/MyProxyClient/myproxy/__init__.py

    r4770 r4838  
    77__date__ = "15/12/08" 
    88__copyright__ = "(C) 2009 Science and Technology Facilities Council" 
    9 __license__ = """LGPL""" 
     9__license__ = """BSD""" 
    1010__contact__ = "Philip.Kershaw@stfc.ac.uk" 
    1111__revision__ = '$Id$' 
  • TI12-security/trunk/python/MyProxyClient/myproxy/client.py

    r4770 r4838  
    1111__date__ = "02/06/05" 
    1212__copyright__ = "(C) 2009 Science and Technology Facilities Council" 
    13 __license__ = """LGPL 
     13__license__ = """BSD 
    1414 
    1515For myproxy_logon see Access Grid Toolkit Public License (AGTPL) 
  • TI12-security/trunk/python/MyProxyClient/myproxy/utils/__init__.py

    r4770 r4838  
    77__date__ = "15/12/08" 
    88__copyright__ = "(C) 2009 Science and Technology Facilities Council" 
    9 __license__ = """LGPL""" 
     9__license__ = """BSD""" 
    1010__contact__ = "Philip.Kershaw@stfc.ac.uk" 
    1111__revision__ = '$Id$' 
  • TI12-security/trunk/python/MyProxyClient/myproxy/utils/openssl.py

    r4770 r4838  
    77__date__ = "08/02/07" 
    88__copyright__ = "(C) 2009 Science and Technology Facilities Council" 
    9 __license__ = """LGPL""" 
     9__license__ = """BSD""" 
    1010__contact__ = "Philip.Kershaw@stfc.ac.uk" 
    1111__revision__ = '$Id:openssl.py 4643 2008-12-15 14:53:53Z pjkersha $' 
  • TI12-security/trunk/python/MyProxyClient/setup.py

    r4770 r4838  
    77__date__ = "12/12/08" 
    88__copyright__ = "(C) 2009 Science and Technology Facilities Council" 
    9 __license__ = """LGPL 
     9__license__ = """BSD 
    1010 
    1111Software adapted from myproxy_logon.  - For myproxy_logon see Access Grid  
     
    5252        'Intended Audience :: System Administrators', 
    5353        'Intended Audience :: Science/Research', 
    54         'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', 
     54        'License :: OSI Approved :: GNU Library or Lesser General Public License (BSD)', 
    5555        'Natural Language :: English', 
    5656        'Operating System :: Microsoft :: Windows', 
  • TI12-security/trunk/python/MyProxyClient/test/__init__.py

    r4770 r4838  
    66__date__ = "13/12/08" 
    77__copyright__ = "(C) 2009 Science and Technology Facilities Council" 
    8 __license__ = """LGPL""" 
     8__license__ = """BSD""" 
    99__contact__ = "Philip.Kershaw@stfc.ac.uk" 
    1010__revision__ = '$Id$' 
  • TI12-security/trunk/python/MyProxyClient/test/test_myproxyclient.py

    r4770 r4838  
    77__date__ = "02/07/07" 
    88__copyright__ = "(C) 2009 Science and Technology Facilities Council" 
    9 __license__ = """LGPL""" 
     9__license__ = """BSD""" 
    1010__contact__ = "Philip.Kershaw@stfc.ac.uk" 
    1111__revision__ = '$Id$' 
  • TI12-security/trunk/python/ndg.security.common/ndg/security/common/X509.py

    r4770 r4838  
    66__date__ = "05/04/05" 
    77__copyright__ = "(C) 2009 Science and Technology Facilities Council" 
    8 __license__ = \ 
    9 """This software may be distributed under the terms of the Q Public  
    10 License, version 1.0 or later.""" 
     8__license__ = "BSD - See LICENSE file in the top-level directory" 
    119__contact__ = "Philip.Kershaw@stfc.ac.uk" 
    1210__revision__ = '$Id$' 
     
    980978        else: 
    981979            return None 
     980 
     981    @classmethod 
     982    def Parse(cls, dn): 
     983        """Convenience method to create an X500DN object from a DN string 
     984        @type dn: basestring 
     985        @param dn: Distinguished Name  
     986        """ 
     987        return cls(dn=dn) 
     988     
     989    Deserialise = Deserialize = Parse 
  • TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/__init__.py

    r4822 r4838  
    1010__contact__ = "Philip.Kershaw@stfc.ac.uk" 
    1111__revision__ = '$Id$' 
     12import logging 
     13log = logging.getLogger(__name__) 
     14import httplib 
    1215 
    1316class NDGSecurityMiddlewareBase(object): 
     
    1518    propertyDefaults = {} 
    1619     
    17     def __init__(self, app, app_conf, **local_conf): 
    18         pass 
    19      
     20    def __init__(self, app, app_conf, prefix='', **local_conf): 
     21        '''Set object attributes directly from app_conf and local_conf inputs 
     22        @type prefix: basestring 
     23        @param prefix: prefix for app_conf parameters e.g. 'ndgsecurity.' - 
     24        enables other global configuration parameters to be filtered out 
     25        ''' 
     26        self._app = app 
     27        opt = self.__class__.propertyDefaults.copy() 
     28         
     29        # If no prefix is set, there is no way to distinguish options set for  
     30        # this app and those applying to other applications 
     31        if app_conf is not None and prefix: 
     32            # Update from application config dictionary - filter using prefix 
     33            self.__class__._filterOpts(opt, app_conf, prefix=prefix) 
     34                         
     35        # Similarly, filter keyword input                  
     36        self.__class__._filterOpts(opt, local_conf, prefix=prefix) 
     37        
     38        # Update options from keywords - matching app_conf ones will be  
     39        # overwritten 
     40        opt.update(local_conf) 
     41         
     42        # Set options as object attributes 
     43        for name, val in opt.items(): 
     44            if not name.startswith('_'): 
     45                setattr(self, name, val) 
     46                         
     47    def _setResponse(self,  
     48                     environ,  
     49                     start_response,  
     50                     notFoundMsg=None, 
     51                     notFoundMsgContentType=None): 
     52        if self._app: 
     53            return self._app(environ, start_response) 
     54        else: 
     55            return self._setErrorResponse(environ,  
     56                                          start_response,  
     57                                          msg=notFoundMsg, 
     58                                          code=404, 
     59                                          contentType=notFoundMsgContentType) 
     60             
     61    def _setErrorResponse(self, environ, start_response, msg=None, code=500, 
     62                          contentType=None): 
     63        '''Convenience method to set a simple error response 
     64         
     65        @type environ: dict 
     66        @param environ: standard WSGI environ parameter 
     67        @type start_response: builtin_function_or_method 
     68        @param start_response: standard WSGI callable to set the HTTP header 
     69        @type msg: basestring 
     70        @param msg: optional error message 
     71        @type code: int 
     72        @param code: standard HTTP error response code 
     73        @type contentType: basestring 
     74        @param contentType: set 'Content-type' HTTP header field - defaults to 
     75        'text/plain' 
     76        ''' 
     77        status = '%d %s' % (code, httplib.responses[code]) 
     78        if msg is None: 
     79            response = status 
     80        else: 
     81            response = msg 
     82         
     83        if contentType is None: 
     84            contentType = 'text/plain' 
     85                 
     86        start_response(status, 
     87                       [('Content-type', contentType), 
     88                        ('Content-Length', str(len(response)))]) 
     89        return response 
     90         
    2091    # Utility functions to support Paste Deploy application and filter function 
    2192    # signatures 
     
    45116        defOpt class variable 
    46117        ''' 
    47          
    48118        badOpt = [] 
    49119        for k,v in newOpt.items(): 
     
    62132            raise TypeError("Invalid input option(s) set: %s" %  
    63133                            (", ".join(badOpt))) 
     134 
     135    def setPathInfo(self, pathInfo=None, environ=None): 
     136        if pathInfo: 
     137            self._pathInfo = pathInfo 
     138        else: 
     139            if environ is None: 
     140                environ = self._environ 
     141             
     142            self._pathInfo = environ['PATH_INFO'] 
     143         
     144    def _getPathInfo(self): 
     145        return self._pathInfo 
     146     
     147    pathInfo = property(fget=_getPathInfo, 
     148                        fset=setPathInfo, 
     149                        doc="URL path as assigned to PATH_INFO environ key") 
     150 
     151    def _setEnviron(self, environ): 
     152        self._environ = environ 
     153         
     154    def _getEnviron(self): 
     155        return self._environ 
     156     
     157    environ = property(fget=_getEnviron, 
     158                       fset=_setEnviron, 
     159                       doc="Copy of WSGI environ dict") 
     160     
     161     
     162class NDGSecurityPathFilter(NDGSecurityMiddlewareBase): 
     163    """Specialization of NDG Security Middleware to enable filtering based on 
     164    PATH_INFO""" 
     165    propertyDefaults = { 
     166        'errorResponseCode': 401, 
     167        'serverName': None, 
     168        'mountPath': '', 
     169        'pathMatchList': '/' 
     170    } 
     171    propertyDefaults.update(NDGSecurityMiddlewareBase.propertyDefaults) 
     172     
     173    _pathMatch = lambda self: self._pathInfo in self.pathMatchList 
     174    pathMatch = property(fget=_pathMatch, 
     175                         doc="Check for input path match to list of paths" 
     176                             "to which this middleware is to be applied") 
     177     
     178    def __init__(self, *arg, **kw): 
     179        super(NDGSecurityPathFilter, self).__init__(*arg, **kw) 
     180        self._pathMatchList = [] 
     181         
     182    def _getPathMatchList(self): 
     183        return self._pathMatchList 
     184     
     185    def _setPathMatchList(self, pathList): 
     186        ''' 
     187        @type pathList: list or tuple 
     188        @param pathList: list of URL paths to apply this middleware  
     189        to. Paths are relative to the point at which this middleware is mounted 
     190        as set in environ['PATH_INFO'] 
     191        ''' 
     192        # TODO: refactor to: 
     193        # * enable reading of path list from a database or some other  
     194        # configuration source. 
     195        # * enable some kind of pattern matching for paths 
     196         
     197        if isinstance(pathList, basestring): 
     198            # Try parsing a space separated list of file paths 
     199             self._pathMatchList = pathList.split() 
     200             
     201        elif not isinstance(pathList, (list, tuple)): 
     202            raise TypeError('Expecting a list or tuple for "pathMatchList"') 
     203        else: 
     204            self._pathMatchList = pathList 
     205             
     206    pathMatchList = property(fget=_getPathMatchList, 
     207                             fset=_setPathMatchList, 
     208                             doc='List of URL paths to which to apply SSL ' 
     209                                 'client authentication') 
     210         
     211    def _getErrorResponseCode(self): 
     212        """Error response code getter 
     213        @rtype: int 
     214        @return: HTTP error code set by this middleware on client cert. 
     215        verification error 
     216        """ 
     217        return self._errorResponseCode 
     218             
     219    def _setErrorResponseCode(self, code): 
     220        """Error response code setter 
     221        @type code: int or basestring 
     222        @param code: error response code set if client cert. verification 
     223        fails""" 
     224        if isinstance(code, int): 
     225            self._errorResponseCode = code 
     226        elif isinstance(code, basestring): 
     227            self._errorResponseCode = int(code) 
     228        else: 
     229            raise TypeError('Expecting int or string type for ' 
     230                            '"errorResponseCode" attribute') 
     231             
     232        if self._errorResponseCode not in httplib.responses:  
     233            raise ValueError("Error response code [%d] is not recognised " 
     234                             "standard HTTP response code" %  
     235                             self._errorResponseCode)   
     236             
     237    errorResponseCode = property(fget=_getErrorResponseCode, 
     238                                 fset=_setErrorResponseCode, 
     239                                 doc="Response code raised if client " 
     240                                     "certificate verification fails") 
  • TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/pep/__init__.py

    r4822 r4838  
    1 """WSGI Policy Enforcement Package 
     1"""WSGI Policy Enforcement Point Package 
    22 
    33NERC DataGrid Project 
     
    1111import logging 
    1212log = logging.getLogger(__name__) 
     13import httplib 
    1314 
    14 from ndg.security.server.wsgi import NDGSecurityMiddlewareBase 
     15from ndg.security.server.wsgi import NDGSecurityPathFilter 
     16from ndg.security.common.X509 import X500DN 
    1517 
    1618 
    17 class PEP(NDGSecurityMiddlewareBase): 
    18     def __init__(self, app, app_conf, **local_conf): 
    19         self._app = app 
     19class PEPMiddleware(NDGSecurityPathFilter): 
     20    
     21    def __init__(self, *arg, **kw): 
     22        log.debug("Initialising PEPMiddleware ...") 
     23        super(PEPMiddleware, self).__init__(*arg, **kw) 
     24        self.charset = '; charset=utf-8' 
    2025     
    2126    def __call__(self, environ, start_response): 
     27        log.info("Calling PEP middleware ...") 
     28        self.environ = environ 
     29        self.setPathInfo() 
     30        log.error("environ=%s" % environ) 
     31        print >> environ['wsgi.errors'], "environ=%s" % environ 
     32         
     33        # TODO: Is a security session set? 
     34         
     35        # Is this requested URL secured? 
     36        if self.pathMatch: 
     37            return self._setErrorResponse(environ,  
     38                                          start_response, 
     39                                          code=self.errorResponseCode) 
     40        else: 
     41            # User is logged in - Redirect to HTTP based URL and complete 
     42            # Policy enforcement 
     43            response = self._redirectFromHTTPS2HTTP(start_response) 
     44            if response is not None: 
     45                return response 
     46             
    2247        return self._setResponse(environ, start_response) 
     48 
     49    def _redirectFromHTTPS2HTTP(self, start_response): 
     50        sslServerDN = self.environ.get('SSL_SERVER_S_DN') 
     51        if sslServerDN is not None: 
     52            if self.serverName: 
     53                serverName = self.serverName 
     54            else: 
     55                dn = X500DN.Parse(sslServerDN) 
     56                serverName = dn['CN'] 
     57             
     58            url = 'http://' + serverName + self.mountPath + self.pathInfo 
     59            print >> self.environ['wsgi.errors'], "redirecting to [%s]" % url 
     60            return self._redirect(start_response, url) 
    2361         
    24     def _setResponse(self,  
    25                      environ,  
    26                      start_response,  
    27                      notFoundMsg=None, 
    28                      notFoundMsgContentType=None): 
    29         if self._app: 
    30             return self._app(environ, start_response) 
    31         else: 
    32             return self._setErrorResponse(environ,  
    33                                           start_response,  
    34                                           msg=notFoundMsg, 
    35                                           code=404, 
    36                                           contentType=notFoundMsgContentType) 
    37              
    38     def _setErrorResponse(self, environ, start_response, msg=None, code=500, 
    39                           contentType=None): 
    40         '''Convenience method to set a simple error response 
    4162         
    42         @type environ: dict 
    43         @param environ: standard WSGI environ parameter 
    44         @type start_response: builtin_function_or_method 
    45         @param start_response: standard WSGI callable to set the HTTP header 
    46         @type msg: basestring 
    47         @param msg: optional error message 
    48         @type code: int 
    49         @param code: standard HTTP error response code 
    50         @type contentType: basestring 
    51         @param contentType: set 'Content-type' HTTP header field - defaults to 
    52         'text/plain' 
    53         ''' 
    54         status = '%d %s' % (code, httplib.responses[code]) 
    55         if msg is None: 
    56             response = status 
    57         else: 
    58             response = msg 
     63    def _redirect(self, start_response, url): 
     64        """Do a HTTP 302 redirect 
    5965         
    60         if contentType is None: 
    61             contentType = 'text/plain' 
    62                  
    63         start_response(status, 
    64                        [('Content-type', contentType), 
    65                         ('Content-Length', str(len(response)))]) 
    66         return response 
     66        @type start_response: callable following WSGI start_response convention 
     67        @param start_response: WSGI start response callable 
     68        @type url: basestring 
     69        @param url: URL to redirect to 
     70        @rtype: list 
     71        @return: empty HTML body 
     72        """ 
     73        start_response('302 %s' % httplib.responses[302],  
     74                       [('Content-type', 'text/html'+self.charset), 
     75                        ('Location', url)]) 
     76        return [] 
  • TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/ssl.py

    r4823 r4838  
    2222import httplib 
    2323 
    24 from ndg.security.server.wsgi import NDGSecurityMiddlewareBase 
     24from ndg.security.server.wsgi import NDGSecurityPathFilter 
    2525from ndg.security.common.X509 import X509Stack, X509Cert, X509CertError 
    2626 
    27 class SSLClientAuthNMiddleware(NDGSecurityMiddlewareBase): 
     27class SSLClientAuthNMiddleware(NDGSecurityPathFilter): 
    2828    '''Apply to SSL client authentication to configured URL paths. 
    2929 
     
    3434     
    3535    propertyDefaults = { 
    36         'errorResponseCode': 401, 
    37         'pathMatchList': '/', 
    3836        'caCertFilePathList': [] 
    3937    } 
    40  
     38    propertyDefaults.update(NDGSecurityPathFilter.propertyDefaults) 
     39     
    4140    _isSSLClientCertSet = lambda self: bool(self._environ.get( 
    4241                                SSLClientAuthNMiddleware.sslClientCertKeyName))  
    4342    isSSLClientCertSet = property(fget=_isSSLClientCertSet, 
    4443                                  doc="Check for client cert. set in environ") 
    45      
    46     _pathMatch = lambda self: self._path in self.pathMatchList 
    47     pathMatch = property(fget=_pathMatch, 
    48                          doc="Check for input path match to list of paths" 
    49                              "to which SSL client AuthN is to be applied") 
    50      
    51     def __init__(self, app, app_conf, prefix='', **local_conf): 
    52         self._app = app 
    53  
    54         opt = SSLClientAuthNMiddleware.propertyDefaults.copy() 
    55          
    56         # If no prefix is set, there is no way to distinguish options set for  
    57         # this app and those applying to other applications 
    58         if app_conf is not None and prefix: 
    59             # Update from application config dictionary - filter using prefix 
    60             SSLClientAuthNMiddleware._filterOpts(opt, app_conf, prefix=prefix) 
    61                          
    62         # Similarly, filter keyword input                  
    63         SSLClientAuthNMiddleware._filterOpts(opt, local_conf, prefix=prefix) 
    64         
    65         # Update options from keywords - matching app_conf ones will be  
    66         # overwritten 
    67         opt.update(local_conf) 
    68          
    69         # Set options as object attributes 
    70         for name, val in opt.items(): 
    71             setattr(self, name, val) 
    72      
    73     def _getErrorResponseCode(self): 
    74         """ 
    75         @rtype: int 
    76         @return: HTTP error code set by this middleware on client cert. 
    77         verification error 
    78         """ 
    79         return self._errorResponseCode 
    80              
    81     def _setErrorResponseCode(self, code): 
    82         """ 
    83         @type code: int or basestring 
    84         @param code: error response code set if client cert. verification 
    85         fails""" 
    86         if isinstance(code, int): 
    87             self._errorResponseCode = code 
    88         elif isinstance(code, basestring): 
    89             self._errorResponseCode = int(code) 
    90         else: 
    91             raise TypeError('Expecting int or string type for ' 
    92                             '"errorResponseCode" attribute') 
    93              
    94         if self._errorResponseCode not in httplib.responses:  
    95             raise ValueError("Error response code [%d] is not recognised " 
    96                              "standard HTTP response code" %  
    97                              self._errorResponseCode)   
    98              
    99     errorResponseCode = property(fget=_getErrorResponseCode, 
    100                                  fset=_setErrorResponseCode, 
    101                                  doc="Response code raised if client " 
    102                                      "certificate verification fails") 
    10344         
    10445    def _setCACertsFromFileList(self, caCertFilePathList): 
     
    15697                             doc='List of URL paths to which to apply SSL ' 
    15798                                 'client authentication') 
    158      
    159     @classmethod 
    160     def _filterOpts(cls, opt, newOpt, prefix=''): 
    161         '''Convenience utility to filter input options set in __init__ via 
    162         app_conf or keywords 
    163          
    164         @type opt: dict 
    165         @param opt: existing options set.  These will be updated by this 
    166         method based on the content of newOpt 
    167         @type newOpt: dict 
    168         @param newOpt: new options to update opt with 
    169         @type prefix: basestring  
    170         @param prefix: if set, remove the given prefix from the input options 
    171         @raise KeyError: if an option is set that is not in the classes 
    172         defOpt class variable 
    173         ''' 
    174          
    175         badOpt = [] 
    176         for k,v in newOpt.items(): 
    177             if prefix and k.startswith(prefix): 
    178                 subK = k.replace(prefix, '')                     
    179                 filtK = '_'.join(subK.split('.'))   
    180             else: 
    181                 filtK = k 
    182                      
    183             if filtK not in cls.propertyDefaults: 
    184                 badOpt += [k]                 
    185             else: 
    186                 opt[filtK] = v 
    187                  
    188         if len(badOpt) > 0: 
    189             raise TypeError("Invalid input option(s) set: %s" %  
    190                             (", ".join(badOpt))) 
    19199                
    192100    def __call__(self, environ, start_response): 
Note: See TracChangeset for help on using the changeset viewer.