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

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

Completed AuthorizationMiddleware? unit tests ndg.security.test.unit.wsgi.authz:

  • Test 8, 'test08AccessDeniedForAdminQueryArg' tries out the use case for a URI which can display additional content for users with admin privileges. The caller needs to be able to display the correct content according to whether the user has admin rights or not:
    1. the caller invokes /securedURI?admin=1
    2. if the user has admin, rights the PDP will grant access and the PEP will deliver this URI.
    3. if the user doesn't have admin rights, a special overloaded PEP result handler class detects that access was denied for the admin URI and redirects the user to a modified URI subtracting the admin flag. The application code can then deliver the appropriate content minus admin privileges.
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 handle:
182    - set user session details following redirect from OpenID Relying Party
183    signin
184    - redirect back to referrer URI following call to a logout
185    URI as implemented in AuthKit'''
186    prefix = 'sessionHandler.'
187   
188    sessionKeyNames = ('username', 'sessionManagerURI', 'sessionId', 'pepCtx')
189   
190    propertyDefaults = {
191        'signoutPath': None,
192        'sessionKey': 'beaker.session.ndg.security'
193    }
194    propertyDefaults.update(NDGSecurityMiddlewareBase.propertyDefaults)
195
196
197    def __init__(self, app, global_conf, prefix='', **app_conf):
198        super(SessionHandlerMiddleware, self).__init__(app, 
199                                                       global_conf,
200                                                       prefix=prefix, 
201                                                       **app_conf)
202       
203    @NDGSecurityMiddlewareBase.initCall
204    def __call__(self, environ, start_response):
205        """Manage setting of session from AuthKit following OpenID Relying
206        Party sign in and manage logout
207       
208        @type environ: dict
209        @param environ: WSGI environment variables dictionary
210        @type start_response: function
211        @param start_response: standard WSGI start response function
212
213        """
214        log.debug("SessionHandlerMiddleware.__call__ ...")
215       
216        session = environ[self.sessionKey]
217       
218        if self.signoutPath and self.pathInfo == self.signoutPath:
219            log.debug("SessionHandlerMiddleware: caught sign out path [%s]",
220                      self.signoutPath)
221           
222            referrer = environ.get('HTTP_REFERER')
223            if referrer is not None:
224                def _start_response(status, header, exc_info=None):
225                    """Alter the header to send a redirect to the logout
226                    referrer address"""
227                    header.extend([('Location', referrer)])
228                    return start_response(self.getStatusMessage(302), 
229                                          header,
230                                          exc_info)
231                   
232            else:
233                log.error('No referrer set for redirect following logout')
234                _start_response = start_response
235               
236            # Clear user details from beaker session
237            for keyName in self.__class__.sessionKeyNames:
238                session.pop(keyName, None)
239            session.save()
240        else:
241            log.debug("SessionHandlerMiddleware: checking for REMOTE_* "
242                      "environment variable settings set by OpenID Relying "
243                      "Party signin...")
244           
245            if 'username' not in session and 'REMOTE_USER' in environ:
246                log.debug("SessionHandlerMiddleware: updating session with "
247                          "username=%s", environ['REMOTE_USER'])
248               
249                session['username'] = environ['REMOTE_USER']
250               
251            remoteUserData = environ.get('REMOTE_USER_DATA', '')   
252            if remoteUserData:
253                log.debug("SessionHandlerMiddleware: found REMOTE_USER_DATA="
254                          "%s, set from OpenID Relying Party signin",
255                          environ['REMOTE_USER_DATA'])
256               
257                # eval is safe here because AuthKit cookie is signed and
258                # AuthKit middleware checks for tampering
259                if 'sessionManagerURI' not in session or \
260                   'sessionId' not in session:
261                   
262                    axData = eval(remoteUserData)
263                    if isinstance(axData, dict) and 'ax' in axData:
264                        sessionManagerURI = \
265                                    axData['ax']['value.sessionManagerURI.1']
266                        session['sessionManagerURI'] = sessionManagerURI
267   
268                        sessionId = axData['ax']['value.sessionId.1']
269                        session['sessionId'] = sessionId
270   
271                        log.debug("SessionHandlerMiddleware: updated session "
272                                  "with sessionManagerURI=%s and "
273                                  "sessionId=%s", 
274                                  sessionManagerURI, 
275                                  sessionId)
276                   
277                # Reset cookie removing user data
278                environ['paste.auth_tkt.set_user'](session['username'])
279
280            _start_response = start_response
281           
282        return self._app(environ, _start_response)
283
284
285from authkit.authenticate.multi import MultiHandler
286
287class AuthenticationMiddlewareConfigError(NDGSecurityMiddlewareConfigError):
288    '''Authentication Middleware Configuration error'''
289
290class AuthenticationMiddleware(MultiHandler, NDGSecurityMiddlewareBase):
291    '''Handler to intercept 401 Unauthorized HTTP responses and redirect to an
292    authentication URI.  This class also implements a redirect handler to
293    redirect back to the referrer if logout is invoked.
294    '''
295
296    def __init__(self, app, global_conf, prefix='', **app_conf):
297        '''
298        @type app: callable following WSGI interface
299        @param app: next middleware application in the chain     
300        @type global_conf: dict       
301        @param global_conf: PasteDeploy global configuration dictionary
302        @type prefix: basestring
303        @param prefix: prefix for configuration items
304        @type app_conf: dict       
305        @param app_conf: PasteDeploy application specific configuration
306        dictionary
307        '''
308       
309        # Set logout URI parameter from AuthKit settings if not otherwise set
310        sessionHandlerPrefix = prefix + SessionHandlerMiddleware.prefix
311        signoutPathParamName = sessionHandlerPrefix + 'signoutPath'
312        if signoutPathParamName not in app_conf:
313            try:                   
314                app_conf[signoutPathParamName] = app_conf[
315                                                'authkit.cookie.signoutpath']
316            except KeyError, e:
317                raise AuthenticationMiddlewareConfigError("No %s or %s keys "
318                                                    "set" % 
319                                                    (signoutPathParamName, e))
320       
321        app = SessionHandlerMiddleware(app, 
322                                       global_conf, 
323                                       prefix=sessionHandlerPrefix,
324                                       **app_conf)
325       
326        # Remove session handler middleware specific parameters
327        for k in app_conf.keys():
328            if k.startswith(sessionHandlerPrefix):
329                del app_conf[k]
330       
331        app = authkit.authenticate.middleware(app, app_conf)       
332        app = SessionMiddleware(app, 
333                                environ_key='beaker.session.ndg.security',
334                                **app_conf)
335       
336        MultiHandler.__init__(self, app)
337
338        self.add_method(AuthenticationRedirectMiddleware.id, 
339                        AuthenticationRedirectMiddleware.filter_app_factory, 
340                        global_conf,
341                        prefix=prefix,
342                        **app_conf)
343       
344        self.add_checker(AuthenticationRedirectMiddleware.id, 
345                         AuthenticationRedirectMiddleware.checker)
346
Note: See TracBrowser for help on using the repository browser.