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

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

Working Genshi PEP result handler plugin

Line 
1"""Session handling middleware module
2
3Refactored authn module moving session specific code to here
4 
5NERC DataGrid Project
6"""
7__author__ = "P J Kershaw"
8__date__ = "05/01/10"
9__copyright__ = "(C) 2010 Science and Technology Facilities Council"
10__license__ = "BSD - see LICENSE file in top-level directory"
11__contact__ = "Philip.Kershaw@stfc.ac.uk"
12__revision__ = "$Id: $"
13import logging
14log = logging.getLogger(__name__)
15
16from ndg.security.server.wsgi import (NDGSecurityMiddlewareBase,
17                                      NDGSecurityMiddlewareError)
18
19
20class SessionMiddlewareBase(NDGSecurityMiddlewareBase):
21    """Base class for Authentication redirect middleware and Session Handler
22    middleware
23   
24    @type propertyDefaults: dict
25    @cvar propertyDefaults: valid configuration property keywords
26    """   
27    propertyDefaults = {
28        'sessionKey': 'beaker.session.ndg.security'
29    }
30
31    # Key names for PEP context information
32    PEPCTX_SESSION_KEYNAME = 'pepCtx'
33    PEPCTX_REQUEST_SESSION_KEYNAME = 'request'
34    PEPCTX_RESPONSE_SESSION_KEYNAME = 'response'
35    PEPCTX_TIMESTAMP_SESSION_KEYNAME = 'timestamp'
36   
37    _isAuthenticated = lambda self: \
38        SessionMiddlewareBase.USERNAME_SESSION_KEYNAME in \
39        self.environ.get(self.sessionKey, ())
40       
41    isAuthenticated = property(fget=_isAuthenticated,
42                               doc='boolean to indicate is user logged in')
43       
44
45class SessionHandlerMiddlewareError(NDGSecurityMiddlewareError):
46    """Base exception for SessionHandlerMiddleware"""
47           
48           
49class SessionHandlerMiddlewareConfigError(SessionHandlerMiddlewareError):
50    """Configuration errors from SessionHandlerMiddleware"""
51   
52   
53class OpenIdAXConfigError(SessionHandlerMiddlewareError):
54    """Error parsing OpenID Ax (Attribute Exchange) parameters"""
55   
56   
57class SessionHandlerMiddleware(SessionMiddlewareBase):
58    '''Middleware to:
59    - establish user session details following redirect from OpenID Relying
60    Party sign-in or SSL Client authentication
61    - end session redirecting back to referrer URI following call to a logout
62    URI as implemented in AuthKit
63    '''
64    AX_SESSION_KEYNAME = 'openid.ax'
65    SM_URI_SESSION_KEYNAME = 'sessionManagerURI'
66    ID_SESSION_KEYNAME = 'sessionId'
67    PEP_CTX_SESSION_KEYNAME = 'pepCtx'
68    CREDENTIAL_WALLET_SESSION_KEYNAME = 'credentialWallet'
69   
70    SESSION_KEYNAMES = (
71        SessionMiddlewareBase.USERNAME_SESSION_KEYNAME, 
72        SM_URI_SESSION_KEYNAME, 
73        ID_SESSION_KEYNAME, 
74        PEP_CTX_SESSION_KEYNAME, 
75        CREDENTIAL_WALLET_SESSION_KEYNAME
76    )
77   
78    AX_KEYNAME = 'ax'
79    SM_URI_AX_KEYNAME = 'value.sessionManagerURI.1'
80    SESSION_ID_AX_KEYNAME = 'value.sessionId.1'
81   
82    AUTHKIT_COOKIE_SIGNOUT_PARAMNAME = 'authkit.cookie.signoutpath'
83    SIGNOUT_PATH_PARAMNAME = 'signoutPath'
84    SESSION_KEY_PARAMNAME = 'sessionKey'
85    propertyDefaults = {
86        SIGNOUT_PATH_PARAMNAME: None,
87        SESSION_KEY_PARAMNAME: 'beaker.session.ndg.security'
88    }
89   
90    AUTH_TKT_SET_USER_ENVIRON_KEYNAME = 'paste.auth_tkt.set_user'
91   
92    PARAM_PREFIX = 'sessionHandler.'
93   
94    def __init__(self, app, global_conf, prefix=PARAM_PREFIX, **app_conf):
95        '''
96        @type app: callable following WSGI interface
97        @param app: next middleware application in the chain     
98        @type global_conf: dict       
99        @param global_conf: PasteDeploy global configuration dictionary
100        @type prefix: basestring
101        @param prefix: prefix for configuration items
102        @type app_conf: dict       
103        @param app_conf: PasteDeploy application specific configuration
104        dictionary
105        '''
106        signoutPathParamName = prefix + \
107                                SessionHandlerMiddleware.SIGNOUT_PATH_PARAMNAME
108       
109        if signoutPathParamName not in app_conf:
110            authKitSignOutPath = app_conf.get(
111                    SessionHandlerMiddleware.AUTHKIT_COOKIE_SIGNOUT_PARAMNAME)
112           
113            if authKitSignOutPath:
114                app_conf[signoutPathParamName] = authKitSignOutPath
115               
116                log.info('Set signoutPath=%s from "%s" setting', 
117                     authKitSignOutPath,
118                     SessionHandlerMiddleware.AUTHKIT_COOKIE_SIGNOUT_PARAMNAME)
119            else:
120                raise SessionHandlerMiddlewareConfigError(
121                                        '"signoutPath" parameter is not set')
122           
123        super(SessionHandlerMiddleware, self).__init__(app,
124                                                       global_conf,
125                                                       prefix=prefix, 
126                                                       **app_conf)
127       
128    @NDGSecurityMiddlewareBase.initCall
129    def __call__(self, environ, start_response):
130        """Manage setting of session from AuthKit following OpenID Relying
131        Party sign in and manage logout
132       
133        @type environ: dict
134        @param environ: WSGI environment variables dictionary
135        @type start_response: function
136        @param start_response: standard WSGI start response function
137        """
138        log.debug("SessionHandlerMiddleware.__call__ ...")
139       
140        session = environ.get(self.sessionKey)
141        if session is None:
142            raise SessionHandlerMiddlewareConfigError(
143                   'SessionHandlerMiddleware.__call__: No beaker session key '
144                   '"%s" found in environ' % self.sessionKey)
145       
146        if self.signoutPath and self.pathInfo == self.signoutPath:
147            log.debug("SessionHandlerMiddleware.__call__: caught sign out "
148                      "path [%s]", self.signoutPath)
149           
150            referrer = environ.get('HTTP_REFERER')
151            if referrer is not None:
152                def _start_response(status, header, exc_info=None):
153                    """Alter the header to send a redirect to the logout
154                    referrer address"""
155                    filteredHeader = [(field, val) for field, val in header
156                                      if field.lower() != 'location']       
157                    filteredHeader.extend([('Location', referrer)])
158                    return start_response(self.getStatusMessage(302), 
159                                          filteredHeader,
160                                          exc_info)
161                   
162            else:
163                log.error('No referrer set for redirect following logout')
164                _start_response = start_response
165               
166            # Clear user details from beaker session
167            for keyName in self.__class__.SESSION_KEYNAMES:
168                session.pop(keyName, None)
169            session.save()
170        else:
171            log.debug("SessionHandlerMiddleware.__call__: checking for "
172                      "REMOTE_* environment variable settings set by OpenID "
173                      "Relying Party signin...")
174           
175            if SessionHandlerMiddleware.USERNAME_SESSION_KEYNAME not in session\
176               and SessionHandlerMiddleware.USERNAME_ENVIRON_KEYNAME in environ:
177                log.debug("SessionHandlerMiddleware.__call__: updating session "
178                          "username=%s", environ[
179                            SessionHandlerMiddleware.USERNAME_ENVIRON_KEYNAME])
180               
181                session[SessionHandlerMiddleware.USERNAME_SESSION_KEYNAME
182                        ] = environ[
183                            SessionHandlerMiddleware.USERNAME_ENVIRON_KEYNAME]
184                session.save()
185               
186            remoteUserData = environ.get(
187                        SessionHandlerMiddleware.USERDATA_ENVIRON_KEYNAME, '')   
188            if remoteUserData:
189                log.debug("SessionHandlerMiddleware.__call__: found "
190                          "REMOTE_USER_DATA=%s, set from OpenID Relying Party "
191                          "signin", 
192                          environ[
193                              SessionHandlerMiddleware.USERDATA_ENVIRON_KEYNAME
194                          ])
195               
196                # eval is safe here because AuthKit cookie is signed and
197                # AuthKit middleware checks for tampering
198                if (SessionHandlerMiddleware.SM_URI_SESSION_KEYNAME not in 
199                    session or 
200                    SessionHandlerMiddleware.ID_SESSION_KEYNAME not in session):
201                   
202                    axData = eval(remoteUserData)
203                    if (isinstance(axData, dict) and 
204                        SessionHandlerMiddleware.AX_KEYNAME in axData):
205                       
206                        ax = axData[SessionHandlerMiddleware.AX_KEYNAME]
207                       
208                        # Save attributes keyed by attribute name
209                        session[
210                            SessionHandlerMiddleware.AX_SESSION_KEYNAME
211                        ] = SessionHandlerMiddleware._parseOpenIdAX(ax)
212                       
213                        log.debug("SessionHandlerMiddleware.__call__: updated "
214                                  "session with OpenID AX values: %r" % 
215                                  session[
216                                    SessionHandlerMiddleware.AX_SESSION_KEYNAME
217                                  ])
218                           
219                        # Save Session Manager specific attributes
220                        sessionManagerURI = ax.get(
221                                SessionHandlerMiddleware.SM_URI_AX_KEYNAME)
222                           
223                        session[SessionHandlerMiddleware.SM_URI_SESSION_KEYNAME
224                                ] = sessionManagerURI
225   
226                        sessionId = ax.get(
227                                SessionHandlerMiddleware.SESSION_ID_AX_KEYNAME)
228                        session[SessionHandlerMiddleware.ID_SESSION_KEYNAME
229                                ] = sessionId
230                               
231                        session.save()
232                       
233                        log.debug("SessionHandlerMiddleware.__call__: updated "
234                                  "session "
235                                  "with sessionManagerURI=%s and "
236                                  "sessionId=%s", 
237                                  sessionManagerURI, 
238                                  sessionId)
239                   
240                # Reset cookie removing user data
241                setUser = environ[
242                    SessionHandlerMiddleware.AUTH_TKT_SET_USER_ENVIRON_KEYNAME]
243                setUser(
244                    session[SessionHandlerMiddleware.USERNAME_SESSION_KEYNAME])
245            else:
246                log.debug("SessionHandlerMiddleware.__call__: REMOTE_USER_DATA "
247                          "is not set")
248
249            _start_response = start_response
250           
251        return self._app(environ, _start_response)
252   
253    @staticmethod                   
254    def _parseOpenIdAX(ax):
255        """Return a dictionary of attribute exchange attributes parsed from the
256        OpenID Provider response set in the REMOTE_USER_DATA AuthKit environ
257        key
258       
259        @param ax: dictionary of AX parameters - format of keys is e.g.
260        count.paramName, value.paramName.<n>, type.paramName
261        @type ax: dict
262        @return: dictionary of parameters keyed by parameter with values for
263        each parameter a tuple of count.paramName values
264        @rtype: dict
265        """
266       
267        # Copy Attributes into session
268        outputKeys = [k.replace('type.', '') for k in ax.keys()
269                      if k.startswith('type.')]
270       
271        output = {}
272        for outputKey in outputKeys:
273            axCountKeyName = 'count.' + outputKey
274            axCount = int(ax[axCountKeyName])
275           
276            axValueKeyPrefix = 'value.%s.' % outputKey
277            output[outputKey] = tuple([v for k, v in ax.items() 
278                                       if k.startswith(axValueKeyPrefix)])
279           
280            nVals = len(output[outputKey])
281            if nVals != axCount:
282                raise OpenIdAXConfigError('Got %d parameters for AX attribute '
283                                          '"%s"; but "%s" AX key is set to %d'
284                                          % (nVals,
285                                             axCountKeyName,
286                                             axCountKeyName,
287                                             axCount))
288                                             
289        return output
Note: See TracBrowser for help on using the repository browser.