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

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/authn.py@5315
Revision 5315, 13.3 KB checked in by pjkersha, 12 years ago (diff)
  • Fix to logout in ndg.security.server.wsgi.authn
  • improved graphics with anti-aliasing to soften edges
  • added README files into integration test packages
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            if environ.get('REMOTE_USER_DATA', ''):
252                log.debug("SessionHandlerMiddleware: found REMOTE_USER_DATA="
253                          "%s, set from OpenID Relying Party signin",
254                          environ['REMOTE_USER_DATA'])
255               
256                # eval is safe here because AuthKit cookie is signed and
257                # AuthKit middleware checks for tampering
258                if 'sessionManagerURI' not in session or \
259                   'sessionId' not in session:
260                    axData = eval(environ['REMOTE_USER_DATA'])
261                    sessionManagerURI = \
262                                    axData['ax']['value.sessionManagerURI.1']
263                    session['sessionManagerURI'] = sessionManagerURI
264
265                    sessionId = axData['ax']['value.sessionId.1']
266                    session['sessionId'] = sessionId
267
268                    log.debug("SessionHandlerMiddleware: updated session "
269                              "with sessionManagerURI=%s and sessionId=%s", 
270                              sessionManagerURI, sessionId)
271                   
272                # Reset cookie removing user data
273                environ['paste.auth_tkt.set_user'](session['username'])
274
275            _start_response = start_response
276           
277        return self._app(environ, _start_response)
278
279
280from authkit.authenticate.multi import MultiHandler
281
282class AuthenticationMiddlewareConfigError(NDGSecurityMiddlewareConfigError):
283    '''Authentication Middleware Configuration error'''
284
285class AuthenticationMiddleware(MultiHandler, NDGSecurityMiddlewareBase):
286    '''Handler to intercept 401 Unauthorized HTTP responses and redirect to an
287    authentication URI.  This class also implements a redirect handler to
288    redirect back to the referrer if logout is invoked.
289    '''
290
291    def __init__(self, app, global_conf, prefix='', **app_conf):
292        '''
293        @type app: callable following WSGI interface
294        @param app: next middleware application in the chain     
295        @type global_conf: dict       
296        @param global_conf: PasteDeploy global configuration dictionary
297        @type prefix: basestring
298        @param prefix: prefix for configuration items
299        @type app_conf: dict       
300        @param app_conf: PasteDeploy application specific configuration
301        dictionary
302        '''
303       
304        # Set logout URI parameter from AuthKit settings if not otherwise set
305        sessionHandlerPrefix = prefix + SessionHandlerMiddleware.prefix
306        signoutPathParamName = sessionHandlerPrefix + 'signoutPath'
307        if signoutPathParamName not in app_conf:
308            try:                   
309                app_conf[signoutPathParamName] = app_conf[
310                                                'authkit.cookie.signoutpath']
311            except KeyError, e:
312                raise AuthenticationMiddlewareConfigError("No %s or %s keys "
313                                                    "set" % 
314                                                    (signoutPathParamName, e))
315       
316        app = SessionHandlerMiddleware(app, 
317                                       global_conf, 
318                                       prefix=sessionHandlerPrefix,
319                                       **app_conf)
320       
321        # Remove session handler middleware specific parameters
322        for k in app_conf.keys():
323            if k.startswith(sessionHandlerPrefix):
324                del app_conf[k]
325       
326        app = authkit.authenticate.middleware(app, app_conf)       
327        app = SessionMiddleware(app, 
328                                environ_key='beaker.session.ndg.security',
329                                **app_conf)
330       
331        MultiHandler.__init__(self, app)
332
333        self.add_method(AuthenticationRedirectMiddleware.id, 
334                        AuthenticationRedirectMiddleware.filter_app_factory, 
335                        global_conf,
336                        prefix=prefix,
337                        **app_conf)
338       
339        self.add_checker(AuthenticationRedirectMiddleware.id, 
340                         AuthenticationRedirectMiddleware.checker)
341
Note: See TracBrowser for help on using the repository browser.