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

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

Fix to ndg.security.server.wsgi.authn.AuthenticationRedirectMiddleware?: when user is authenticated return next app in stack rather raising 403 response. 403 is not needed here in order to activate the authorisation middleware. The latter can trigger itself.

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
22#class HTTPBasicAuthNMiddleware(NDGSecurityMiddlewareBase):
23#    '''HTTP Basic Authentication Middleware
24#   
25#    TODO: implement authN interface and username/password retrieval from
26#    HTTP header.'''
27#   
28#    def __init__(self, app, app_conf, **local_conf):
29#
30#        super(HTTPBasicAuthentication).__init__(self,
31#                                                app,
32#                                                app_conf,
33#                                                **local_conf)
34#       
35#    def __call__(self, environ, start_response):
36#        """Authenticate based HTTP header elements as specified by the HTTP
37#        Basic Authentication spec."""
38#        log.debug("HTTPBasicAuthNMiddleware.__call__ ...")
39#       
40#        try:
41#            self.authNInterface.logon(username, password)
42#               
43#        except Exception, e:
44#            return self._errorResponse(code=401)
45#        else:
46#            return self._app(environ, start_response)
47           
48class HTTPBasicAuthentication(object):
49    '''Authkit based HTTP Basic Authentication.   __call__ defines a
50    validation function to fit with the pattern for the AuthKit interface
51    '''
52   
53    def __init__(self):
54        self._userIn = UserIn([])
55       
56    def __call__(self, environ, username, password):
57        """validation function"""
58        try:
59            client = WSGISessionManagerClient(environ=environ,
60                                environKeyName=self.sessionManagerFilterID)
61            res = client.connect(username, passphrase=password)
62
63            if username not in self._userIn.users:
64                self._userIn.users += [username]
65           
66            # TODO: set session
67               
68        except Exception, e:
69            return False
70        else:
71            return True
72
73
74import urllib
75from urlparse import urlparse
76from paste.request import construct_url
77from beaker.middleware import SessionMiddleware
78import authkit.authenticate
79
80from ndg.security.server.wsgi import NDGSecurityMiddlewareBase, \
81    NDGSecurityMiddlewareConfigError
82
83class AuthenticationRedirectMiddleware(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.  AuthenticationMiddleware wrapper handles this.'''
89    propertyDefaults = {
90        'redirectURI': None,
91        'sessionKey': 'beaker.session'
92    }
93    propertyDefaults.update(NDGSecurityMiddlewareBase.propertyDefaults)
94
95    return2URIArgName = 'ndg.security.r'
96   
97    _isAuthenticated = lambda self: 'REMOTE_USER' in self.environ
98    isAuthenticated = property(fget=_isAuthenticated,
99                               doc='boolean for is user logged in')
100
101    triggerStatus = '401'
102    id = 'authNMiddleware'
103
104    def __init__(self, app, global_conf, **app_conf):
105        self._redirectURI = None
106        super(AuthenticationRedirectMiddleware, self).__init__(app, 
107                                                               global_conf, 
108                                                               **app_conf)
109       
110    @NDGSecurityMiddlewareBase.initCall
111    def __call__(self, environ, start_response):
112        '''Invoke redirect if user is not authenticated'''
113       
114        log.debug("AuthenticationRedirectMiddleware.__call__ ...")
115       
116        if self.isAuthenticated:
117            # Call next app in stack
118            return self._app(environ, start_response)       
119        else:
120            # User is not authenticated - Redirect to OpenID Relying Party URI
121            # for user OpenID entry
122            return self._setRedirectResponse()
123   
124    def _setRedirectURI(self, uri):
125        if not isinstance(uri, basestring):
126            raise TypeError("Redirect URI must be set to string type")   
127         
128        self._redirectURI = uri
129       
130    def _getRedirectURI(self):
131        return self._redirectURI
132   
133    redirectURI = property(fget=_getRedirectURI,
134                       fset=_setRedirectURI,
135                       doc="URI to redirect to if user is not authenticated")
136
137    def _setRedirectResponse(self):
138        """Construct a redirect response adding in a return to address in a
139        URI query argument
140       
141        @rtype: basestring
142        @return: redirect response
143        """
144       
145        return2URI = construct_url(self.environ)
146        quotedReturn2URI = urllib.quote(return2URI, safe='')
147        return2URIQueryArg = urllib.urlencode(
148                    {AuthenticationRedirectMiddleware.return2URIArgName: 
149                     quotedReturn2URI})
150
151        redirectURI = self.redirectURI
152       
153        if '?' in redirectURI:
154            if redirectURI.endswith('&'):
155                redirectURI += return2URIQueryArg
156            else:
157                redirectURI += '&' + return2URIQueryArg
158        else:
159            if redirectURI.endswith('?'):
160                redirectURI += return2URIQueryArg
161            else:
162                redirectURI += '?' + return2URIQueryArg
163               
164        return self._redirect(redirectURI)
165       
166    @classmethod
167    def checker(cls, environ, status, headers):
168        """Set the trigger for calling this middleware.  In this case, it's a
169        HTTP 401 Unauthorized response detected in the middleware chain
170        """
171        if status.startswith(cls.triggerStatus):
172            log.debug("%s.checker caught status %s: invoking authentication "
173                      "handler", cls.__name__, cls.triggerStatus)
174            return True
175        else:
176            log.debug("%s.checker skipping status [%s]", cls.__name__, status)
177            return False
178
179
180class SessionHandlerMiddleware(NDGSecurityMiddlewareBase):
181    '''Middleware to redirect back to referrer URI following call to a logout
182    URI as implemented in AuthKit'''
183    prefix = 'sessionHandler.'
184   
185    logoutReturn2URIArgName = 'ndg.security.logout.r'
186    propertyDefaults = {
187        'signoutPath': None,
188        'sessionKey': 'beaker.session.ndg.security'
189    }
190    propertyDefaults.update(NDGSecurityMiddlewareBase.propertyDefaults)
191
192
193    def __init__(self, app, global_conf, prefix='', **app_conf):
194        super(SessionHandlerMiddleware, self).__init__(app, 
195                                                       global_conf,
196                                                       prefix=prefix, 
197                                                       **app_conf)
198       
199    @NDGSecurityMiddlewareBase.initCall
200    def __call__(self, environ, start_response):
201       
202        log.debug("SessionHandlerMiddleware.__call__ ...")
203       
204        session = environ[self.sessionKey]
205       
206        if self.signoutPath and self.pathInfo == self.signoutPath:
207            log.debug("SessionHandlerMiddleware: caught sign out path [%s]",
208                      self.signoutPath)
209           
210            referer = session.get(self.__class__.logoutReturn2URIArgName)
211            if referer is not None:
212                def _start_response(status, header, exc_info=None):
213                    header.extend([('Location', referer)])
214                    return start_response(self.getStatusMessage(302), 
215                                          header,
216                                          exc_info)
217                   
218            else:
219                log.error('No referrer set for redirect following logout')
220                _start_response = start_response
221               
222            # Clear user details from beaker session
223            session.pop('username', None)
224            session.pop('sessionManagerURI', None)
225            session.pop('sessionId', None)
226            session.pop('pepCtx', None)
227            session.save()
228        else:
229            log.debug("SessionHandlerMiddleware: checking for REMOTE_* "
230                      "environment variable settings set by OpenID Relying "
231                      "Party signin...")
232           
233            if 'username' not in session and 'REMOTE_USER' in environ:
234                log.debug("SessionHandlerMiddleware: updating session with "
235                          "username=%s", environ['REMOTE_USER'])
236               
237                session['username'] = environ['REMOTE_USER']
238               
239            if environ.get('REMOTE_USER_DATA', ''):
240                log.debug("SessionHandlerMiddleware: found REMOTE_USER_DATA="
241                          "%s, set from OpenID Relying Party signin",
242                          environ['REMOTE_USER_DATA'])
243               
244                # eval is safe here because AuthKit cookie is signed and
245                # AuthKit middleware checks for tampering
246                if 'sessionManagerURI' not in session or \
247                   'sessionId' not in session:
248                    axData = eval(environ['REMOTE_USER_DATA'])
249                    sessionManagerURI = \
250                                    axData['ax']['value.sessionManagerURI.1']
251                    session['sessionManagerURI'] = sessionManagerURI
252
253                    sessionId = axData['ax']['value.sessionId.1']
254                    session['sessionId'] = sessionId
255
256                    log.debug("SessionHandlerMiddleware: updated session "
257                              "with sessionManagerURI=%s and sessionId=%s", 
258                              sessionManagerURI, sessionId)
259                   
260                # Reset cookie removing user data
261                environ['paste.auth_tkt.set_user'](session['username'])
262               
263            # Set a return to address for logout
264            session[self.__class__.logoutReturn2URIArgName] = self.pathInfo
265            session.save()
266                     
267            _start_response = start_response
268           
269        return self._app(environ, _start_response)
270
271
272from authkit.authenticate.multi import MultiHandler
273
274class AuthenticationMiddlewareConfigError(NDGSecurityMiddlewareConfigError):
275    '''Authentication Middleware Configuration error'''
276
277class AuthenticationMiddleware(MultiHandler, NDGSecurityMiddlewareBase):
278    '''Handler to intercept 401 Unauthorized HTTP responses and redirect to an
279    authentication URI.  This class also implements a redirect handler to
280    redirect back to the referrer if logout is invoked.
281    '''
282
283    def __init__(self, app, global_conf, prefix='', **app_conf):
284        '''
285        @type app: callable following WSGI interface
286        @param app: next middleware application in the chain     
287        @type global_conf: dict       
288        @param global_conf: PasteDeploy global configuration dictionary
289        @type prefix: basestring
290        @param prefix: prefix for configuration items
291        @type app_conf: dict       
292        @param app_conf: PasteDeploy application specific configuration
293        dictionary
294        '''
295       
296        # Set logout URI parameter from AuthKit settings if not otherwise set
297        sessionHandlerPrefix = prefix + SessionHandlerMiddleware.prefix
298        signoutPathParamName = sessionHandlerPrefix + 'signoutPath'
299        if signoutPathParamName not in app_conf:
300            try:                   
301                app_conf[signoutPathParamName] = app_conf[
302                                                'authkit.cookie.signoutpath']
303            except KeyError, e:
304                raise AuthenticationMiddlewareConfigError("No %s or %s keys "
305                                                    "set" % 
306                                                    (signoutPathParamName, e))
307       
308        app = SessionHandlerMiddleware(app, 
309                                       global_conf, 
310                                       prefix=sessionHandlerPrefix,
311                                       **app_conf)
312       
313        # Remove session handler middleware specific parameters
314        for k in app_conf.keys():
315            if k.startswith(sessionHandlerPrefix):
316                del app_conf[k]
317       
318        app = authkit.authenticate.middleware(app, app_conf)       
319        app = SessionMiddleware(app, 
320                                environ_key='beaker.session.ndg.security',
321                                **app_conf)
322       
323        MultiHandler.__init__(self, app)
324
325        self.add_method(AuthenticationRedirectMiddleware.id, 
326                        AuthenticationRedirectMiddleware.filter_app_factory, 
327                        global_conf,
328                        prefix=prefix,
329                        **app_conf)
330       
331        self.add_checker(AuthenticationRedirectMiddleware.id, 
332                         AuthenticationRedirectMiddleware.checker)
333
Note: See TracBrowser for help on using the repository browser.