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

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

Further improvements to the authorization middleware:

  • PEPFilter no longer explicitly calls the PEPResultHandlerMiddleware (This latter class is the WSGI component which handles the access denied response that the server returns). This is not necessary as it can set a 403 response in order to trigger multiHandlerIntercept callback function set in the MultiHandler? instance. This responds to all 403 type status codes by invoking the PEPResultHandlerMiddleware.
  • ndg.security.common.authz.msi: improvements to the PDP, PIP and Response classes.
  • ndg.security.test.integration.dap: added integration test for secured pyDAP service
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        if status.startswith(cls.triggerStatus):
149            log.debug("%s.checker caught status %s: invoking authentication "
150                      "handler", cls.__name__, cls.triggerStatus)
151            return True
152        else:
153            log.debug("%s.checker skipping status [%s]", cls.__name__, status)
154            return False
155
156
157class SessionHandlerMiddleware(NDGSecurityMiddlewareBase):
158    '''Middleware to redirect back to referrer URI following call to a logout
159    URI as implemented in AuthKit'''
160    prefix = 'sessionHandler.'
161   
162    logoutReturn2URIArgName = 'ndg.security.logout.r'
163    propertyDefaults = {
164        'signoutPath': None,
165        'sessionKey': 'beaker.session.ndg.security'
166    }
167    propertyDefaults.update(NDGSecurityMiddlewareBase.propertyDefaults)
168
169
170    def __init__(self, app, global_conf, prefix='', **app_conf):
171        super(SessionHandlerMiddleware, self).__init__(app, 
172                                                       global_conf,
173                                                       prefix=prefix, 
174                                                       **app_conf)
175       
176    @NDGSecurityMiddlewareBase.initCall
177    def __call__(self, environ, start_response):
178        session = environ[self.sessionKey]
179       
180        if self.signoutPath and self.pathInfo == self.signoutPath:
181            log.debug("SessionHandlerMiddleware: caught signout path [%s]",
182                      self.signoutPath)
183           
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            log.debug("SessionHandlerMiddleware: checking for REMOTE_* "
203                      "environment variable settings set by OpenID Relying "
204                      "Party signin...")
205           
206            if 'username' not in session and 'REMOTE_USER' in environ:
207                log.debug("SessionHandlerMiddleware: updating session with "
208                          "username=%s", environ['REMOTE_USER'])
209               
210                session['username'] = environ['REMOTE_USER']
211               
212            if environ.get('REMOTE_USER_DATA', ''):
213                log.debug("SessionHandlerMiddleware: found REMOTE_USER_DATA="
214                          "%s, set from OpenID Relying Party signin")
215               
216                # eval is safe here because AuthKit cookie is signed and
217                # AuthKit middleware checks for tampering
218                if 'sessionManagerURI' not in session or \
219                   'sessionId' not in session:
220                    axData = eval(environ['REMOTE_USER_DATA'])
221                    sessionManagerURI = \
222                                    axData['ax']['value.sessionManagerURI.1']
223                    session['sessionManagerURI'] = sessionManagerURI
224
225                    sessionId = axData['ax']['value.sessionId.1']
226                    session['sessionId'] = sessionId
227
228                    log.debug("SessionHandlerMiddleware: updated session "
229                              "with sessionManagerURI=%s and sessionId=%s", 
230                              sessionManagerURI, sessionId)
231                   
232                # Reset cookie removing user data
233                environ['paste.auth_tkt.set_user'](session['username'])
234               
235            # Set a return to address for logout
236            session[self.__class__.logoutReturn2URIArgName] = self.pathInfo
237            session.save()
238                     
239            _start_response = start_response
240           
241        return self._app(environ, _start_response)
242
243
244from authkit.authenticate.multi import MultiHandler
245
246class AuthenticationMiddlewareConfigError(NDGSecurityMiddlewareConfigError):
247    '''Authentication Middleware Configuration error'''
248
249class AuthenticationMiddleware(MultiHandler, NDGSecurityMiddlewareBase):
250    '''Handler to intercept 401 Unauthorized HTTP responses and redirect to an
251    authentication URI.  This class also implements a redirect handler to
252    redirect back to the referrer if logout is invoked.
253    '''
254
255    def __init__(self, app, global_conf, prefix='', **app_conf):
256        '''
257        @type app: callable following WSGI interface
258        @param app: next middleware application in the chain     
259        @type global_conf: dict       
260        @param global_conf: PasteDeploy global configuration dictionary
261        @type prefix: basestring
262        @param prefix: prefix for configuration items
263        @type app_conf: dict       
264        @param app_conf: PasteDeploy application specific configuration
265        dictionary
266        '''
267       
268        # Set logout URI parameter from AuthKit settings if not otherwise set
269        sessionHandlerPrefix = prefix + SessionHandlerMiddleware.prefix
270        signoutPathParamName = sessionHandlerPrefix + 'signoutPath'
271        if signoutPathParamName not in app_conf:
272            try:                   
273                app_conf[signoutPathParamName] = app_conf[
274                                                'authkit.cookie.signoutpath']
275            except KeyError, e:
276                raise AuthenticationMiddlewareConfigError("No %s or %s keys "
277                                                    "set" % 
278                                                    (signoutPathParamName, e))
279       
280        app = SessionHandlerMiddleware(app, 
281                                       global_conf, 
282                                       prefix=sessionHandlerPrefix,
283                                       **app_conf)
284       
285        # Remove session handler middleware specific parameters
286        for k in app_conf.keys():
287            if k.startswith(sessionHandlerPrefix):
288                del app_conf[k]
289       
290        app = authkit.authenticate.middleware(app, app_conf)       
291        app = SessionMiddleware(app, 
292                                environ_key='beaker.session.ndg.security',
293                                **app_conf)
294       
295        MultiHandler.__init__(self, app)
296
297        self.add_method(AuthenticationRedirectMiddleware.id, 
298                        AuthenticationRedirectMiddleware.filter_app_factory, 
299                        global_conf,
300                        prefix=prefix,
301                        **app_conf)
302       
303        self.add_checker(AuthenticationRedirectMiddleware.id, 
304                         AuthenticationRedirectMiddleware.checker)
305
Note: See TracBrowser for help on using the repository browser.