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

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

1.0.1 rc2

Added capability for Policy Information Point to query an Attribute Authority directly without a remote Session Manager intermediary to cache credentials. This is the use case for ESG based IdP connecting to NDG services.

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.ndg.security'
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            log.debug("SessionHandlerMiddleware: caught signout path [%s]",
185                      self.signoutPath)
186           
187            referer = session.get(self.__class__.logoutReturn2URIArgName)
188            if referer is not None:
189                def _start_response(status, header, exc_info=None):
190                    header.extend([('Location', referer)])
191                    return start_response(self.getStatusMessage(302), 
192                                          header,
193                                          exc_info)
194                   
195            else:
196                log.error('No referer set for redirect following logout')
197                _start_response = start_response
198               
199            # Clear user details from beaker session
200            session.pop('username', None)
201            session.pop('sessionManagerURI', None)
202            session.pop('sessionId', None)
203            session.save()
204        else:
205            log.debug("SessionHandlerMiddleware: checking for REMOTE_* "
206                      "environment variable settings set by OpenID Relying "
207                      "Party signin...")
208           
209            if 'username' not in session and 'REMOTE_USER' in environ:
210                log.debug("SessionHandlerMiddleware: updating session with "
211                          "username=%s", environ['REMOTE_USER'])
212               
213                session['username'] = environ['REMOTE_USER']
214               
215            if environ.get('REMOTE_USER_DATA', ''):
216                log.debug("SessionHandlerMiddleware: found REMOTE_USER_DATA="
217                          "%s, set from OpenID Relying Party signin")
218               
219                # eval is safe here because AuthKit cookie is signed and
220                # AuthKit middleware checks for tampering
221                if 'sessionManagerURI' not in session or \
222                   'sessionId' not in session:
223                    axData = eval(environ['REMOTE_USER_DATA'])
224                    sessionManagerURI=axData['ax']['value.sessionManagerURI.1']
225                    session['sessionManagerURI'] = sessionManagerURI
226
227                    sessionId = axData['ax']['value.sessionId.1']
228                    session['sessionId'] = sessionId
229
230                    log.debug("SessionHandlerMiddleware: updated session "
231                              "with sessionManagerURI=%s and sessionId=%s", 
232                              sessionManagerURI, sessionId)
233                   
234                # Reset cookie removing user data
235                environ['paste.auth_tkt.set_user'](session['username'])
236               
237            # Set a return to address for logout
238            session[self.__class__.logoutReturn2URIArgName] = self.pathInfo
239            session.save()
240                     
241            _start_response = start_response
242           
243        return self._app(environ, _start_response)
244
245
246from authkit.authenticate.multi import MultiHandler
247
248class AuthenticationMiddlewareConfigError(NDGSecurityMiddlewareConfigError):
249    '''Authentication Middleware Configuration error'''
250
251class AuthenticationMiddleware(MultiHandler, NDGSecurityMiddlewareBase):
252    '''Handler to intercept 401 Unauthorized HTTP responses and redirect to an
253    authentication URI.  This class also implements a redirect handler to
254    redirect back to the referrer if logout is invoked.
255    '''
256
257    def __init__(self, app, global_conf, prefix='', **app_conf):
258        '''
259        @type app: callable following WSGI interface
260        @param app: next middleware application in the chain     
261        @type global_conf: dict       
262        @param global_conf: PasteDeploy global configuration dictionary
263        @type prefix: basestring
264        @param prefix: prefix for configuration items
265        @type app_conf: dict       
266        @param app_conf: PasteDeploy application specific configuration
267        dictionary
268        '''
269       
270        # Set logout URI parameter from AuthKit settings if not otherwise set
271        sessionHandlerPrefix = prefix + SessionHandlerMiddleware.prefix
272        signoutPathParamName = sessionHandlerPrefix + 'signoutPath'
273        if signoutPathParamName not in app_conf:
274            try:                   
275                app_conf[signoutPathParamName] = app_conf[
276                                                'authkit.cookie.signoutpath']
277            except KeyError, e:
278                raise AuthenticationMiddlewareConfigError("No %s or %s keys "
279                                                    "set" % 
280                                                    (signoutPathParamName, e))
281       
282        app = SessionHandlerMiddleware(app, 
283                                       global_conf, 
284                                       prefix=sessionHandlerPrefix,
285                                       **app_conf)
286       
287        # Remove session handler middleware specific parameters
288        for k in app_conf.keys():
289            if k.startswith(sessionHandlerPrefix):
290                del app_conf[k]
291       
292        app = authkit.authenticate.middleware(app, app_conf)       
293        app = SessionMiddleware(app, 
294                                environ_key='beaker.session.ndg.security',
295                                **app_conf)
296       
297        MultiHandler.__init__(self, app)
298
299        self.add_method(AuthenticationRedirectMiddleware.id, 
300                        AuthenticationRedirectMiddleware.filter_app_factory, 
301                        global_conf,
302                        prefix=prefix,
303                        **app_conf)
304       
305        self.add_checker(AuthenticationRedirectMiddleware.id, 
306                         AuthenticationRedirectMiddleware.checker)
307
Note: See TracBrowser for help on using the repository browser.