source: TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/authn.py @ 5154

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

Major refactoring of PEP and PDP middleware:

  • moved code out of ndg.security.server.wsgi.pdp into ndg.security.server.wsgi.pep - the PDP doesn't need to be and should not be a WSGI
  • Modified PEPMiddleware to be called by via authkit.authenticate.multi.MultiHandler?.
  • New wrapper class AuthorizationMiddleware? wraps the MultiHandler?
  • TODO: delete ndg.security.server.wsgi.pdp and rename ndg.security.server.wsgi.pep package ndg.security.server.wsgi.authz
Line 
1"""Module containing:
2 * HTTP Basic Authentication Middleware
3 * middleware to enable redirection to OpenID Relying Party for login
4 * logout middleware for deleting AuthKit cookie and redirecting back to
5   referrer
6 
7NERC DataGrid Project
8"""
9__author__ = "P J Kershaw"
10__date__ = "13/01/09"
11__copyright__ = "(C) 2009 Science and Technology Facilities Council"
12__license__ = "BSD - see LICENSE file in top-level directory"
13__contact__ = "Philip.Kershaw@stfc.ac.uk"
14__revision__ = "$Id$"
15import logging
16log = logging.getLogger(__name__)
17from authkit.permissions import UserIn
18
19from ndg.security.server.wsgi.utils.sessionmanagerclient import \
20    WSGISessionManagerClient
21
22class HTTPBasicAuthentication(object):
23    '''Authkit based HTTP Basic Authentication'''
24    def __init__(self):
25        self._userIn = UserIn([])
26       
27    def __call__(self, environ, username, password):
28        """validation function"""
29        try:
30            client = WSGISessionManagerClient(environ=environ,
31                                        environKey=self.sessionManagerFilterID)
32            res = client.connect(username, passphrase=password)
33
34            if username not in self._userIn.users:
35                self._userIn.users += [username]
36           
37            # TODO: set session
38               
39        except Exception, e:
40            return False
41        else:
42            return True
43
44
45import urllib
46from urlparse import urlparse
47from paste.request import construct_url
48from beaker.middleware import SessionMiddleware
49import authkit.authenticate
50
51from ndg.security.server.wsgi import NDGSecurityMiddlewareBase, \
52    NDGSecurityMiddlewareConfigError
53
54class AuthenticationRedirectMiddleware(NDGSecurityMiddlewareBase):
55    '''Middleware to redirect to another URI if no user is set in the
56    REMOTE_USER key of environ
57   
58    AuthKit.authenticate.middleware must be in place upstream of this
59    middleware.  AuthenticationMiddleware wrapper handles this.'''
60    propertyDefaults = {
61        'redirectURI': None,
62        'sessionKey': 'beaker.session'
63    }
64    propertyDefaults.update(NDGSecurityMiddlewareBase.propertyDefaults)
65
66    return2URIArgName = 'ndg.security.r'
67   
68    _isAuthenticated = lambda self: 'REMOTE_USER' in self.environ
69    isAuthenticated = property(fget=_isAuthenticated,
70                               doc='boolean for is user logged in')
71
72    triggerStatus = '401'
73    id = 'authNMiddleware'
74
75    def __init__(self, app, global_conf, **app_conf):
76        self._redirectURI = None
77        super(AuthenticationRedirectMiddleware, self).__init__(app, 
78                                                      global_conf, 
79                                                      **app_conf)
80       
81    @NDGSecurityMiddlewareBase.initCall
82    def __call__(self, environ, start_response):
83        '''Invoke redirect if user is not authenticated'''
84       
85        if not self.isAuthenticated:
86            # Redirect to OpenID Relying Party URI for user OpenID entry
87            return self._setRedirectResponse()
88       
89        else:
90            # Retrieve OpenID response parameters and set a Forbidden response
91            # to trigger the PEP to check to see if the requested URI is a
92            # secured one
93           
94            def set403Response(status, header, exc_info=None):
95                return start_response(self.getStatusMessage(403),
96                                      header,
97                                      exc_info)
98               
99            return self._app(environ, set403Response)           
100   
101    def _setRedirectURI(self, uri):
102        if not isinstance(uri, basestring):
103            raise TypeError("Redirect URI must be set to string type")   
104         
105        self._redirectURI = uri
106       
107    def _getRedirectURI(self):
108        return self._redirectURI
109   
110    redirectURI = property(fget=_getRedirectURI,
111                       fset=_setRedirectURI,
112                       doc="URI to redirect to if user is not authenticated")
113
114    def _setRedirectResponse(self):
115        """Construct a redirect response adding in a return to address in a
116        URI query argument
117       
118        @rtype: basestring
119        @return: redirect response
120        """
121       
122        return2URI = construct_url(self.environ)
123        quotedReturn2URI = urllib.quote(return2URI, safe='')
124        return2URIQueryArg = urllib.urlencode(
125                    {AuthenticationRedirectMiddleware.return2URIArgName: 
126                     quotedReturn2URI})
127
128        redirectURI = self.redirectURI
129       
130        if '?' in redirectURI:
131            if redirectURI.endswith('&'):
132                redirectURI += return2URIQueryArg
133            else:
134                redirectURI += '&' + return2URIQueryArg
135        else:
136            if redirectURI.endswith('?'):
137                redirectURI += return2URIQueryArg
138            else:
139                redirectURI += '?' + return2URIQueryArg
140               
141        return self._redirect(redirectURI)
142       
143    @classmethod
144    def checker(cls, environ, status, headers):
145        """Set the trigger for calling this middleware.  In this case, it's a
146        HTTP 401 Unauthorized response detected in the middleware chain
147        """
148        log.debug("AuthNMiddleware.checker received status %r, "
149                  "headers %r", status, headers)
150       
151        if status.startswith(cls.triggerStatus):
152            log.debug("%s.checker returning True" % cls.__name__)
153            return True
154        else:
155            log.debug("%s.checker returning False" % cls.__name__)
156            return False
157
158
159class SessionHandlerMiddleware(NDGSecurityMiddlewareBase):
160    '''Middleware to redirect back to referrer URI following call to a logout
161    URI as implemented in AuthKit'''
162    prefix = 'sessionHandler.'
163   
164    logoutReturn2URIArgName = 'ndg.security.logout.r'
165    propertyDefaults = {
166        'signoutPath': None,
167        'sessionKey': 'beaker.session'
168    }
169    propertyDefaults.update(NDGSecurityMiddlewareBase.propertyDefaults)
170
171
172    def __init__(self, app, global_conf, prefix='', **app_conf):
173        super(SessionHandlerMiddleware, self).__init__(app, 
174                                                       global_conf,
175                                                       prefix=prefix, 
176                                                       **app_conf)
177       
178    @NDGSecurityMiddlewareBase.initCall
179    def __call__(self, environ, start_response):
180        session = environ[self.sessionKey]
181       
182        if self.signoutPath and self.pathInfo == self.signoutPath:
183            referer = session.get(self.__class__.logoutReturn2URIArgName)
184            if referer is not None:
185                def _start_response(status, header, exc_info=None):
186                    header.extend([('Location', referer)])
187                    return start_response(self.getStatusMessage(302), 
188                                          header,
189                                          exc_info)
190                   
191            else:
192                log.error('No referer set for redirect following logout')
193                _start_response = start_response
194               
195            # Clear user details from beaker session
196            session.pop('username', None)
197            session.pop('sessionManagerURI', None)
198            session.save()
199        else:
200            if 'username' not in session and 'REMOTE_USER' in environ:
201                session['username'] = environ['REMOTE_USER']
202               
203            if environ.get('REMOTE_USER_DATA', ''):
204                # eval is safe here because AuthKit cookie is signed and
205                # AuthKit middleware checks for tampering
206                if 'sessionManagerURI' not in session:
207                    axData = eval(environ['REMOTE_USER_DATA'])
208                    sessionManagerURI=axData['ax']['value.sessionManagerURI.1']
209                    session['sessionManagerURI'] = sessionManagerURI
210               
211                # Reset cookie removing user data
212                environ['paste.auth_tkt.set_user'](session['username'])
213               
214            # Set a return to address for logout
215            session[self.__class__.logoutReturn2URIArgName] = self.pathInfo
216            session.save()
217                     
218            _start_response = start_response
219           
220        return self._app(environ, _start_response)
221
222
223from authkit.authenticate.multi import MultiHandler
224
225class AuthenticationMiddlewareConfigError(NDGSecurityMiddlewareConfigError):
226    '''Authentication Middleware Configuration error'''
227
228class AuthenticationMiddleware(MultiHandler, NDGSecurityMiddlewareBase):
229    '''Handler to intercept 401 Unauthorized HTTP responses and redirect to an
230    authentication URI.  This class also implements a redirect handler to
231    redirect back to the referrer if logout is invoked.
232    '''
233
234    def __init__(self, app, global_conf, prefix='', **app_conf):
235        '''
236        @type app: callable following WSGI interface
237        @param app: next middleware application in the chain     
238        @type global_conf: dict       
239        @param global_conf: PasteDeploy global configuration dictionary
240        @type prefix: basestring
241        @param prefix: prefix for configuration items
242        @type app_conf: dict       
243        @param app_conf: PasteDeploy application specific configuration
244        dictionary
245        '''
246       
247        # Set logout URI parameter from AuthKit settings if not otherwise set
248        sessionHandlerPrefix = prefix + SessionHandlerMiddleware.prefix
249        signoutPathParamName = sessionHandlerPrefix + 'signoutPath'
250        if signoutPathParamName not in app_conf:
251            try:                   
252                app_conf[signoutPathParamName] = app_conf[
253                                                'authkit.cookie.signoutpath']
254            except KeyError, e:
255                raise AuthenticationMiddlewareConfigError("No %s or %s keys "
256                                                    "set" % 
257                                                    (signoutPathParamName, e))
258       
259        app = SessionHandlerMiddleware(app, 
260                                       global_conf, 
261                                       prefix=sessionHandlerPrefix,
262                                       **app_conf)
263       
264        # Remove session handler middleware specific parameters
265        for k in app_conf.keys():
266            if k.startswith(sessionHandlerPrefix):
267                del app_conf[k]
268       
269        app = authkit.authenticate.middleware(app, app_conf)       
270        app = SessionMiddleware(app, **app_conf)
271       
272        MultiHandler.__init__(self, app)
273
274        self.add_method(AuthenticationRedirectMiddleware.id, 
275                        AuthenticationRedirectMiddleware.filter_app_factory, 
276                        global_conf,
277                        prefix=prefix,
278                        **app_conf)
279       
280        self.add_checker(AuthenticationRedirectMiddleware.id, 
281                         AuthenticationRedirectMiddleware.checker)
282
Note: See TracBrowser for help on using the repository browser.