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

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

Completed OpenID Provider interface content to enable user to see and select which AX items to return to the Relying Party. OpenIDProviderMiddleware.do_allow method needs fixing to affect selections made by the user allow/deny return of individual attributes to the RP.

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: 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: found REMOTE_USER_DATA="
551                          "%s, set from OpenID Relying Party signin",
552                          environ[
553                          SessionHandlerMiddleware.USERDATA_ENVIRON_KEYNAME])
554               
555                # eval is safe here because AuthKit cookie is signed and
556                # AuthKit middleware checks for tampering
557                if (SessionHandlerMiddleware.SM_URI_SESSION_KEYNAME not in 
558                    session or 
559                    SessionHandlerMiddleware.ID_SESSION_KEYNAME not in session):
560                   
561                    axData = eval(remoteUserData)
562                    if (isinstance(axData, dict) and 
563                        SessionHandlerMiddleware.AX_KEYNAME in axData):
564                       
565                        ax = axData[SessionHandlerMiddleware.AX_KEYNAME]
566                       
567                        # Save attributes keyed by attribute name
568                        session[
569                            SessionHandlerMiddleware.AX_SESSION_KEYNAME
570                        ] = SessionHandlerMiddleware._parseOpenIdAX(ax)
571                       
572                        log.debug("SessionHandlerMiddleware: updated session "
573                                  "with OpenID AX values: %r" % 
574                                  session[
575                                    SessionHandlerMiddleware.AX_SESSION_KEYNAME
576                                  ])
577                           
578                        # Save Session Manager specific attributes
579                        sessionManagerURI = ax.get(
580                                SessionHandlerMiddleware.SM_URI_AX_KEYNAME)
581                           
582                        session[SessionHandlerMiddleware.SM_URI_SESSION_KEYNAME
583                                ] = sessionManagerURI
584   
585                        sessionId = ax.get(
586                                SessionHandlerMiddleware.SESSION_ID_AX_KEYNAME)
587                        session[SessionHandlerMiddleware.ID_SESSION_KEYNAME
588                                ] = sessionId
589                               
590                        session.save()
591                       
592                        log.debug("SessionHandlerMiddleware: updated session "
593                                  "with sessionManagerURI=%s and "
594                                  "sessionId=%s", 
595                                  sessionManagerURI, 
596                                  sessionId)
597                   
598                # Reset cookie removing user data
599                setUser = environ[
600                    SessionHandlerMiddleware.AUTH_TKT_SET_USER_ENVIRON_KEYNAME]
601                setUser(
602                    session[SessionHandlerMiddleware.USERNAME_SESSION_KEYNAME])
603
604            _start_response = start_response
605           
606        return self._app(environ, _start_response)
607   
608    @staticmethod                   
609    def _parseOpenIdAX(ax):
610        """Return a dictionary of attribute exchange attributes parsed from the
611        OpenID Provider response set in the REMOTE_USER_DATA AuthKit environ
612        key
613       
614        @param ax: dictionary of AX parameters - format of keys is e.g.
615        count.paramName, value.paramName.<n>, type.paramName
616        @type ax: dict
617        @return: dictionary of parameters keyed by parameter with values for
618        each parameter a tuple of count.paramName values
619        @rtype: dict
620        """
621       
622        # Copy Attributes into session
623        outputKeys = [k.replace('type.', '') for k in ax.keys()
624                      if k.startswith('type.')]
625       
626        output = {}
627        for outputKey in outputKeys:
628            axCountKeyName = 'count.' + outputKey
629            axCount = int(ax[axCountKeyName])
630           
631            axValueKeyPrefix = 'value.%s.' % outputKey
632            output[outputKey] = tuple([v for k, v in ax.items() 
633                                       if k.startswith(axValueKeyPrefix)])
634           
635            nVals = len(output[outputKey])
636            if nVals != axCount:
637                raise OpenIdAXConfigError('Got %d parameters for AX attribute '
638                                          '"%s"; but "%s" AX key is set to %d'
639                                          % (nVals,
640                                             axCountKeyName,
641                                             axCountKeyName,
642                                             axCount))
643                                             
644        return output
645
646
647from authkit.authenticate.multi import MultiHandler
648
649class AuthenticationMiddlewareConfigError(NDGSecurityMiddlewareConfigError):
650    '''Authentication Middleware Configuration error'''
651
652
653class AuthenticationMiddleware(MultiHandler, NDGSecurityMiddlewareBase):
654    '''Top-level class encapsulates session and authentication handlers
655    in this module
656   
657    Handler to intercept 401 Unauthorized HTTP responses and redirect to an
658    authentication URI.  This class also implements a redirect handler to
659    redirect back to the referrer if logout is invoked.
660    '''
661
662    def __init__(self, app, global_conf, prefix='', **app_conf):
663        '''
664        @type app: callable following WSGI interface
665        @param app: next middleware application in the chain     
666        @type global_conf: dict       
667        @param global_conf: PasteDeploy global configuration dictionary
668        @type prefix: basestring
669        @param prefix: prefix for configuration items
670        @type app_conf: dict       
671        @param app_conf: PasteDeploy application specific configuration
672        dictionary
673        '''
674       
675        # Set logout URI parameter from AuthKit settings if not otherwise set
676        sessionHandlerPrefix = prefix + SessionHandlerMiddleware.PARAM_PREFIX       
677        app = SessionHandlerMiddleware(app, 
678                                       global_conf, 
679                                       prefix=sessionHandlerPrefix,
680                                       **app_conf)
681       
682        # Remove session handler middleware specific parameters
683        for k in app_conf.keys():
684            if k.startswith(sessionHandlerPrefix):
685                del app_conf[k]
686       
687        app = authkit.authenticate.middleware(app, app_conf)       
688       
689        MultiHandler.__init__(self, app)
690
691        # Redirection middleware is invoked based on a check method which
692        # catches HTTP 401 responses.
693        self.add_method(AuthnRedirectInitiatorMiddleware.MIDDLEWARE_ID, 
694                        AuthnRedirectInitiatorMiddleware.filter_app_factory, 
695                        global_conf,
696                        prefix=prefix,
697                        **app_conf)
698       
699        self.add_checker(AuthnRedirectInitiatorMiddleware.MIDDLEWARE_ID, 
700                         AuthnRedirectInitiatorMiddleware.checker)
Note: See TracBrowser for help on using the repository browser.