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

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

Updates following initial release on ndg3beta - release is now 1.0.1rc1:

  • Added additional test authN interface class for OpenID Provider BasicSessionManagerOpenIDAuthNInterface. This links to a session manager for authN but uses a simple look-up in the ini file to resolve usernames to OpenID identifiers. This is useful for testing. The production version SessionManagerOpenIDAuthNInterface uses a table in a database to do the same function.
  • improved ndg.security.server.wsgi.authn.AuthenticationRedirectMiddleware? log messaging for checker intercept method.
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                                    environKeyName=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("%s.checker received status %r, "
149                  "headers %r", cls.__name__, status, headers)
150       
151        if status.startswith(cls.triggerStatus):
152            log.debug("%s.checker caught status %s: invoking authentication "
153                      "handler", cls.__name__, cls.triggerStatus)
154            return True
155        else:
156            log.debug("%s.checker skipping status %s", cls.__name__, status)
157            return False
158
159
160class SessionHandlerMiddleware(NDGSecurityMiddlewareBase):
161    '''Middleware to redirect back to referrer URI following call to a logout
162    URI as implemented in AuthKit'''
163    prefix = 'sessionHandler.'
164   
165    logoutReturn2URIArgName = 'ndg.security.logout.r'
166    propertyDefaults = {
167        'signoutPath': None,
168        'sessionKey': 'beaker.session'
169    }
170    propertyDefaults.update(NDGSecurityMiddlewareBase.propertyDefaults)
171
172
173    def __init__(self, app, global_conf, prefix='', **app_conf):
174        super(SessionHandlerMiddleware, self).__init__(app, 
175                                                       global_conf,
176                                                       prefix=prefix, 
177                                                       **app_conf)
178       
179    @NDGSecurityMiddlewareBase.initCall
180    def __call__(self, environ, start_response):
181        session = environ[self.sessionKey]
182       
183        if self.signoutPath and self.pathInfo == self.signoutPath:
184            referer = session.get(self.__class__.logoutReturn2URIArgName)
185            if referer is not None:
186                def _start_response(status, header, exc_info=None):
187                    header.extend([('Location', referer)])
188                    return start_response(self.getStatusMessage(302), 
189                                          header,
190                                          exc_info)
191                   
192            else:
193                log.error('No referer set for redirect following logout')
194                _start_response = start_response
195               
196            # Clear user details from beaker session
197            session.pop('username', None)
198            session.pop('sessionManagerURI', None)
199            session.pop('sessionId', None)
200            session.save()
201        else:
202            if 'username' not in session and 'REMOTE_USER' in environ:
203                session['username'] = environ['REMOTE_USER']
204               
205            if environ.get('REMOTE_USER_DATA', ''):
206                # eval is safe here because AuthKit cookie is signed and
207                # AuthKit middleware checks for tampering
208                if 'sessionManagerURI' not in session or \
209                   'sessionId' not in session:
210                    axData = eval(environ['REMOTE_USER_DATA'])
211                    sessionManagerURI=axData['ax']['value.sessionManagerURI.1']
212                    session['sessionManagerURI'] = sessionManagerURI
213
214                    sessionId = axData['ax']['value.sessionId.1']
215                    session['sessionId'] = sessionId
216                   
217                # Reset cookie removing user data
218                environ['paste.auth_tkt.set_user'](session['username'])
219               
220            # Set a return to address for logout
221            session[self.__class__.logoutReturn2URIArgName] = self.pathInfo
222            session.save()
223                     
224            _start_response = start_response
225           
226        return self._app(environ, _start_response)
227
228
229from authkit.authenticate.multi import MultiHandler
230
231class AuthenticationMiddlewareConfigError(NDGSecurityMiddlewareConfigError):
232    '''Authentication Middleware Configuration error'''
233
234class AuthenticationMiddleware(MultiHandler, NDGSecurityMiddlewareBase):
235    '''Handler to intercept 401 Unauthorized HTTP responses and redirect to an
236    authentication URI.  This class also implements a redirect handler to
237    redirect back to the referrer if logout is invoked.
238    '''
239
240    def __init__(self, app, global_conf, prefix='', **app_conf):
241        '''
242        @type app: callable following WSGI interface
243        @param app: next middleware application in the chain     
244        @type global_conf: dict       
245        @param global_conf: PasteDeploy global configuration dictionary
246        @type prefix: basestring
247        @param prefix: prefix for configuration items
248        @type app_conf: dict       
249        @param app_conf: PasteDeploy application specific configuration
250        dictionary
251        '''
252       
253        # Set logout URI parameter from AuthKit settings if not otherwise set
254        sessionHandlerPrefix = prefix + SessionHandlerMiddleware.prefix
255        signoutPathParamName = sessionHandlerPrefix + 'signoutPath'
256        if signoutPathParamName not in app_conf:
257            try:                   
258                app_conf[signoutPathParamName] = app_conf[
259                                                'authkit.cookie.signoutpath']
260            except KeyError, e:
261                raise AuthenticationMiddlewareConfigError("No %s or %s keys "
262                                                    "set" % 
263                                                    (signoutPathParamName, e))
264       
265        app = SessionHandlerMiddleware(app, 
266                                       global_conf, 
267                                       prefix=sessionHandlerPrefix,
268                                       **app_conf)
269       
270        # Remove session handler middleware specific parameters
271        for k in app_conf.keys():
272            if k.startswith(sessionHandlerPrefix):
273                del app_conf[k]
274       
275        app = authkit.authenticate.middleware(app, app_conf)       
276        app = SessionMiddleware(app, **app_conf)
277       
278        MultiHandler.__init__(self, app)
279
280        self.add_method(AuthenticationRedirectMiddleware.id, 
281                        AuthenticationRedirectMiddleware.filter_app_factory, 
282                        global_conf,
283                        prefix=prefix,
284                        **app_conf)
285       
286        self.add_checker(AuthenticationRedirectMiddleware.id, 
287                         AuthenticationRedirectMiddleware.checker)
288
Note: See TracBrowser for help on using the repository browser.