source: TI12-security/trunk/NDGSecurity/python/ndg_security_server/ndg/security/server/wsgi/authn.py @ 6246

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

User selection of OpenID Provider AX attributes completed: OpenIDProviderMiddleware.do_allow now correctly reviews and updates the response returned to the RP checking user selection of AX parameters POST'ed from the decide page.

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__)
17
18import re
19import base64
20import httplib
21import urllib
22from paste.request import construct_url, parse_querystring
23import authkit.authenticate
24
25from ndg.security.server.wsgi import NDGSecurityMiddlewareBase, \
26    NDGSecurityMiddlewareError, NDGSecurityMiddlewareConfigError       
27
28class AuthnException(NDGSecurityMiddlewareError):
29    """Base exception for this module"""
30   
31   
32class HTTPBasicAuthMiddlewareError(AuthnException):
33    """Base exception type for HTTPBasicAuthMiddleware"""
34   
35   
36class HTTPBasicAuthMiddlewareConfigError(NDGSecurityMiddlewareConfigError):
37    """Configuration error with HTTP Basic Auth middleware"""
38
39
40class HTTPBasicAuthUnauthorized(HTTPBasicAuthMiddlewareError): 
41    """Raise from custom authentication interface in order to set HTTP
42    401 Unuathorized response"""
43   
44   
45class HTTPBasicAuthMiddleware(NDGSecurityMiddlewareBase):
46    '''HTTP Basic Authentication Middleware
47   
48    '''
49   
50    AUTHN_FUNC_ENV_KEYNAME = ('ndg.security.server.wsgi.authn.'
51                              'HTTPBasicAuthMiddleware.authenticate')
52    AUTHN_FUNC_ENV_KEYNAME_OPTNAME = 'authnFuncEnvKeyName'       
53    PARAM_PREFIX = 'http.auth.basic.'
54    HTTP_HDR_FIELDNAME = 'basic'
55    FIELD_SEP = ':'
56    AUTHZ_ENV_KEYNAME = 'HTTP_AUTHORIZATION'
57   
58    RE_PATH_MATCH_LIST_OPTNAME = 'rePathMatchList'
59   
60    def __init__(self, app, app_conf, prefix=PARAM_PREFIX, **local_conf):
61        self.__rePathMatchList = None
62        self.__authnFuncEnvironKeyName = None
63       
64        super(HTTPBasicAuthMiddleware, self).__init__(app, app_conf, 
65                                                      **local_conf)
66
67        rePathMatchListOptName = prefix + \
68                            HTTPBasicAuthMiddleware.RE_PATH_MATCH_LIST_OPTNAME
69        rePathMatchListVal = app_conf.pop(rePathMatchListOptName, '')
70       
71        self.rePathMatchList = [re.compile(i) 
72                                for i in rePathMatchListVal.split()]
73
74        paramName = prefix + \
75                    HTTPBasicAuthMiddleware.AUTHN_FUNC_ENV_KEYNAME_OPTNAME
76                   
77        self.authnFuncEnvironKeyName = local_conf.get(paramName,
78                                HTTPBasicAuthMiddleware.AUTHN_FUNC_ENV_KEYNAME)
79
80    def _getAuthnFuncEnvironKeyName(self):
81        return self.__authnFuncEnvironKeyName
82
83    def _setAuthnFuncEnvironKeyName(self, value):
84        if not isinstance(value, basestring):
85            raise TypeError('Expecting string type for '
86                            '"authnFuncEnvironKeyName"; got %r type' % 
87                            type(value))
88        self.__authnFuncEnvironKeyName = value
89
90    authnFuncEnvironKeyName = property(fget=_getAuthnFuncEnvironKeyName, 
91                                       fset=_setAuthnFuncEnvironKeyName, 
92                                       doc="key name in environ for the "
93                                           "custom authentication function "
94                                           "used by this class")
95
96    def _getRePathMatchList(self):
97        return self.__rePathMatchList
98
99    def _setRePathMatchList(self, value):
100        if not isinstance(value, (list, tuple)):
101            raise TypeError('Expecting list or tuple type for '
102                            '"rePathMatchList"; got %r' % type(value))
103       
104        self.__rePathMatchList = value
105
106    rePathMatchList = property(fget=_getRePathMatchList, 
107                               fset=_setRePathMatchList, 
108                               doc="List of regular expressions determine the "
109                                   "URI paths intercepted by this middleware")
110
111    def _pathMatch(self):
112        """Apply a list of regular expression matching patterns to the contents
113        of environ['PATH_INFO'], if any match, return True.  This method is
114        used to determine whether to apply SSL client authentication
115        """
116        path = self.pathInfo
117        for regEx in self.rePathMatchList:
118            if regEx.match(path):
119                return True
120           
121        return False   
122
123    def _parseCredentials(self):
124        """Extract username and password from HTTP_AUTHORIZATION environ key
125       
126        @rtype: tuple
127        @return: username and password.  If the key is not set or the auth
128        method is not basic return a two element tuple with elements both set
129        to None
130        """
131        basicAuthHdr = self.environ.get(
132                                    HTTPBasicAuthMiddleware.AUTHZ_ENV_KEYNAME)
133        if basicAuthHdr is None:
134            log.debug("No %r setting in environ: skipping HTTP Basic Auth",
135                      HTTPBasicAuthMiddleware.AUTHZ_ENV_KEYNAME)
136            return None, None
137                       
138        method, encodedCreds = basicAuthHdr.split(None, 1)
139        if method.lower() != HTTPBasicAuthMiddleware.HTTP_HDR_FIELDNAME:
140            log.debug("Auth method is %r not %r: skipping request",
141                      method, HTTPBasicAuthMiddleware.HTTP_HDR_FIELDNAME)
142            return None, None
143           
144        creds = base64.decodestring(encodedCreds)
145        username, password = creds.split(HTTPBasicAuthMiddleware.FIELD_SEP, 1)
146        return username, password
147
148    @NDGSecurityMiddlewareBase.initCall
149    def __call__(self, environ, start_response):
150        """Authenticate based HTTP header elements as specified by the HTTP
151        Basic Authentication spec."""
152        log.debug("HTTPBasicAuthNMiddleware.__call__ ...")
153       
154        if not self._pathMatch():
155            return self._app(environ, start_response)
156       
157        authenticate = environ.get(self.authnFuncEnvironKeyName)
158        if authenticate is None:
159            # HTTP 500 default is right for this error
160            raise HTTPBasicAuthMiddlewareConfigError("No authentication "
161                                                     "function set in environ")
162           
163        username, password = self._parseCredentials()
164        if username is None:
165            return self._setErrorResponse(code=httplib.UNAUTHORIZED)
166       
167        # Call authentication application
168        try:
169            return authenticate(environ, start_response, username, password)
170       
171        except HTTPBasicAuthUnauthorized, e:
172            log.error(e)
173            return self._setErrorResponse(code=httplib.UNAUTHORIZED)
174        else:
175            return self._app(environ, start_response)
176
177
178# AuthKit based HTTP basic authentication plugin not currently needed but may
179# need resurrecting
180
181#from authkit.permissions import UserIn
182#from ndg.security.server.wsgi.utils.sessionmanagerclient import \
183#    WSGISessionManagerClient
184#           
185#class HTTPBasicAuthentication(object):
186#    '''Authkit based HTTP Basic Authentication.   __call__ defines a
187#    validation function to fit with the pattern for the AuthKit interface
188#    '''
189#   
190#    def __init__(self):
191#        self._userIn = UserIn([])
192#       
193#    def __call__(self, environ, username, password):
194#        """validation function"""
195#        try:
196#            client = WSGISessionManagerClient(environ=environ,
197#                                environKeyName=self.sessionManagerFilterID)
198#            res = client.connect(username, passphrase=password)
199#
200#            if username not in self._userIn.users:
201#                self._userIn.users += [username]
202#           
203#            # TODO: set session
204#               
205#        except Exception, e:
206#            return False
207#        else:
208#            return True
209
210
211class SessionMiddlewareBase(NDGSecurityMiddlewareBase):
212    """Base class for Authentication redirect middleware and Session Handler
213    middleware
214   
215    @type propertyDefaults: dict
216    @cvar propertyDefaults: valid configuration property keywords
217    """   
218    propertyDefaults = {
219        'sessionKey': 'beaker.session.ndg.security'
220    }
221   
222    _isAuthenticated = lambda self: \
223        SessionMiddlewareBase.USERNAME_SESSION_KEYNAME in \
224        self.environ.get(self.sessionKey, ())
225       
226    isAuthenticated = property(fget=_isAuthenticated,
227                               doc='boolean to indicate is user logged in')
228
229       
230class AuthnRedirectMiddleware(SessionMiddlewareBase):
231    """Base class for Authentication HTTP redirect initiator and redirect
232    response WSGI middleware
233
234    @type RETURN2URI_ARGNAME: basestring
235    @cvar RETURN2URI_ARGNAME: name of URI query argument used to pass the
236    return to URI between initiator and consumer classes"""
237    RETURN2URI_ARGNAME = 'ndg.security.r'
238
239
240class AuthnRedirectInitiatorMiddleware(AuthnRedirectMiddleware):
241    '''Middleware to initiate a redirect to another URI if a user is not
242    authenticated i.e. security cookie is not set
243   
244    AuthKit.authenticate.middleware must be in place upstream of this
245    middleware.  AuthenticationMiddleware wrapper handles this.
246   
247    @type propertyDefaults: dict
248    @cvar propertyDefaults: valid configuration property keywords   
249    '''
250    propertyDefaults = {
251        'redirectURI': None,
252    }
253    propertyDefaults.update(AuthnRedirectMiddleware.propertyDefaults)
254   
255
256    TRIGGER_HTTP_STATUS_CODE = '401'
257    MIDDLEWARE_ID = 'AuthnRedirectInitiatorMiddleware'
258
259    def __init__(self, app, global_conf, **app_conf):
260        '''
261        @type app: callable following WSGI interface
262        @param app: next middleware application in the chain     
263        @type global_conf: dict       
264        @param global_conf: PasteDeploy global configuration dictionary
265        @type prefix: basestring
266        @param prefix: prefix for configuration items
267        @type app_conf: dict       
268        @param app_conf: PasteDeploy application specific configuration
269        dictionary
270        '''
271        self._redirectURI = None
272        super(AuthnRedirectInitiatorMiddleware, self).__init__(app, 
273                                                               global_conf, 
274                                                               **app_conf)
275       
276    @NDGSecurityMiddlewareBase.initCall
277    def __call__(self, environ, start_response):
278        '''Invoke redirect if user is not authenticated'''
279       
280        log.debug("AuthnRedirectInitiatorMiddleware.__call__ ...")
281       
282        if self.isAuthenticated:
283            # Call next app in stack
284            return self._app(environ, start_response)       
285        else:
286            # User is not authenticated - Redirect to OpenID Relying Party URI
287            # for user OpenID entry
288            return self._setRedirectResponse()
289   
290    def _setRedirectURI(self, uri):
291        if not isinstance(uri, basestring):
292            raise TypeError("Redirect URI must be set to string type")   
293         
294        self._redirectURI = uri
295       
296    def _getRedirectURI(self):
297        return self._redirectURI
298   
299    redirectURI = property(fget=_getRedirectURI,
300                       fset=_setRedirectURI,
301                       doc="URI to redirect to if user is not authenticated")
302
303    def _setRedirectResponse(self):
304        """Construct a redirect response adding in a return to address in a
305        URI query argument
306       
307        @rtype: basestring
308        @return: redirect response
309        """       
310        return2URI = construct_url(self.environ)
311        quotedReturn2URI = urllib.quote(return2URI, safe='')
312        return2URIQueryArg = urllib.urlencode(
313                    {AuthnRedirectInitiatorMiddleware.RETURN2URI_ARGNAME: 
314                     quotedReturn2URI})
315
316        redirectURI = self.redirectURI
317       
318        if '?' in redirectURI:
319            if redirectURI.endswith('&'):
320                redirectURI += return2URIQueryArg
321            else:
322                redirectURI += '&' + return2URIQueryArg
323        else:
324            if redirectURI.endswith('?'):
325                redirectURI += return2URIQueryArg
326            else:
327                redirectURI += '?' + return2URIQueryArg
328         
329        # Call NDGSecurityMiddlewareBase.redirect utility method     
330        return self.redirect(redirectURI)
331       
332    @classmethod
333    def checker(cls, environ, status, headers):
334        """Set the MultiHandler checker function for triggering this
335        middleware.  In this case, it's a HTTP 401 Unauthorized response
336        detected in the middleware chain
337        """
338        if status.startswith(cls.TRIGGER_HTTP_STATUS_CODE):
339            log.debug("%s.checker caught status [%s]: invoking authentication"
340                      " handler", cls.__name__, cls.TRIGGER_HTTP_STATUS_CODE)
341            return True
342        else:
343            log.debug("%s.checker skipping status [%s]", cls.__name__, status)
344            return False
345
346
347class AuthnRedirectResponseMiddleware(AuthnRedirectMiddleware):
348    """Compliment to AuthnRedirectInitiatorMiddleware
349    functioning as the opposite end of the HTTP redirect interface.  It
350    performs the following tasks:
351    - Detect a redirect URI set in a URI query argument and copy it into
352    a user session object.
353    - Redirect back to the redirect URI once a user is authenticated
354   
355    Also see,
356    ndg.security.server.wsgi.openid.relyingparty.OpenIDRelyingPartyMiddleware
357    which performs a similar function.
358    """
359    @NDGSecurityMiddlewareBase.initCall
360    def __call__(self, environ, start_response):
361        session = environ[self.sessionKey]
362       
363        # Check for return to address in URI query args set by
364        # AuthnRedirectInitiatorMiddleware in application code stack
365        if environ['REQUEST_METHOD'] == "GET":
366            params = dict(parse_querystring(environ))
367        else:
368            params = {}
369       
370        # Store the return URI query argument in a beaker session
371        quotedReferrer = params.get(self.__class__.RETURN2URI_ARGNAME, '')
372        referrerURI = urllib.unquote(quotedReferrer)
373        if referrerURI:
374            session[self.__class__.RETURN2URI_ARGNAME] = referrerURI
375            session.save()
376           
377        # Check for a return URI setting in the beaker session and if the user
378        # is authenticated, redirect to this URL deleting the beaker session
379        # URL setting
380        return2URI = session.get(self.__class__.RETURN2URI_ARGNAME)   
381        if self.isAuthenticated and return2URI:
382            del session[self.__class__.RETURN2URI_ARGNAME]
383            session.save()
384            return self.redirect(return2URI)
385
386        return self._app(environ, start_response)
387
388
389class AuthKitRedirectResponseMiddleware(AuthnRedirectResponseMiddleware):
390    """Overload isAuthenticated method in parent class to set Authenticated
391    state based on presence of AuthKit 'REMOTE_USER' environ variable
392    """
393    _isAuthenticated = lambda self: \
394        AuthnRedirectResponseMiddleware.USERNAME_ENVIRON_KEYNAME in self.environ
395       
396    isAuthenticated = property(fget=_isAuthenticated,
397                               doc="Boolean indicating if AuthKit "
398                                   "'REMOTE_USER' environment variable is set")
399    def __init__(self, app, app_conf, **local_conf):
400        super(AuthKitRedirectResponseMiddleware, self).__init__(app, app_conf,
401                                                                **local_conf)
402    @NDGSecurityMiddlewareBase.initCall
403    def __call__(self, environ, start_response):
404        return super(AuthKitRedirectResponseMiddleware, self).__call__(environ,
405                                                                start_response)
406       
407
408class SessionHandlerMiddlewareError(AuthnException):
409    """Base exception for SessionHandlerMiddleware"""
410           
411class SessionHandlerMiddlewareConfigError(SessionHandlerMiddlewareError):
412    """Configuration errors from SessionHandlerMiddleware"""
413   
414class OpenIdAXConfigError(SessionHandlerMiddlewareError):
415    """Error parsing OpenID Ax (Attribute Exchange) parameters"""
416   
417   
418class SessionHandlerMiddleware(SessionMiddlewareBase):
419    '''Middleware to:
420    - establish user session details following redirect from OpenID Relying
421    Party sign-in or SSL Client authentication
422    - end session redirecting back to referrer URI following call to a logout
423    URI as implemented in AuthKit
424    '''
425    AX_SESSION_KEYNAME = 'openid.ax'
426    SM_URI_SESSION_KEYNAME = 'sessionManagerURI'
427    ID_SESSION_KEYNAME = 'sessionId'
428    PEP_CTX_SESSION_KEYNAME = 'pepCtx'
429    CREDENTIAL_WALLET_SESSION_KEYNAME = 'credentialWallet'
430   
431    SESSION_KEYNAMES = (
432        SessionMiddlewareBase.USERNAME_SESSION_KEYNAME, 
433        SM_URI_SESSION_KEYNAME, 
434        ID_SESSION_KEYNAME, 
435        PEP_CTX_SESSION_KEYNAME, 
436        CREDENTIAL_WALLET_SESSION_KEYNAME
437    )
438   
439    AX_KEYNAME = 'ax'
440    SM_URI_AX_KEYNAME = 'value.sessionManagerURI.1'
441    SESSION_ID_AX_KEYNAME = 'value.sessionId.1'
442   
443    AUTHKIT_COOKIE_SIGNOUT_PARAMNAME = 'authkit.cookie.signoutpath'
444    SIGNOUT_PATH_PARAMNAME = 'signoutPath'
445    SESSION_KEY_PARAMNAME = 'sessionKey'
446    propertyDefaults = {
447        SIGNOUT_PATH_PARAMNAME: None,
448        SESSION_KEY_PARAMNAME: 'beaker.session.ndg.security'
449    }
450   
451    AUTH_TKT_SET_USER_ENVIRON_KEYNAME = 'paste.auth_tkt.set_user'
452   
453    PARAM_PREFIX = 'sessionHandler.'
454   
455    def __init__(self, app, global_conf, prefix=PARAM_PREFIX, **app_conf):
456        '''
457        @type app: callable following WSGI interface
458        @param app: next middleware application in the chain     
459        @type global_conf: dict       
460        @param global_conf: PasteDeploy global configuration dictionary
461        @type prefix: basestring
462        @param prefix: prefix for configuration items
463        @type app_conf: dict       
464        @param app_conf: PasteDeploy application specific configuration
465        dictionary
466        '''
467        signoutPathParamName = prefix + \
468                                SessionHandlerMiddleware.SIGNOUT_PATH_PARAMNAME
469       
470        if signoutPathParamName not in app_conf:
471            authKitSignOutPath = app_conf.get(
472                    SessionHandlerMiddleware.AUTHKIT_COOKIE_SIGNOUT_PARAMNAME)
473           
474            if authKitSignOutPath:
475                app_conf[signoutPathParamName] = authKitSignOutPath
476               
477                log.info('Set signoutPath=%s from "%s" setting', 
478                     authKitSignOutPath,
479                     SessionHandlerMiddleware.AUTHKIT_COOKIE_SIGNOUT_PARAMNAME)
480            else:
481                raise SessionHandlerMiddlewareConfigError(
482                                        '"signoutPath" parameter is not set')
483           
484        super(SessionHandlerMiddleware, self).__init__(app,
485                                                       global_conf,
486                                                       prefix=prefix, 
487                                                       **app_conf)
488       
489    @NDGSecurityMiddlewareBase.initCall
490    def __call__(self, environ, start_response):
491        """Manage setting of session from AuthKit following OpenID Relying
492        Party sign in and manage logout
493       
494        @type environ: dict
495        @param environ: WSGI environment variables dictionary
496        @type start_response: function
497        @param start_response: standard WSGI start response function
498        """
499        log.debug("SessionHandlerMiddleware.__call__ ...")
500       
501        session = environ.get(self.sessionKey)
502        if session is None:
503            raise SessionHandlerMiddlewareConfigError(
504                   'SessionHandlerMiddleware.__call__: No beaker session key '
505                   '"%s" found in environ' % self.sessionKey)
506       
507        if self.signoutPath and self.pathInfo == self.signoutPath:
508            log.debug("SessionHandlerMiddleware.__call__: caught sign out "
509                      "path [%s]", self.signoutPath)
510           
511            referrer = environ.get('HTTP_REFERER')
512            if referrer is not None:
513                def _start_response(status, header, exc_info=None):
514                    """Alter the header to send a redirect to the logout
515                    referrer address"""
516                    filteredHeader = [(field, val) for field, val in header
517                                      if field.lower() != 'location']       
518                    filteredHeader.extend([('Location', referrer)])
519                    return start_response(self.getStatusMessage(302), 
520                                          filteredHeader,
521                                          exc_info)
522                   
523            else:
524                log.error('No referrer set for redirect following logout')
525                _start_response = start_response
526               
527            # Clear user details from beaker session
528            for keyName in self.__class__.SESSION_KEYNAMES:
529                session.pop(keyName, None)
530            session.save()
531        else:
532            log.debug("SessionHandlerMiddleware.__call__: checking for "
533                      "REMOTE_* environment variable settings set by OpenID "
534                      "Relying Party signin...")
535           
536            if SessionHandlerMiddleware.USERNAME_SESSION_KEYNAME not in session\
537               and SessionHandlerMiddleware.USERNAME_ENVIRON_KEYNAME in environ:
538                log.debug("SessionHandlerMiddleware.__call__: updating session "
539                          "username=%s", environ[
540                            SessionHandlerMiddleware.USERNAME_ENVIRON_KEYNAME])
541               
542                session[SessionHandlerMiddleware.USERNAME_SESSION_KEYNAME
543                        ] = environ[
544                            SessionHandlerMiddleware.USERNAME_ENVIRON_KEYNAME]
545                session.save()
546               
547            remoteUserData = environ.get(
548                        SessionHandlerMiddleware.USERDATA_ENVIRON_KEYNAME, '')   
549            if remoteUserData:
550                log.debug("SessionHandlerMiddleware.__call__: found "
551                          "REMOTE_USER_DATA=%s, set from OpenID Relying Party "
552                          "signin", 
553                          environ[
554                              SessionHandlerMiddleware.USERDATA_ENVIRON_KEYNAME
555                          ])
556               
557                # eval is safe here because AuthKit cookie is signed and
558                # AuthKit middleware checks for tampering
559                if (SessionHandlerMiddleware.SM_URI_SESSION_KEYNAME not in 
560                    session or 
561                    SessionHandlerMiddleware.ID_SESSION_KEYNAME not in session):
562                   
563                    axData = eval(remoteUserData)
564                    if (isinstance(axData, dict) and 
565                        SessionHandlerMiddleware.AX_KEYNAME in axData):
566                       
567                        ax = axData[SessionHandlerMiddleware.AX_KEYNAME]
568                       
569                        # Save attributes keyed by attribute name
570                        session[
571                            SessionHandlerMiddleware.AX_SESSION_KEYNAME
572                        ] = SessionHandlerMiddleware._parseOpenIdAX(ax)
573                       
574                        log.debug("SessionHandlerMiddleware.__call__: updated "
575                                  "session with OpenID AX values: %r" % 
576                                  session[
577                                    SessionHandlerMiddleware.AX_SESSION_KEYNAME
578                                  ])
579                           
580                        # Save Session Manager specific attributes
581                        sessionManagerURI = ax.get(
582                                SessionHandlerMiddleware.SM_URI_AX_KEYNAME)
583                           
584                        session[SessionHandlerMiddleware.SM_URI_SESSION_KEYNAME
585                                ] = sessionManagerURI
586   
587                        sessionId = ax.get(
588                                SessionHandlerMiddleware.SESSION_ID_AX_KEYNAME)
589                        session[SessionHandlerMiddleware.ID_SESSION_KEYNAME
590                                ] = sessionId
591                               
592                        session.save()
593                       
594                        log.debug("SessionHandlerMiddleware.__call__: updated "
595                                  "session "
596                                  "with sessionManagerURI=%s and "
597                                  "sessionId=%s", 
598                                  sessionManagerURI, 
599                                  sessionId)
600                   
601                # Reset cookie removing user data
602                setUser = environ[
603                    SessionHandlerMiddleware.AUTH_TKT_SET_USER_ENVIRON_KEYNAME]
604                setUser(
605                    session[SessionHandlerMiddleware.USERNAME_SESSION_KEYNAME])
606            else:
607                log.debug("SessionHandlerMiddleware.__call__: REMOTE_USER_DATA "
608                          "is not set")
609
610            _start_response = start_response
611           
612        return self._app(environ, _start_response)
613   
614    @staticmethod                   
615    def _parseOpenIdAX(ax):
616        """Return a dictionary of attribute exchange attributes parsed from the
617        OpenID Provider response set in the REMOTE_USER_DATA AuthKit environ
618        key
619       
620        @param ax: dictionary of AX parameters - format of keys is e.g.
621        count.paramName, value.paramName.<n>, type.paramName
622        @type ax: dict
623        @return: dictionary of parameters keyed by parameter with values for
624        each parameter a tuple of count.paramName values
625        @rtype: dict
626        """
627       
628        # Copy Attributes into session
629        outputKeys = [k.replace('type.', '') for k in ax.keys()
630                      if k.startswith('type.')]
631       
632        output = {}
633        for outputKey in outputKeys:
634            axCountKeyName = 'count.' + outputKey
635            axCount = int(ax[axCountKeyName])
636           
637            axValueKeyPrefix = 'value.%s.' % outputKey
638            output[outputKey] = tuple([v for k, v in ax.items() 
639                                       if k.startswith(axValueKeyPrefix)])
640           
641            nVals = len(output[outputKey])
642            if nVals != axCount:
643                raise OpenIdAXConfigError('Got %d parameters for AX attribute '
644                                          '"%s"; but "%s" AX key is set to %d'
645                                          % (nVals,
646                                             axCountKeyName,
647                                             axCountKeyName,
648                                             axCount))
649                                             
650        return output
651
652
653from authkit.authenticate.multi import MultiHandler
654
655class AuthenticationMiddlewareConfigError(NDGSecurityMiddlewareConfigError):
656    '''Authentication Middleware Configuration error'''
657
658
659class AuthenticationMiddleware(MultiHandler, NDGSecurityMiddlewareBase):
660    '''Top-level class encapsulates session and authentication handlers
661    in this module
662   
663    Handler to intercept 401 Unauthorized HTTP responses and redirect to an
664    authentication URI.  This class also implements a redirect handler to
665    redirect back to the referrer if logout is invoked.
666    '''
667
668    def __init__(self, app, global_conf, prefix='', **app_conf):
669        '''
670        @type app: callable following WSGI interface
671        @param app: next middleware application in the chain     
672        @type global_conf: dict       
673        @param global_conf: PasteDeploy global configuration dictionary
674        @type prefix: basestring
675        @param prefix: prefix for configuration items
676        @type app_conf: dict       
677        @param app_conf: PasteDeploy application specific configuration
678        dictionary
679        '''
680       
681        # Set logout URI parameter from AuthKit settings if not otherwise set
682        sessionHandlerPrefix = prefix + SessionHandlerMiddleware.PARAM_PREFIX       
683        app = SessionHandlerMiddleware(app, 
684                                       global_conf, 
685                                       prefix=sessionHandlerPrefix,
686                                       **app_conf)
687       
688        # Remove session handler middleware specific parameters
689        for k in app_conf.keys():
690            if k.startswith(sessionHandlerPrefix):
691                del app_conf[k]
692       
693        app = authkit.authenticate.middleware(app, app_conf)       
694       
695        MultiHandler.__init__(self, app)
696
697        # Redirection middleware is invoked based on a check method which
698        # catches HTTP 401 responses.
699        self.add_method(AuthnRedirectInitiatorMiddleware.MIDDLEWARE_ID, 
700                        AuthnRedirectInitiatorMiddleware.filter_app_factory, 
701                        global_conf,
702                        prefix=prefix,
703                        **app_conf)
704       
705        self.add_checker(AuthnRedirectInitiatorMiddleware.MIDDLEWARE_ID, 
706                         AuthnRedirectInitiatorMiddleware.checker)
Note: See TracBrowser for help on using the repository browser.