source: TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/openid/relyingparty/__init__.py @ 4907

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/openid/relyingparty/__init__.py@4907
Revision 4907, 9.4 KB checked in by pjkersha, 11 years ago (diff)
  • Single Sign On Service logout controller: fixed WSGI Session Manager client instantiation - added Session Manager environ key arg
  • Completed OpenIDRelyingPartyMiddleware - wraps Authkit OpenID RP code adding a custom signin template and logout capability.
Line 
1"""NDG Security OpenID Relying Party Middleware
2
3Wrapper to AuthKit OpenID Middleware
4
5NERC DataGrid Project
6"""
7__author__ = "P J Kershaw"
8__date__ = "20/01/2009"
9__copyright__ = "(C) 2009 Science and Technology Facilities Council"
10__license__ = "BSD - see top-level directory for LICENSE file"
11__contact__ = "Philip.Kershaw@stfc.ac.uk"
12__revision__ = "$Id$"
13import logging
14log = logging.getLogger(__name__)
15
16import httplib # to get official status code messages
17
18import authkit.authenticate
19import beaker.middleware
20
21from ndg.security.server.wsgi import NDGSecurityMiddlewareBase
22from ndg.security.common.utils.classfactory import instantiateClass
23
24class OpenIDRelyingPartyMiddlewareError(Exception):
25    """OpenID Relying Party WSGI Middleware Error"""
26
27class OpenIDRelyingPartyConfigError(OpenIDRelyingPartyMiddlewareError):
28    """OpenID Relying Party Configuration Error"""
29 
30class OpenIDRelyingPartyMiddleware(NDGSecurityMiddlewareBase):
31    '''Implementation of OpenID Relying Party based on AuthKit'''
32    propertyDefaults = {
33        'signinInterfaceMiddlewareClass': None,
34        'baseURL': '',
35        'sessionKey': 'beaker.session',
36        'reservedPaths': []
37    }
38    propertyDefaults.update(NDGSecurityMiddlewareBase.propertyDefaults)
39   
40    def __init__(self, app, global_conf, prefix='openid.relyingparty.', 
41                 **app_conf):
42        """Add AuthKit and Beaker middleware dependencies to WSGI stack
43       
44        @type app: callable following WSGI interface signature
45        @param app: next middleware application in the chain     
46        @type global_conf: dict       
47        @param global_conf: PasteDeploy application global configuration -
48        must follow format of propertyDefaults class variable
49        @type prefix: basestring
50        @param prefix: prefix for OpenID Relying Party configuration items
51        @type app_conf: dict
52        @param app_conf: application specific configuration - must follow
53        format of propertyDefaults class variable"""   
54
55           
56        # Check for sign in template settings
57        if prefix+'signinInterfaceMiddlewareClass' in app_conf:
58            if 'authkit.openid.template.obj' in app_conf or \
59               'authkit.openid.template.string' in app_conf or \
60               'authkit.openid.template.file' in app_conf:
61                log.warning("OpenID Relying Party "
62                            "'signinInterfaceMiddlewareClass' "
63                            "setting overrides 'authkit.openid.template.*' "
64                            "AuthKit settings")
65               
66            moduleName, className = \
67                app_conf[prefix+'signinInterfaceMiddlewareClass'].rsplit('.',1)
68           
69            signinInterfacePrefix = prefix+'signinInterface.'
70            classProperties = {'prefix': signinInterfacePrefix}
71            classProperties.update(app_conf)
72            app = instantiateClass(moduleName, 
73                                   className, 
74                                   objectType=SigninInterface, 
75                                   classArgs=(app, global_conf),
76                                   classProperties=classProperties)           
77           
78            # Delete sign in interface middleware settings
79            for conf in app_conf, global_conf or {}:
80                for k in conf.keys():
81                    if k.startswith(signinInterfacePrefix):
82                        del conf[k]
83       
84            app_conf['authkit.openid.template.string'] = app.makeTemplate()
85               
86        self.signoutPath = app_conf.get('authkit.cookie.signoutpath')
87
88        authKitApp = authkit.authenticate.middleware(app, app_conf)
89       
90        super(OpenIDRelyingPartyMiddleware, self).__init__(authKitApp, 
91                                                           global_conf, 
92                                                           prefix=prefix, 
93                                                           **app_conf)
94
95       
96    @NDGSecurityMiddlewareBase.initCall     
97    def __call__(self, environ, start_response):
98        '''Alter start_response to override the status code and force to 401.
99        This will non-browser based client code to bypass the OpenID interface
100       
101        @type environ: dict
102        @param environ: WSGI environment variables dictionary
103        @type start_response: builtin_function_or_method
104        @param start_response: standard WSGI start response function
105        '''
106        session = environ[self.sessionKey]
107        if self.signoutPath is not None and self.pathInfo == self.signoutPath:
108            # TODO: Redirect to referrer ...
109            referer = session.get(
110                        'ndg.security.server.wsgi.openid.relyingparty.referer')
111            if referer is not None:
112                def setRedirectResponse(status, header, exc_info=None):
113                    header.extend([('Location', referer)])
114                    return start_response('302 %s' % httplib.responses[302], 
115                                          header,
116                                          exc_info)
117                   
118                return self._app(environ, setRedirectResponse)
119            else:
120                log.debug('No referer set for redirect following logout')
121               
122#        if self.pathInfo not in self.reservedPaths:
123        if 'HTTP_REFERER' in environ:
124            session['ndg.security.server.wsgi.openid.relyingparty.referer'] = \
125                environ['HTTP_REFERER']
126            session.save()
127           
128        def set401UnauthorizedReponse(status, header, exc_info=None):
129            '''Make OpenID Relying Party OpenID prompt page return a 401
130            status to signal to non-browser based clients that authentication
131            is required.  Requests are filtered on content type so that
132            static content such as graphics and style sheets associated with
133            the page are let through unaltered
134           
135            @type status: str
136            @param status: HTTP status code and status message
137            @type header: list
138            @param header: list of field, value tuple HTTP header content
139            @type exc_info: Exception
140            @param exc_info: exception info
141            '''
142            _status = status
143            for name, val in header:
144                if name.lower() == 'content-type' and \
145                   val.startswith('text/html'):
146                    _status = "%d %s" % (401, httplib.responses[401])
147                    break
148               
149            return start_response(_status, header, exc_info)
150
151        return self._app(environ, set401UnauthorizedReponse)
152   
153    def _redirect(self, url):
154        """Do a HTTP 302 redirect
155       
156        @type url: basestring
157        @param url: URL to redirect to
158        @rtype: list
159        @return: empty HTML body
160        """
161        self.start_response('302 %s' % httplib.responses[302], 
162                            [('Content-type', 'text/html'),
163                             ('Location', url)])
164        return []
165
166    def _setReservedPaths(self, paths):
167        if isinstance(paths, basestring):
168            self._reservedPaths = [path.strip() for path in paths.split(',')]
169        elif isinstance(paths, (tuple, list)):
170            self._reservedPaths = paths
171        else:
172            raise AttributeError("Reserved paths must be a string or list or "
173                                 "tuple")
174    def _getReservedPaths(self):
175        return self._reservedPaths
176   
177    reservedPaths = property(fget=_getReservedPaths,
178                             fset=_setReservedPaths,
179                             doc="Set paths that logout redirect must avoid "
180                                 "e.g. AuthKit OpenID processing")
181   
182class SigninInterfaceError(Exception):
183    """Base class for SigninInterface exceptions
184   
185    A standard message is raised set by the msg class variable but the actual
186    exception details are logged to the error log.  The use of a standard
187    message enables callers to use its content for user error messages.
188   
189    @type msg: basestring
190    @cvar msg: standard message to be raised for this exception"""
191    userMsg = ("An internal error occurred with the page layout,  Please "
192               "contact your system administrator")
193    errorMsg = "SigninInterface error"
194   
195    def __init__(self, *arg, **kw):
196        if len(arg) > 0:
197            msg = arg[0]
198        else:
199            msg = self.__class__.errorMsg
200           
201        log.error(msg)
202        Exception.__init__(self, msg, **kw)
203       
204class SigninInterfaceInitError(SigninInterfaceError):
205    """Error with initialisation of SigninInterface.  Raise from __init__"""
206    errorMsg = "SigninInterface initialisation error"
207   
208class SigninInterfaceConfigError(SigninInterfaceError):
209    """Error with configuration settings.  Raise from __init__"""
210    errorMsg = "SigninInterface configuration error"   
211
212class SigninInterface(NDGSecurityMiddlewareBase):
213    """Base class for sign in rendering.  This is implemented as WSGI
214    middleware to enable additional middleware to be added into the call
215    stack e.g. StaticFileParser to enable rendering of graphics and other
216    static content in the Sign In page"""
217   
218    def getTemplateFunc(self):
219        """Return template function for AuthKit to render OpenID Relying
220        Party Sign in page"""
221        raise NotImplementedError()
222   
223    def __call__(self, environ, start_response):
224        return self._app(self, environ, start_response)
Note: See TracBrowser for help on using the repository browser.