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

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

Added a Policy Information Point to encapsulate subject attribute retrieval.

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