Changeset 5037 for TI12-security


Ignore:
Timestamp:
25/02/09 13:52:50 (11 years ago)
Author:
pjkersha
Message:

ndg.security.server.wsgi.authn: AuthNMiddleware and AuthNRedirectMiddleware catch HTTP 401 responses from a WSGI application stack to be protected and redirect to OpenID Relying Party middleware running on an application server running NDG Security services.

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

Legend:

Unmodified
Added
Removed
  • TI12-security/trunk/python/Tests/sqlalchemy/test_sqlalchemy.py

    r4544 r5037  
    33from sqlalchemy import create_engine 
    44 
     5from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey 
     6 
     7class CreateResourceConstraintsDb(object): 
     8 
     9    def createResourceConstraintsTable(self): 
     10        metadata = MetaData() 
     11        users_table = Table('resource_constraints', metadata, 
     12            Column('id', Integer, primary_key=True), 
     13            Column('uri_regex', String)) 
     14         
     15    def createActionTypeTable(self): 
     16        metadata = MetaData() 
     17        users_table = Table('action_type', metadata, 
     18            Column('id', Integer, primary_key=True), 
     19            Column('uri_regex', String), 
     20            Column('action', String)) 
     21         
     22    def createAttributesTable(self): 
     23        metadata = MetaData() 
     24        users_table = Table('attributes', metadata, 
     25            Column('id', Integer, primary_key=True), 
     26            Column('uri_regex', String), 
     27            Column('attribute', String)) 
     28             
    529if __name__ == "__main__": 
    630    import sys 
  • TI12-security/trunk/python/ndg.security.common/ndg/security/common/authz/pdp/__init__.py

    r4975 r5037  
    114114     
    115115    # Alias for convenience 
    116     __call__ = accessPermitted 
     116    def __call__(self, *arg, **kw): 
     117        return self.accessPermitted(*arg, **kw) 
    117118     
  • TI12-security/trunk/python/ndg.security.server/ndg/security/server/paster_templates/default_deployment/services.ini_tmpl

    r4863 r5037  
    234234[filter:SessionMiddlewareFilter] 
    235235paste.filter_app_factory=beaker.middleware:SessionMiddleware 
     236 
    236237# Chain of SOAP Middleware filters 
    237238[pipeline:main] 
  • TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/__init__.py

    r4909 r5037  
    266266            # self.start_response will be None if initCall decorator wasn't  
    267267            # applied to __call__ 
    268             if start_response is None: 
     268            if self.start_response is None: 
    269269                raise NDGSecurityMiddlewareConfigError("No start_response " 
    270270                                                       "function set.") 
  • TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/authn.py

    r4909 r5037  
    1010__contact__ = "Philip.Kershaw@stfc.ac.uk" 
    1111__revision__ = "$Id$" 
     12import logging 
     13log = logging.getLogger(__name__) 
    1214from authkit.permissions import UserIn 
    13 from authkit.authorize import authorize 
    1415 
    1516from ndg.security.server.wsgi.utils.sessionmanagerclient import \ 
     
    3738        else: 
    3839            return True 
     40 
     41import urllib 
     42from urlparse import urlparse 
     43from paste.request import construct_url 
     44import authkit.authenticate 
     45 
     46from ndg.security.server.wsgi import NDGSecurityMiddlewareBase, \ 
     47    NDGSecurityMiddlewareConfigError 
     48 
     49class AuthNMiddleware(NDGSecurityMiddlewareBase):  
     50    '''Wrapper for AuthKit and Redirect middleware so that AuthKit Cookie 
     51    handler is called *before* redirect __call__.  This is to enable the 
     52    latter to correctly check the login status updated by the AuthKit cookie 
     53    handler'''  
     54    triggerStatus = '401' 
     55    id = 'authNMiddleware' 
     56     
     57    def __init__(self, app, global_conf, **app_conf): 
     58        app = AuthNRedirectMiddleware(app, global_conf, **app_conf) 
     59        self._app = authkit.authenticate.middleware(app, app_conf) 
     60#        super(AuthNMiddleware, self).__init__(app, global_conf, **app_conf) 
     61         
     62    def __call__(self, environ, start_response): 
     63        return self._app(environ, start_response) 
     64         
     65    @classmethod 
     66    def checker(cls, environ, status, headers): 
     67        """Set the trigger for calling this middleware.  In this case, it's a 
     68        HTTP 401 Unauthorized response detected in the middleware chain 
     69        """ 
     70        log.debug("AuthNMiddleware.checker received status %r, " 
     71                  "headers %r", status, headers) 
     72         
     73        if status.startswith(cls.triggerStatus): 
     74            log.debug("AuthNMiddleware.checker returning True") 
     75            return True 
     76        else: 
     77            log.debug("AuthNMiddleware.checker returning False") 
     78            return False 
     79         
     80class AuthNRedirectMiddlewareConfigError(NDGSecurityMiddlewareConfigError): 
     81    '''Authentication Redirect Middleware Configuration error''' 
     82 
     83class AuthNRedirectMiddleware(NDGSecurityMiddlewareBase): 
     84    '''Middleware to redirect to another URI if no user is set in the  
     85    REMOTE_USER key of environ 
     86     
     87    AuthKit.authenticate.middleware must be in place upstream of this  
     88    middleware.  AuthNMiddleware wrapper handles this.  See above''' 
     89    propertyDefaults = { 
     90        'redirectURI': None, 
     91        'sessionKey': 'beaker.session' 
     92    } 
     93    propertyDefaults.update(NDGSecurityMiddlewareBase.propertyDefaults) 
     94 
     95    return2URIArgName = 'ndg.security.r' 
     96    logoutReturn2URIArgName = 'ndg.security.logout.r' 
     97     
     98    _isAuthenticated = lambda self: 'REMOTE_USER' in self.environ 
     99    isAuthenticated = property(fget=_isAuthenticated, 
     100                               doc='boolean for is user logged in') 
     101 
     102    def __init__(self, app, global_conf, **app_conf): 
     103        self._redirectURI = None 
     104         
     105        # AuthKit params are expected to be present in  
     106        self._signoutPath = app_conf.get('authkit.cookie.signoutpath') 
     107         
     108        super(AuthNRedirectMiddleware, self).__init__(app,  
     109                                                      global_conf,  
     110                                                      **app_conf) 
     111         
     112    @NDGSecurityMiddlewareBase.initCall 
     113    def __call__(self, environ, start_response): 
     114        '''Invoke redirect if user is not authenticated''' 
     115        session = environ[self.sessionKey] 
     116         
     117        if not self.isAuthenticated: 
     118            # Redirect to OpenID Relying Party URI for user OpenID entry 
     119            return self._setRedirectResponse() 
     120         
     121        elif self._signoutPath is not None and \ 
     122             self.pathInfo == self._signoutPath: 
     123            referer = session.get( 
     124                            AuthNRedirectMiddleware.logoutReturn2URIArgName) 
     125            if referer is not None: 
     126                def setRedirectResponse(status, header, exc_info=None): 
     127                    header.extend([('Location', referer)]) 
     128                    return start_response(self.getStatusMessage(302),  
     129                                          header, 
     130                                          exc_info) 
     131                     
     132                return self._app(environ, setRedirectResponse) 
     133            else: 
     134                log.debug('No referer set for redirect following logout') 
     135 
     136        else: 
     137            # User is logged in, alter response to 403 to signal that an 
     138            # authorisation check is required and set a return to address for 
     139            # logout 
     140            session[AuthNRedirectMiddleware.logoutReturn2URIArgName] = \ 
     141                                                                self.pathInfo 
     142            session.save() 
     143             
     144            def set403Response(status, header, exc_info=None): 
     145                return start_response(self.getStatusMessage(403), 
     146                                      header, 
     147                                      exc_info) 
     148                 
     149            return self._app(environ, set403Response)             
     150    
     151    def _setRedirectURI(self, uri): 
     152        if not isinstance(uri, basestring): 
     153            raise TypeError("Redirect URI must be set to string type")    
     154          
     155        self._redirectURI = uri 
     156         
     157    def _getRedirectURI(self): 
     158        return self._redirectURI 
     159     
     160    redirectURI = property(fget=_getRedirectURI, 
     161                       fset=_setRedirectURI, 
     162                       doc="URI to redirect to if user is not authenticated") 
     163 
     164    def _setRedirectResponse(self): 
     165        """Construct a redirect response adding in a return to address in a 
     166        URI query argument 
     167         
     168        @rtype: basestring 
     169        @return: redirect response 
     170        """ 
     171         
     172        return2URI = construct_url(self.environ) 
     173        quotedReturn2URI = urllib.quote(return2URI, safe='') 
     174        return2URIQueryArg = urllib.urlencode( 
     175                    {AuthNRedirectMiddleware.return2URIArgName:  
     176                     quotedReturn2URI}) 
     177 
     178        redirectURI = self.redirectURI 
     179         
     180        if '?' in redirectURI: 
     181            if redirectURI.endswith('&'): 
     182                redirectURI += return2URIQueryArg 
     183            else: 
     184                redirectURI += '&' + return2URIQueryArg 
     185        else: 
     186            if redirectURI.endswith('?'): 
     187                redirectURI += return2URIQueryArg 
     188            else: 
     189                redirectURI += '?' + return2URIQueryArg 
     190                 
     191        return self._redirect(redirectURI) 
     192 
     193 
     194from authkit.authenticate.multi import MultiHandler 
     195 
     196class AuthNRedirectHandlerMiddleware(MultiHandler, NDGSecurityMiddlewareBase): 
     197    '''Handler to intercept 401 Unauthorized HTTP responses and redirect to an 
     198    authentication URI.  Add THIS class to any middleware chain and NOT 
     199    AuthNRedirectMiddleware which it wraps. 
     200    ''' 
     201    def __init__(self, app, global_conf, **app_conf): 
     202        MultiHandler.__init__(self, app) 
     203 
     204        self.add_method(AuthNMiddleware.id,  
     205                        AuthNMiddleware.filter_app_factory,  
     206                        global_conf, 
     207                        **app_conf) 
     208         
     209        self.add_checker(AuthNMiddleware.id, AuthNMiddleware.checker) 
  • TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/openid/__init__.py

    r4840 r5037  
    11"""WSGI Middleware components - OpenID package containing an OpenID Provider 
    2 WSGI implementation 
     2and Relying Party WSGI implementations 
    33 
    44NERC Data Grid Project""" 
  • TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/openid/relyingparty/__init__.py

    r4909 r5037  
    1515 
    1616import httplib # to get official status code messages 
    17  
     17import urllib # decode quoted URI in query arg 
     18from urlparse import urlparse 
     19 
     20from paste.request import parse_querystring, parse_formvars 
    1821import authkit.authenticate 
    1922import beaker.middleware 
     
    8689 
    8790        authKitApp = authkit.authenticate.middleware(app, app_conf) 
    88          
     91        _app = authKitApp 
     92        while True: 
     93            if isinstance(_app,authkit.authenticate.open_id.AuthOpenIDHandler): 
     94                self._authKitVerifyPath = _app.path_verify 
     95                self._authKitProcessPath = _app.path_process 
     96                break 
     97             
     98            elif hasattr(_app, 'app'): 
     99                _app = _app.app 
     100            else: 
     101                break 
     102          
     103        if not hasattr(self, '_authKitVerifyPath'): 
     104            raise OpenIDRelyingPartyConfigError("Error locating the AuthKit " 
     105                                                "AuthOpenIDHandler in the " 
     106                                                "WSGI stack") 
     107             
    89108        super(OpenIDRelyingPartyMiddleware, self).__init__(authKitApp,  
    90109                                                           global_conf,  
     
    96115    def __call__(self, environ, start_response): 
    97116        '''Alter start_response to override the status code and force to 401. 
    98         This will non-browser based client code to bypass the OpenID interface 
     117        This will enable non-browser based client code to bypass the OpenID  
     118        interface 
    99119         
    100120        @type environ: dict 
     
    104124        ''' 
    105125        session = environ[self.sessionKey] 
     126         
     127        # Check for return to address in URI query args 
     128        if environ['REQUEST_METHOD'] == "GET": 
     129            params = dict(parse_querystring(environ)) 
     130        else: 
     131            params = dict(parse_formvars(environ)) 
     132         
     133        referer = urllib.unquote(params.get('ndg.security.r', '')) 
     134        refererPathInfo = urlparse(referer)[2] 
     135        if referer and \ 
     136           not refererPathInfo.endswith(self._authKitVerifyPath) and \ 
     137           not refererPathInfo.endswith(self._authKitProcessPath): 
     138            # Set-up for authkit.authenticate.open_id.AuthOpenIDHandler.process 
     139            session['referer'] = referer 
     140            session.save() 
     141                 
    106142        if self.signoutPath is not None and self.pathInfo == self.signoutPath: 
    107143            # TODO: Redirect to referrer ... 
  • TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/pdp.py

    r4975 r5037  
    1313import httplib 
    1414 
    15 from ndg.security.common.authz.pdp.xacml import PDP 
     15from ndg.security.common.authz.pdp.xacml import PDP as XacmlPDP 
     16from ndg.security.common.authz.pdp.xacml import Subject as XacmlSubject 
     17from ndg.security.common.authz.pdp.xacml import Action as XacmlAction 
     18from ndg.security.common.authz.pdp.xacml import Environment as XacmlEnvironment 
    1619 
    1720from ndg.security.server.wsgi import NDGSecurityMiddlewareBase, \ 
    1821    NDGSecurityMiddlewareConfigError 
    1922 
     23# TODO Refactor module placing 
     24from ndg.security.server.wsgi.pep import Resource 
     25 
     26class Subject(XacmlSubject): 
     27    def __init__(self): 
     28        self.sessionManagerURI = None 
     29        self.sessionManagerEnvironKey = None 
     30        self.userID = None 
     31         
     32        # TODO: may need to refactor out as attributes can be derived from the 
     33        # user's wallet held be the Session Manager 
     34        self.attributes = [] 
     35 
     36class Action(XacmlAction): 
     37    pass 
     38 
     39class Environment(XacmlEnvironment): 
     40    pass 
     41         
     42class PDP(XacmlPDP): 
     43    def accessPermitted(self, subject, resource, action, environ): 
     44        '''Make access control decision''' 
     45        if action is None: 
     46            action = Action() 
     47             
     48        if environ is None: 
     49            environ = Environment() 
     50             
     51        super(PDP, self).accessPermitted(subject, resource, action, environ) 
     52        for attr in resource.attributes: 
     53            if attr in subject.attributes: 
     54                return True 
     55             
     56        return False 
     57         
     58         
    2059class PDPMiddlewareConfigError(NDGSecurityMiddlewareConfigError): 
    2160    '''Policy Decision Point Middleware Configuration error''' 
    2261 
    2362class PDPMiddleware(NDGSecurityMiddlewareBase): 
    24     forbiddenStatus = '403' 
     63    triggerStatus = '403' 
    2564    id = 'forbidden' 
    2665     
    27     _isAuthenticated = lambda self: 'REMOTE_USER' in environ 
     66    _isAuthenticated = lambda self: 'REMOTE_USER' in self.environ 
    2867    isAuthenticated = property(fget=_isAuthenticated, 
    2968                               doc='boolean for is user logged in') 
     
    3372        self.pdp = PDP() 
    3473         
    35     @PDPMiddleware.initCall 
     74    @NDGSecurityMiddlewareBase.initCall 
    3675    def __call__(self, environ, start_response): 
    3776        if self.isAuthenticated: 
     
    5190        '''Check constraints on the requested URI and return boolean - access 
    5291        allowed/denied''' 
    53         status = self.pdp(subject, resource, action, environ) 
     92        environ = self.environ 
     93         
     94        # TODO: Refactor here perhaps calling resource constraint look-up or do  
     95        # inside PDP call?       
     96        resource = self.environ.get('ndg.security.server.wsgi.pep.resource') or \ 
     97            Resource() 
     98        subject = Subject() 
     99        subject.userID = self.environ.get('REMOTE_USER') 
     100        subject.attributes += ['someAttribute'] 
     101        status = self.pdp(subject, resource, None, None) 
    54102        return status 
    55103     
     
    72120                  "%r", status, headers) 
    73121         
    74         if status.startswith(cls.forbiddenStatus): 
     122        if status.startswith(cls.triggerStatus): 
    75123            log.debug("PDPMiddleware.checker returning True") 
    76124            return True 
  • TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/pep/__init__.py

    r4909 r5037  
    1616from ndg.security.common.X509 import X500DN 
    1717 
     18 
     19from ndg.security.server.wsgi import NDGSecurityMiddlewareBase, \ 
     20    NDGSecurityMiddlewareConfigError 
     21 
     22# TODO: move this class to separate resource constraint module 
     23from ndg.security.common.authz.pdp.xacml import Resource as XacmlResource 
     24 
     25class Resource(XacmlResource): 
     26    def __init__(self, uri, attributes): 
     27        self.uri = uri 
     28        self.attributes = attributes 
    1829 
    1930class PEPMiddleware(NDGSecurityPathFilter): 
     
    4051        # Is this requested URL secured? 
    4152        if self.pathMatch: 
    42 #            return self._setErrorResponse(environ,  
    43 #                                          start_response, 
    44 #                                          code=self.errorResponseCode) 
     53            environ['ndg.security.server.wsgi.pep.resource'] = Resource( 
     54                                                                self.pathInfo, 
     55                                                                ['someAttribute']) 
    4556            def _start_response(status, header, exc_info=None): 
    4657                '''alter start_response to return unauthorised status 
  • TI12-security/trunk/python/ndg.security.test/ndg/security/test/integration/authz/securedapp.ini

    r5016 r5037  
    1212 
    1313[pipeline:main] 
    14 pipeline = AuthNRedirectFilter 
     14pipeline = SessionMiddlewareFilter 
     15                   AuthenticationFilter 
    1516           TestApp 
    1617 
    1718[app:TestApp] 
    18 paste.app_factory = ndg.security.test.wsgi.authn.test_authn:TestAuthNMiddleware 
     19paste.app_factory = ndg.security.test.integration.authz.securedapp:TestAuthNMiddleware 
    1920 
    20 [filter:AuthNRedirectFilter] 
     21#______________________________________________________________________________ 
     22# Beaker Session Middleware (used by Authentication Filter) 
     23[filter:SessionMiddlewareFilter] 
     24paste.filter_app_factory=beaker.middleware:SessionMiddleware 
     25#beaker.session.key = sso 
     26beaker.session.secret = somesecret 
     27 
     28# If you'd like to fine-tune the individual locations of the cache data dirs 
     29# for the Cache data, or the Session saves, un-comment the desired settings 
     30# here: 
     31beaker.cache.data_dir = %(here)s/beaker/cache 
     32beaker.session.data_dir = %(here)s/beaker/sessions 
     33 
     34[filter:AuthenticationFilter] 
    2135paste.filter_app_factory = ndg.security.server.wsgi.authn:AuthNRedirectHandlerMiddleware 
    2236prefix = authN. 
    23 authN.redirectURI = http://localhost:80443/verify 
     37 
     38# Set redirect for OpenID Relying Party in the Security Services app instance 
     39authN.redirectURI = http://localhost:7443/verify 
     40 
     41# AuthKit Set-up 
     42authkit.setup.method=cookie 
     43authkit.cookie.secret=secret encryption string 
     44authkit.cookie.signoutpath = /logout 
  • TI12-security/trunk/python/ndg.security.test/ndg/security/test/integration/authz/securedapp.py

    r5016 r5037  
    1111            status = "401 Unauthorized" 
    1212             
    13         elif environ['PATH_INFO'] == '/test_401WithLoggedIn': 
    14             status = "401 Unauthorized" 
    15             environ['REMOTE_USER'] = 'testuser'             
    16              
    17         elif environ['PATH_INFO'] == '/test_200WithNotLoggedIn': 
    18             status = "200 OK" 
    19              
    20         elif environ['PATH_INFO'] == '/test_200WithLoggedIn': 
    21             environ['REMOTE_USER'] = 'testuser' 
    22             status = "200 OK" 
    2313        else: 
    2414            status = "404 Not found" 
    2515                 
    26         start_response(status, 
    27                        [('Content-length',  
    28                          str(len(TestAuthNMiddleware.response))), 
    29                         ('Content-type', 'text/plain')]) 
    30         return [TestAuthNMiddleware.response] 
     16#        start_response(status, 
     17#                       [('Content-length',  
     18#                         str(len(TestAuthNMiddleware.response))), 
     19#                        ('Content-type', 'text/plain')]) 
     20#        return [TestAuthNMiddleware.response] 
     21        return self._setResponse(environ, start_response) 
     22     
     23    def _setResponse(self, environ, start_response): 
     24        if 'REMOTE_USER' in environ: 
     25            response = """<html> 
     26    <head/> 
     27    <body> 
     28        <p>Authenticated!</p> 
     29        <p><a href="/logout">logout</a></p> 
     30    </body> 
     31</html>""" 
     32            start_response('200 OK',  
     33                           [('Content-type', 'text/html'), 
     34                            ('Content-length', str(len(response)))]) 
     35        else: 
     36            response = "Trigger OpenID Relying Party..." 
     37            start_response('401 Unauthorized',  
     38                           [('Content-type', 'text/plain'), 
     39                            ('Content-length', str(len(response)))]) 
     40        return [response] 
    3141     
    3242def app_factory(globalConfig, **localConfig): 
     
    4151if __name__ == '__main__': 
    4252    import sys 
     53    import os 
     54    from os.path import dirname, abspath 
    4355    import logging 
    4456    logging.basicConfig(level=logging.DEBUG) 
     
    4759        port = int(sys.argv[1]) 
    4860    else: 
    49         port = 80080 
     61        port = 7080 
    5062         
    5163    cfgFilePath = os.path.join(dirname(abspath(__file__)), 'securedapp.ini') 
  • TI12-security/trunk/python/ndg.security.test/ndg/security/test/integration/authz/securityservices.ini

    r5017 r5037  
    1818use = egg:Paste#http 
    1919host = 0.0.0.0 
    20 port = 5800 
     20port = 7443 
    2121 
    2222# Play with this pipeline at your peril! ... 
     
    4242 
    4343[app:AuthZTestApp] 
    44 paste.app_factory = ndg.security.test.integration.authz.serverapp:app_factory 
     44paste.app_factory = ndg.security.test.integration.authz.securityservicesapp:app_factory 
    4545 
    4646[filter:PEPMiddlewareFilter] 
     
    9999authkit.openid.session.secret = random string 
    100100 
    101 authkit.openid.baseurl = http://localhost:5800 
     101authkit.openid.baseurl = http://localhost:7443 
    102102 
    103103# Template for signin 
  • TI12-security/trunk/python/ndg.security.test/ndg/security/test/integration/authz/securityservicesapp.py

    r5017 r5037  
    3737             
    3838    def default(self, environ, start_response): 
    39         start_response('200 OK', [('Content-type', 'text/plain')]) 
    40         return "Authorisation integration tests" 
     39        if 'REMOTE_USER' in environ: 
     40            response = """<html> 
     41    <head/> 
     42    <body> 
     43        <p>Authenticated!</p> 
     44        <p><a href="/logout">logout</a></p> 
     45    </body> 
     46</html>""" 
     47            start_response('200 OK',  
     48                           [('Content-type', 'text/html'), 
     49                            ('Content-length', str(len(response)))]) 
     50        else: 
     51            response = "Authorisation integration tests" 
     52            start_response('200 OK',  
     53                           [('Content-type', 'text/html'), 
     54                            ('Content-length', str(len(response)))]) 
     55        return response 
    4156 
    4257    def test_401(self, environ, start_response): 
     
    102117        port = int(sys.argv[1]) 
    103118    else: 
    104         port = 80443 
     119        port = 7443 
    105120         
    106     cfgFilePath = os.path.join(dirname(abspath(__file__)), 'services.ini') 
     121    cfgFilePath = os.path.join(dirname(abspath(__file__)),  
     122                               'securityservices.ini') 
    107123         
    108124    from paste.httpserver import serve 
  • TI12-security/trunk/python/ndg.security.test/ndg/security/test/wsgi/authn/test.ini

    r5015 r5037  
    2121paste.filter_app_factory = ndg.security.server.wsgi.authn:AuthNRedirectHandlerMiddleware 
    2222prefix = authN. 
    23 authN.redirectURI = /redirect2here 
     23#authN.redirectURI = /redirect2here 
     24authN.redirectURI = http://localhost:5800/verify 
  • TI12-security/trunk/python/ndg.security.test/ndg/security/test/wsgi/authn/test_authn.py

    r5015 r5037  
    2828import paste.fixture 
    2929from paste.deploy import loadapp 
    30  
    31 from ndg.security.server.wsgi.authn import AuthNRedirectHandlerMiddleware 
    32  
    3330 
    3431class TestAuthNMiddleware(object): 
Note: See TracChangeset for help on using the changeset viewer.