source: TI12-security/trunk/python/ndg_security_server/ndg/security/server/wsgi/openid/provider/__init__.py @ 6202

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

Adding information about requested attributes for the decide page interface of the OpenID Provider.

Line 
1"""NDG Security OpenID Provider Middleware
2
3Compliments AuthKit OpenID Middleware used for OpenID *Relying Party*
4
5NERC DataGrid Project
6"""
7__author__ = "P J Kershaw"
8__date__ = "01/08/08"
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 httplib
14import sys
15import os
16import traceback
17import logging
18log = logging.getLogger(__name__)
19_debugLevel = log.getEffectiveLevel() <= logging.DEBUG
20
21import re
22from string import Template
23
24import paste.request
25from paste.util.import_string import eval_import
26from openid.extensions import sreg, ax
27from openid.server import server
28from openid.store.filestore import FileOpenIDStore
29from openid.consumer import discover
30
31from ndg.security.common.utils.classfactory import instantiateClass
32from ndg.security.server.wsgi import NDGSecurityMiddlewareBase 
33
34
35class IdentityMapping(object):
36    """Utility class to map between user identifiers and OpenID URIs
37    The user identifier is the user unique component of an OpenID URI
38    """
39    USER_IDENTIFIER_NAME = 'userIdentifier'
40   
41    # Template substitution may or may not contain braces
42    USER_IDENTIFIER_RE = re.compile('\$\{?%s\}?' % USER_IDENTIFIER_NAME)
43   
44    @classmethod
45    def userIdentifier2IdentityURI(cls, identityUriTmpl, userIdentifier):
46        """Convert an OpenID user identifier into an identity URI given a
47        template e.g.
48       
49        https://${userIdentifier}.openid.ac.uk + pjk => https://pjk.openid.ac.uk
50        """
51        mapping = {cls.USER_IDENTIFIER_NAME: userIdentifier}
52        tmpl = Template(identityUriTmpl)
53        return tmpl.substitute(mapping)
54       
55    @classmethod
56    def identityUri2UserIdentifier(cls, identityUriTmpl, identityUri):
57        """Parse an OpenID user identifier from an identity URI given a
58        template e.g.
59       
60        https://pjk.openid.ac.uk + https://${userIdentifier}.openid.ac.uk => pjk
61        """
62        # Subtract start and end URI snippets from the template
63        try:
64            uriPrefix, uriSuffix = cls.USER_IDENTIFIER_RE.split(identityUriTmpl)
65        except ValueError:
66            raise OpenIDProviderConfigError('Error parsing identity URI %r '
67                                            'using template %r' %
68                                            (identityUri, identityUriTmpl))
69           
70        if not identityUri.startswith(uriPrefix): 
71            raise OpenIDProviderConfigError('Identity URI %r doesn\'t match '
72                                            'the template prefix %r' %
73                                            (identityUri, uriPrefix))
74           
75        suffixIndex = identityUri.rfind(uriSuffix)   
76        if suffixIndex == -1: 
77            raise OpenIDProviderConfigError('Identity URI %r doesn\'t match '
78                                            'the template suffix %r' %
79                                            (identityUri, uriSuffix))
80       
81        userIdentifier = identityUri[:suffixIndex].replace(uriPrefix, '', 1)
82        return userIdentifier
83 
84# Place here to avoid circular import error with IdentityMapping class     
85from ndg.security.server.wsgi.openid.provider.authninterface import \
86    AbstractAuthNInterface, AuthNInterfaceError
87from ndg.security.server.wsgi.openid.provider.axinterface import AXInterface,\
88    MissingRequiredAttrs, AXInterfaceReloginRequired
89
90
91# Aliases to AXInterface exception types   
92class OpenIDProviderMissingRequiredAXAttrs(MissingRequiredAttrs): 
93    """Raise if a Relying Party *requires* one or more attributes via
94    the AX interface but this OpenID Provider cannot return them.  This
95    doesn't apply to attributes that are optional"""
96
97class OpenIDProviderReloginRequired(AXInterfaceReloginRequired):
98    pass
99
100# end aliases to AXInterface exception types
101
102
103class OpenIDProviderMiddlewareError(Exception):
104    """OpenID Provider WSGI Middleware Error"""
105
106class OpenIDProviderConfigError(OpenIDProviderMiddlewareError):
107    """OpenID Provider Configuration Error"""
108
109
110class OpenIDProviderMissingAXResponseHandler(OpenIDProviderMiddlewareError): 
111    """Raise if a Relying Party *requires* one or more attributes via
112    the AX interface but no AX Response handler has been set"""
113 
114 
115class OpenIDProviderMiddleware(NDGSecurityMiddlewareBase):
116    """WSGI Middleware to implement an OpenID Provider
117   
118    @cvar defOpt: app_conf options / keywords to __init__ and their default
119    values.  Input keywords must match these
120    @type defOpt: dict
121   
122    @cvar defPaths: subset of defOpt.  These are keyword items corresponding
123    to the URL paths to be set for the individual OpenID Provider functions
124    @type: defPaths: dict
125   
126    @cvar formRespWrapperTmpl: If the response to the Relying Party is too long
127    it's rendered as form with the POST method instead of query arguments in a
128    GET 302 redirect.  Wrap the form in this document to make the form submit
129    automatically without user intervention.  See _displayResponse method
130    below...
131    @type formRespWrapperTmpl: basestring"""
132   
133    formRespWrapperTmpl = """<html>
134    <head>
135        <script type="text/javascript">
136            function doRedirect()
137            {
138                document.forms[0].submit();
139            }
140        </script>
141    </head>
142    <body onLoad="doRedirect()">
143        %s
144    </body>
145</html>"""
146
147    defOpt = dict(
148        path_openidserver='/openidserver',
149        path_login='/login',
150        path_loginsubmit='/loginsubmit',
151        path_id='/id/${userIdentifier}',
152        path_yadis='/yadis/${userIdentifier}',
153        path_serveryadis='/serveryadis',
154        path_allow='/allow',
155        path_decide='/decide',
156        path_mainpage='/',
157        session_middleware='beaker.session',
158        base_url='',
159        consumer_store_dirpath='./',
160        charset=None,
161        trace=False,
162        renderingClass=None,
163        sregResponseHandler=None,
164        axResponseHandler=None,
165        authNInterface=AbstractAuthNInterface)
166   
167    defPaths = dict([(k, v) for k, v in defOpt.items() 
168                     if k.startswith('path_')])
169   
170    userIdentifierPat = '([^/]*)'
171   
172    USERNAME_SESSION_KEYNAME = 'username'
173    IDENTITY_URI_SESSION_KEYNAME = 'identityURI'
174    APPROVED_FLAG_SESSION_KEYNAME = 'approved'
175    LAST_CHECKID_REQUEST_SESSION_KEYNAME = 'lastCheckIDRequest'
176    PARAM_PREFIX = 'openid.provider.'
177   
178    IDENTITY_URI_TMPL_PARAMNAME = 'identityUriTemplate'
179   
180    # Approve and reject submit HTML input types for the Relying Party Approval
181    # page
182    APPROVE_RP_SUBMIT = "ApproveRelyingParty"
183    REJECT_RP_SUBMIT = "RejectRelyingParty"
184   
185    def __init__(self, app, app_conf=None, prefix=PARAM_PREFIX, **kw):
186        '''
187        @type app: callable following WSGI interface
188        @param app: next middleware application in the chain     
189        @type app_conf: dict       
190        @param app_conf: PasteDeploy application configuration dictionary
191        @type prefix: basestring
192        @param prefix: prefix for OpenID Provider configuration items
193        @type kw: dict
194        @param kw: keyword dictionary - must follow format of defOpt
195        class variable   
196        '''
197        self._app = app
198        self._environ = {}
199        self._start_response = None
200        self._pathInfo = None
201        self._path = None
202        self.mountPath = '/'
203
204        opt = OpenIDProviderMiddleware.defOpt.copy()
205        if app_conf is not None:
206            # Update from application config dictionary - filter from using
207            # prefix
208            OpenIDProviderMiddleware._filterOpts(opt, app_conf, prefix=prefix)
209                       
210        # Similarly, filter keyword input                 
211        OpenIDProviderMiddleware._filterOpts(opt, kw, prefix=prefix)
212       
213        # Update options from keywords - matching app_conf ones will be
214        # overwritten
215        opt.update(kw)
216       
217        # Convert from string type where required   
218        opt['charset'] = opt.get('charset', '')
219        opt['trace'] = opt.get('trace', 'false').lower() == 'true'
220         
221        renderingClassVal = opt.get('renderingClass', None)     
222        if renderingClassVal:
223            opt['renderingClass'] = eval_import(renderingClassVal)
224       
225        sregResponseHandlerVal = opt.get('sregResponseHandler', None) 
226        if sregResponseHandlerVal:
227            opt['sregResponseHandler'] = eval_import(sregResponseHandlerVal) 
228        else:
229            opt['sregResponseHandler'] = None
230
231        axResponseHandlerVal = opt.get('axResponseHandler', None) 
232        if axResponseHandlerVal:
233            opt['axResponseHandler'] = eval_import(axResponseHandlerVal)
234        else:
235            opt['axResponseHandler'] = None
236
237        # Authentication interface to OpenID Provider - interface to for
238        # example a user database or other means of authentication
239        authNInterfaceName = opt.get('authNInterface')
240        if authNInterfaceName:
241            authNInterfaceClass = eval_import(authNInterfaceName)
242            if not issubclass(authNInterfaceClass, AbstractAuthNInterface):
243                raise OpenIDProviderMiddlewareError("Authentication interface "
244                                                    "class %r is not a %r "
245                                                    "derived type" % 
246                                                    (authNInterfaceClass,
247                                                     AbstractAuthNInterface))
248        else:
249            authNInterfaceClass = AbstractAuthNInterface
250       
251        # Extract Authentication interface specific properties
252        authNInterfaceProperties = dict([(k.replace('authN_', ''), v) 
253                                         for k, v in opt.items() 
254                                         if k.startswith('authN_')]) 
255         
256        try:
257            self._authN = authNInterfaceClass(**authNInterfaceProperties)
258        except Exception, e:
259            log.error("Error instantiating authentication interface: %s" % e)
260            raise
261
262        # Paths relative to base URL - Nb. remove trailing '/'
263        self.paths = dict([(k, opt[k].rstrip('/'))
264                           for k in OpenIDProviderMiddleware.defPaths])
265                       
266        if not opt['base_url']:
267            raise TypeError("base_url is not set")
268       
269        self.base_url = opt['base_url']
270
271
272        self.identityUriTmpl = opt.get(
273                        OpenIDProviderMiddleware.IDENTITY_URI_TMPL_PARAMNAME)
274        if not self.identityUriTmpl:
275            raise TypeError("%s is not set" % 
276                        OpenIDProviderMiddleware.IDENTITY_URI_TMPL_PARAMNAME)
277       
278        # Full Paths
279        self.urls = dict([(k.replace('path_', 'url_'), self.base_url + v)
280                          for k, v in self.paths.items()])
281
282        self.method = dict([(v, k.replace('path_', 'do_'))
283                            for k, v in self.paths.items()])
284
285        self.session_middleware = opt['session_middleware']
286
287        if not opt['charset']:
288            self.charset = ''
289        else:
290            self.charset = '; charset=' + charset
291       
292        # If True and debug log level is set display content of response
293        self._trace = opt['trace']
294
295        log.debug("opt=%r", opt)       
296       
297        # Pages can be customised by setting external rendering interface
298        # class
299        renderingClass = opt.get('renderingClass', None) or RenderingInterface         
300        if not issubclass(renderingClass, RenderingInterface):
301            raise OpenIDProviderMiddlewareError("Rendering interface "
302                                                "class %r is not a %r "
303                                                "derived type" % \
304                                                (renderingClass,
305                                                 RenderingInterface))
306       
307        # Extract rendering interface specific properties
308        renderingProperties = dict([(k.replace('rendering_', ''), v) 
309                                         for k, v in opt.items() 
310                                         if k.startswith('rendering_')])   
311
312        try:
313            self._render = renderingClass(self._authN,
314                                          self.base_url,
315                                          self.urls,
316                                          **renderingProperties)
317        except Exception, e:
318            log.error("Error instantiating rendering interface: %s" % e)
319            raise
320                   
321        # Callable for setting of Simple Registration attributes in HTTP header
322        # of response to Relying Party
323        self.sregResponseHandler = opt.get('sregResponseHandler', None)
324        if self.sregResponseHandler and not callable(self.sregResponseHandler):
325            raise OpenIDProviderMiddlewareError("Expecting callable for "
326                                                "sregResponseHandler keyword, "
327                                                "got %r" % 
328                                                self.sregResponseHandler)
329           
330        # Class to handle OpenID Attribute Exchange (AX) requests from
331        # the Relying Party
332        axResponseClassName = opt.pop('axResponse_class', None)
333        if axResponseClassName is None:
334            # No AX Response handler set
335            self.axResponse = None
336        else:
337            axResponseModuleFilePath = opt.pop('axResponse_moduleFilePath',
338                                               None)
339            axResponseProperties = dict(
340                [(k.replace('axResponse_', ''), v) 
341                 for k,v in opt.items() if k.startswith('axResponse_')])
342               
343            try:
344                self.axResponse = instantiateClass(
345                                    axResponseClassName, 
346                                    None, 
347                                    moduleFilePath=axResponseModuleFilePath,
348                                    objectType=AXInterface, 
349                                    classProperties=axResponseProperties)
350            except Exception, e:
351                log.error("Error instantiating AX interface: %s" % e)
352                raise
353       
354        # Instantiate OpenID consumer store and OpenID consumer.  If you
355        # were connecting to a database, you would create the database
356        # connection and instantiate an appropriate store here.
357        store = FileOpenIDStore(
358                            os.path.expandvars(opt['consumer_store_dirpath']))
359        self.oidserver = server.Server(store, self.urls['url_openidserver'])
360       
361    @classmethod
362    def _filterOpts(cls, opt, newOpt, prefix=''):
363        '''Convenience utility to filter input options set in __init__ via
364        app_conf or keywords
365       
366        Nb. exclusions for authN and rendering interface properties.
367       
368        @type opt: dict
369        @param opt: existing options set.  These will be updated by this
370        method based on the content of newOpt
371        @type newOpt: dict
372        @param newOpt: new options to update opt with
373        @type prefix: basestring
374        @param prefix: if set, remove the given prefix from the input options
375        @raise KeyError: if an option is set that is not in the classes
376        defOpt class variable
377        '''
378        def _isBadOptName(optName):
379            # Allow for authN.* and rendering.* properties used by the
380            # Authentication and Rendering interfaces respectively
381            return optName not in cls.defOpt and \
382               not optName.startswith('authN_') and \
383               not optName.startswith('rendering_') and \
384               not optName.startswith('axResponse_')
385               
386        badOptNames = [] 
387        for optName, optVal in newOpt.items():
388            if prefix:
389                if optName.startswith(prefix):
390                    optName = optName.replace(prefix, '')               
391                    filtOptName = '_'.join(optName.split('.'))
392                                           
393                    # Skip assignment for bad option names and record them in
394                    # an error list instead
395                    if _isBadOptName(filtOptName):
396                        badOptNames += [optName]                   
397                    else:
398                        opt[filtOptName] = optVal
399            else:
400                filtOptName = '_'.join(optName.split('.'))
401
402                # Record any bad option names
403                if _isBadOptName(filtOptName):
404                    badOptNames += [optName]                   
405                else:
406                    opt[filtOptName] = optVal
407               
408        if len(badOptNames) > 0:
409            raise TypeError("Invalid input option(s) set: %s" % 
410                            (", ".join(badOptNames)))
411                       
412    def _matchIdentityURI(self):
413        idPaths = (self.paths['path_id'], self.paths['path_yadis'])
414        idPathMatches = [Template(path).substitute(
415                    userIdentifier=OpenIDProviderMiddleware.userIdentifierPat)
416                    for path in idPaths]
417       
418        for idPathMatch, idPath in zip(idPathMatches, idPaths):
419            if re.search(idPathMatch, self.path):
420                return idPath
421           
422        return None
423
424    def _isAnIdentityURI(self):
425        """Check input URI is an identity URI.  Use to determine whether a
426        RP discovery request has been made based on a provided user OpenID.
427        i.e. do_id / do_yadis should be invoked - see __call__ method for
428        details.  It takes the identity portion of the URI from the config
429        path_id / path_yadis settings - which ever matches e.g.
430       
431        <http/https>://<domainname>/<path>/${userIdentifier}
432       
433        e.g.
434       
435        https://badc.rl.ac.uk/openid/johnsmith
436       
437        This method should be overridden in a derived class if some
438        other means of representing identity URIs is required. e.g.
439       
440        https://johnsmith.badc.rl.ac.uk
441       
442        but note also see _matchIdentityURI method
443       
444        @rtype: bool
445        @return: return True if the given URI is an identity URI, otherwise
446        False
447        """       
448        return self._matchIdentityURI() is not None
449
450    def _parseIdentityURI(self):
451        '''Split path into identity and path fragment components
452       
453        @rtype: list
454        @return: 2 element list containing the Identity URI path fragment and
455        user identifier respectively.
456        '''
457        return OpenIDProviderMiddleware.parseIdentityURI(self.path)   
458
459    @classmethod
460    def parseIdentityURI(cls, uri):
461        '''Split uri into identity and uri fragment components
462       
463        FIXME: this method assumes a fixed identity URI scheme and should be
464        refactored to use a template based parsing
465       
466        @type uri: basestring
467        @param uri: identity URI to be parsed
468        @rtype: list
469        @return: 2 element list containing the Identity URI fragment and
470        user identifier respectively.
471        '''
472        return uri.rsplit('/', 1)
473   
474    @classmethod
475    def createIdentityURI(cls, uri, userIdentifier):
476        '''This method is the compliment to parseIdentityURI.  Make an OpenID
477        URI from a user identifier and URI fragment
478       
479        @type uri: basestring
480        @param uri: identity URI containing $userIdentifier where user id is
481        to be substituted in
482        @type userIdentifier: basestring
483        @param userIdentifier: identity URI to be parsed
484        @rtype: basestring
485        @return: user OpenID URI
486        '''
487        return Template(uri).substitute(userIdentifier=userIdentifier)
488       
489    @NDGSecurityMiddlewareBase.initCall
490    def __call__(self, environ, start_response):
491        """Standard WSGI interface.  Intercepts the path if it matches any of
492        the paths set in the path_* keyword settings to the config
493       
494        @type environ: dict
495        @param environ: dictionary of environment variables
496        @type start_response: callable
497        @param start_response: standard WSGI callable to set HTTP headers
498        @rtype: basestring
499        @return: WSGI response
500        """
501        if not environ.has_key(self.session_middleware):
502            raise OpenIDProviderConfigError('The session middleware %r is not '
503                                            'present. Have you set up the '
504                                            'session middleware?' %
505                                            self.session_middleware)
506
507        # Beware path is a property and invokes the _setPath method
508        self.session = environ[self.session_middleware]
509        self._render.session = self.session
510       
511        pathMatch = self._matchIdentityURI()
512        if not pathMatch:
513            pathMatch = self.path
514
515        if pathMatch in self.method:
516            # Calls to parse_formvars seem to gobble up the POST content such
517            # that a 2nd call yields nothing! (with Paste 1.7.1)
518            self.query = dict(paste.request.parse_formvars(environ)) 
519            log.debug("Calling method %s ..." % self.method[pathMatch]) 
520           
521            action = getattr(self, self.method[pathMatch])
522            response = action(environ, start_response) 
523            if self._trace and _debugLevel:
524                if isinstance(response, list):
525                    log.debug('Output for %s:\n%s', self.method[pathMatch],
526                                                    ''.join(response))
527                else:
528                    log.debug('Output for %s:\n%s', self.method[pathMatch],
529                                                    response)
530                   
531            return response
532        else:
533            log.debug("No match for path %s" % self.path)
534            return self._setResponse(environ, start_response)
535
536    def do_id(self, environ, start_response):
537        '''URL based discovery with an ID provided
538       
539        @type environ: dict
540        @param environ: dictionary of environment variables
541        @type start_response: callable
542        @param start_response: standard WSGI callable to set HTTP headers
543        @rtype: basestring
544        @return: WSGI response
545       
546        '''
547        response = self._render.identityPage(environ, start_response)
548        return response
549
550    def do_yadis(self, environ, start_response):
551        """Handle Yadis based discovery with an ID provided
552       
553        @type environ: dict
554        @param environ: dictionary of environment variables
555        @type start_response: callable
556        @param start_response: standard WSGI callable to set HTTP headers
557        @rtype: basestring
558        @return: WSGI response
559
560        """
561        response = self._render.yadis(environ, start_response)
562        return response
563
564    def do_serveryadis(self, environ, start_response):
565        """Yadis based discovery for ID Select mode i.e. no user ID given for
566        OpenID identifier at Relying Party
567       
568        @type environ: dict
569        @param environ: dictionary of environment variables
570        @type start_response: callable
571        @param start_response: standard WSGI callable to set HTTP headers
572        @rtype: basestring
573        @return: WSGI response
574
575        """
576        response = self._render.serverYadis(environ, start_response)
577        return response
578
579    def do_openidserver(self, environ, start_response):
580        """OpenID Server endpoint - handles OpenID Request following discovery
581       
582        @type environ: dict
583        @param environ: dictionary of environment variables
584        @type start_response: callable
585        @param start_response: standard WSGI callable to set HTTP headers
586        @rtype: basestring
587        @return: WSGI response
588        """
589        try:
590            oidRequest = self.oidserver.decodeRequest(self.query)
591           
592        except server.ProtocolError, why:
593            response = self._displayResponse(why)
594           
595        else:
596            if oidRequest is None:
597                # Display text indicating that this is an endpoint.
598                response = self.do_mainpage(environ, start_response)
599           
600            # Check mode is one of "checkid_immediate", "checkid_setup"
601            elif oidRequest.mode in server.BROWSER_REQUEST_MODES:
602                response = self._handleCheckIDRequest(oidRequest)
603            else:
604                oidResponse = self.oidserver.handleRequest(oidRequest)
605                response = self._displayResponse(oidResponse)
606           
607        return response
608
609    def do_allow(self, environ, start_response):
610        """Handle allow request processing the result of do_decide: does user
611        allow credentials to be passed back to the Relying Party?
612       
613        This method expects the follow fields to have been set in the posted
614        form created by the RenderingInterface.decidePage method called by
615        do_decide:
616       
617        'Yes'/'No': for return authentication details back to the RP or
618        abort return to RP respectively
619        'remember': remember the decision corresponding to the above 'Yes'
620        /'No'.
621        This may be set to 'Yes' or 'No'
622        'identity': set to the user's identity URL.  This usually is not
623        required since it can be obtained from oidRequest.identity attribute
624        but in ID Select mode, the identity URL will have been selected or set
625        in the decide page interface.
626       
627       
628        @type environ: dict
629        @param environ: dictionary of environment variables
630        @type start_response: callable
631        @param start_response: standard WSGI callable to set HTTP headers
632        @rtype: basestring
633        @return: WSGI response
634
635        """
636       
637        oidRequest = self.session.get(
638                    OpenIDProviderMiddleware.LAST_CHECKID_REQUEST_SESSION_KEYNAME)
639        if oidRequest is None:
640            log.error("Suspected do_allow called from stale request")
641            return self._render.errorPage(environ, start_response,
642                                          "Invalid request.  Please report "
643                                          "this fault to your site "
644                                          "administrator.",
645                                          code=400)
646       
647        if OpenIDProviderMiddleware.APPROVE_RP_SUBMIT in self.query:
648            if oidRequest.idSelect():
649                identity = self.query.get('identity')
650                if identity is None:
651                    log.error("No identity field set from decide page for "
652                              "processing in ID Select mode")
653                    return self._render.errorPage(environ, start_response,
654                                                  "An internal error has "
655                                                  "occurred setting the "
656                                                  "OpenID user identity.  "
657                                                  "Please report this fault "
658                                                  "to your site "
659                                                  "administrator.")
660            else:
661                identity = oidRequest.identity
662
663            trust_root = oidRequest.trust_root
664            if self.query.get('remember', 'no').lower() == 'yes':
665                self.session[
666                    OpenIDProviderMiddleware.APPROVED_FLAG_SESSION_KEYNAME] = {
667                        trust_root: 'always'
668                    }
669                self.session.save()
670             
671            try:
672                oidResponse = self._identityApprovedPostProcessing(oidRequest,
673                                                                   identity)
674
675            except (OpenIDProviderMissingRequiredAXAttrs,
676                    OpenIDProviderMissingAXResponseHandler):
677                log.error("%s type exception raised setting response "
678                          "following ID Approval: %s", 
679                          e.__class__.__name__,
680                          traceback.format_exc())
681               
682                response = self._render.errorPage(environ, start_response,
683                        'The site where you wish to signin requires '
684                        'additional information which this site isn\'t '
685                        'configured to provide.  Please report this fault to '
686                        'your site administrator.')
687                return response
688                   
689            except Exception, e:
690                log.error("%s type exception raised setting response "
691                          "following ID Approval: %s", 
692                          e.__class__.__name__,
693                          traceback.format_exc())
694                return self._render.errorPage(environ, start_response,
695                        'An error occurred setting additional parameters '
696                        'required by the site requesting your ID.  Please '
697                        'report this fault to your site administrator.')
698            else:
699                return self._displayResponse(oidResponse)
700       
701        elif OpenIDProviderMiddleware.REJECT_RP_SUBMIT in self.query:
702            # TODO: Check 'No' response is OK - No causes AuthKit's Relying
703            # Party implementation to crash with 'openid.return_to' KeyError
704            # in Authkit.authenticate.open_id.process
705            oidResponse = oidRequest.answer(False)
706            #return self._displayResponse(oidResponse)
707            return self._render.mainPage(environ, start_response)           
708        else:
709            log.error('Setting response following ID Approval: expecting '
710                      'Yes/No in allow post. %r' % self.query)
711            return self._render.errorPage(environ, start_response,
712                                          'Error setting Yes/No response to '
713                                          'return your credentials back to '
714                                          'the requesting site.  Please '
715                                          'report this fault to your site '
716                                          'administrator.',
717                                          code=400)
718
719    def do_login(self, environ, start_response, **kw):
720        """Display Login form
721       
722        @type environ: dict
723        @param environ: dictionary of environment variables
724        @type start_response: callable
725        @param start_response: standard WSGI callable to set HTTP headers
726        @type kw: dict
727        @param kw: keywords to login renderer - see RenderingInterface class
728        @rtype: basestring
729        @return: WSGI response
730        """
731       
732        if 'fail_to' not in kw:
733            kw['fail_to'] = self.urls['url_login']
734           
735        response = self._render.login(environ, start_response, **kw)
736        return response
737
738    def do_loginsubmit(self, environ, start_response):
739        """Handle user submission from login and logout
740       
741        @type environ: dict
742        @param environ: dictionary of environment variables
743        @type start_response: callable
744        @param start_response: standard WSGI callable to set HTTP headers
745        @rtype: basestring
746        @return: WSGI response
747        """
748       
749        if 'submit' in self.query:
750            if 'username' in self.query:
751                # login
752                if OpenIDProviderMiddleware.USERNAME_SESSION_KEYNAME in \
753                   self.session:
754                    log.error("Attempting login for user %s: user is already "
755                              "logged in", self.session[
756                            OpenIDProviderMiddleware.USERNAME_SESSION_KEYNAME])
757                   
758                    return self._redirect(start_response,
759                                          self.query['fail_to'])
760               
761                oidRequest = self.session.get(
762                OpenIDProviderMiddleware.LAST_CHECKID_REQUEST_SESSION_KEYNAME)
763               
764                if oidRequest is None:
765                    log.error("Getting OpenID request for login - No request "
766                              "found in session")
767                    return self._render.errorPage(environ, start_response,
768                        "An internal error occurred possibly due to a request "
769                        "that's expired.  Please retry from the site where "
770                        "you entered your OpenID.  If the problem persists "
771                        "report it to your site administrator.")
772                   
773                # Get user identifier to check against credentials provided
774                if oidRequest.idSelect():
775                    # ID select mode enables the user to request specifying
776                    # their OpenID Provider without giving a personal user URL
777                    userIdentifiers = self._authN.username2UserIdentifiers(
778                                                        environ,
779                                                        self.query['username'])
780                    if not isinstance(userIdentifiers, (list, tuple)):
781                        log.error("Unexpected type %r returned from %r for "
782                                  "user identifiers; expecting list or tuple"
783                                  % (type(userIdentifiers), type(self._authN)))
784                                 
785                        return self._render.errorPage(environ, start_response,
786                            "An internal error occurred setting your default "
787                            "OpenID URL.  Please retry from the site where "
788                            "you entered your OpenID providing your full "
789                            "identity URL.  If the problem persists report it "
790                            "to your site administrator.")
791                       
792                    # FIXME: Assume the *first* user identifier entry is the
793                    # one to use.  The user could have multiple identifiers
794                    # but in practice it's more manageable to have a single one
795                    identityURI = self.createIdentityURI(self.identityUriTmpl,
796                                                         userIdentifiers[0])
797                else:
798                    # Get the unique user identifier from the user's OpenID URL
799                    identityURI = oidRequest.identity
800                   
801                # Invoke custom authentication interface plugin
802                try:
803                    self._authN.logon(environ,
804                                      identityURI,
805                                      self.query['username'],
806                                      self.query.get('password', ''))
807                   
808                except AuthNInterfaceError, e:
809                    return self._render.login(environ, start_response,
810                                            msg=e.userMsg,
811                                            success_to=self.urls['url_decide'])                   
812                except Exception, e:
813                    log.error("Unexpected %s type exception raised during "
814                              "authentication: %s", type(e),
815                              traceback.format_exc())
816                    msg = ("An internal error occurred.  "
817                           "Please try again or if the problems persists "
818                           "contact your system administrator.")
819
820                    response = self._render.login(environ, start_response,
821                                          msg=msg,
822                                          success_to=self.urls['url_decide'])
823                    return response
824                       
825                self.session[
826                    OpenIDProviderMiddleware.USERNAME_SESSION_KEYNAME
827                ] = self.query['username']
828               
829                self.session[
830                    OpenIDProviderMiddleware.IDENTITY_URI_SESSION_KEYNAME
831                ] = identityURI
832               
833                self.session[
834                    OpenIDProviderMiddleware.APPROVED_FLAG_SESSION_KEYNAME] = {}
835                   
836                self.session.save()
837               
838                log.info("user [%s] logged in", self.session[
839                        OpenIDProviderMiddleware.USERNAME_SESSION_KEYNAME])
840            else:
841                # logout
842                if OpenIDProviderMiddleware.USERNAME_SESSION_KEYNAME not in \
843                   self.session:
844                    log.error("No user is logged in")
845                    return self._redirect(start_response, 
846                                          self.query['fail_to'])
847               
848                log.info("user [%s] logging out ...", self.session[
849                        OpenIDProviderMiddleware.USERNAME_SESSION_KEYNAME])
850
851                del self.session[
852                        OpenIDProviderMiddleware.USERNAME_SESSION_KEYNAME]
853                self.session.pop(
854                        OpenIDProviderMiddleware.APPROVED_FLAG_SESSION_KEYNAME, 
855                        None)
856                self.session.save()
857               
858                try:
859                    self._authN.logout()
860                   
861                except Exception:
862                    log.error("Unexpected exception raised during "
863                              "logout: %s" % traceback.format_exc())
864                    msg = ("An internal error occured during logout.  If the "
865                           "problem persists contact your system "
866                           "administrator.")
867
868                    response = self._render.errorPage(environ, start_response,
869                                                      msg) 
870                    return response
871               
872            return self._redirect(start_response, self.query['success_to'])
873       
874        elif 'cancel' in self.query:
875            return self._redirect(start_response, self.query['fail_to'])
876        else:
877            log.error('Login input not recognised %r' % self.query)
878            return self._redirect(start_response, self.query['fail_to']) 
879
880    def do_mainpage(self, environ, start_response):
881        '''Show an information page about the OpenID Provider
882       
883        @type environ: dict
884        @param environ: dictionary of environment variables
885        @type start_response: callable
886        @param start_response: standard WSGI callable to set HTTP headers
887        @rtype: basestring
888        @return: WSGI response
889        '''   
890        response = self._render.mainPage(environ, start_response)
891        return response
892
893    def _getRender(self):
894        """Get method for rendering interface object
895        @rtype: RenderingInterface
896        @return: rendering interface object
897        """
898        return self._render
899   
900    render = property(fget=_getRender, doc="Rendering interface instance")
901   
902    def do_decide(self, environ, start_response):
903        """Display page prompting the user to decide whether to trust the site
904        requesting their credentials
905       
906        @type environ: dict
907        @param environ: dictionary of environment variables
908        @type start_response: callable
909        @param start_response: standard WSGI callable to set HTTP headers
910        @rtype: basestring
911        @return: WSGI response
912        """
913        oidRequest = self.session.get(
914                OpenIDProviderMiddleware.LAST_CHECKID_REQUEST_SESSION_KEYNAME)
915        if oidRequest is None:
916            log.error("No OpenID request set in session")
917            return self._render.errorPage(environ, start_response,
918                                          "Invalid request.  Please report "
919                                          "the error to your site "
920                                          "administrator.",
921                                          code=400)
922       
923        try:
924            return self._render.decidePage(environ, 
925                                           start_response, 
926                                           oidRequest)
927        except AuthNInterfaceError, e:
928            log.error("%s type exception raised calling decide page "
929                      "rendering - an OpenID identifier look-up error? "
930                      "message is: %s", e.__class__.__name__,
931                      traceback.format_exc())
932            response = self._render.errorPage(environ, start_response,
933                    'An error has occurred displaying an options page '
934                    'which checks whether you want to return to the site '
935                    'requesting your ID.  Please report this fault to '
936                    'your site administrator.')
937            return response
938
939    def _identityIsAuthorized(self, oidRequest):
940        '''Check that a user is authorized i.e. does a session exist for their
941        username and if so does it correspond to the identity URL provided.
942        This last check doesn't apply for ID Select mode where No ID was input
943        at the Relying Party.
944       
945        @type oidRequest: openid.server.server.CheckIDRequest
946        @param oidRequest: OpenID Request object
947        @rtype: bool
948        @return: True/False is user authorized
949        '''
950        username = self.session.get(
951                            OpenIDProviderMiddleware.USERNAME_SESSION_KEYNAME)
952        if username is None:
953            return False
954
955        if oidRequest.idSelect():
956            log.debug("OpenIDProviderMiddleware._identityIsAuthorized - "
957                      "ID Select mode set but user is already logged in")
958            return True
959       
960        identityURI = self.session.get(
961                        OpenIDProviderMiddleware.IDENTITY_URI_SESSION_KEYNAME)
962        if identityURI is None:
963            return False
964       
965        if oidRequest.identity != identityURI:
966            log.debug("OpenIDProviderMiddleware._identityIsAuthorized - user "
967                      "is already logged in with a different ID=%s and "
968                      "identityURI=%s" %
969                      (username, identityURI))
970            return False
971       
972        log.debug("OpenIDProviderMiddleware._identityIsAuthorized - "
973                  "user is logged in with ID matching ID URI")
974        return True
975   
976
977    def _trustRootIsAuthorized(self, trust_root):
978        '''Return True/False for the given trust root (Relying Party)
979        previously been approved by the user
980       
981        @type trust_root: dict
982        @param trust_root: keyed by trusted root (Relying Party) URL and
983        containing string item 'always' if approved
984        @rtype: bool
985        @return: True - trust has already been approved, False - trust root is
986        not approved'''
987        approvedRoots = self.session.get(
988                        OpenIDProviderMiddleware.APPROVED_FLAG_SESSION_KEYNAME, 
989                        {})
990        return approvedRoots.get(trust_root) is not None
991
992    def _addSRegResponse(self, oidRequest, oidResponse):
993        '''Add Simple Registration attributes to response to Relying Party
994       
995        @type oidRequest: openid.server.server.CheckIDRequest
996        @param oidRequest: OpenID Check ID Request object
997        @type oidResponse: openid.server.server.OpenIDResponse
998        @param oidResponse: OpenID response object'''
999       
1000        if self.sregResponseHandler is None:
1001            # No Simple Registration response object was set
1002            return
1003       
1004        sreg_req = sreg.SRegRequest.fromOpenIDRequest(oidRequest)
1005
1006        # Callout to external callable sets additional user attributes to be
1007        # returned in response to Relying Party       
1008        sreg_data = self.sregResponseHandler(self.session.get(
1009                            OpenIDProviderMiddleware.USERNAME_SESSION_KEYNAME))
1010        sreg_resp = sreg.SRegResponse.extractResponse(sreg_req, sreg_data)
1011        oidResponse.addExtension(sreg_resp)
1012
1013    def _addAXResponse(self, oidRequest, oidResponse):
1014        '''Add attributes to response based on the OpenID Attribute Exchange
1015        interface
1016       
1017        @type oidRequest: openid.server.server.CheckIDRequest
1018        @param oidRequest: OpenID Check ID Request object
1019        @type oidResponse: openid.server.server.OpenIDResponse
1020        @param oidResponse: OpenID response object'''
1021
1022        ax_req = ax.FetchRequest.fromOpenIDRequest(oidRequest)
1023        if ax_req is None:
1024            log.debug("No Attribute Exchange extension set in request")
1025            return
1026       
1027        ax_resp = ax.FetchResponse(request=ax_req)
1028       
1029        if self.axResponse is None:
1030            requiredAttr = ax_req.getRequiredAttrs()
1031            if len(requiredAttr) > 0:
1032                msg = ("Relying party requires these attributes: %s; but No"
1033                       "Attribute exchange handler 'axResponseHandler' has "
1034                       "been set" % requiredAttr)
1035                log.error(msg)
1036                raise OpenIDProviderMissingAXResponseHandler(msg)
1037           
1038            return
1039       
1040        log.debug("Calling AX plugin: %s ...",
1041                  self.axResponse.__class__.__name__)
1042       
1043        # Set requested values - need user intervention here to confirm
1044        # release of attributes + assignment based on required attributes -
1045        # possibly via FetchRequest.getRequiredAttrs()
1046        try:
1047            self.axResponse(ax_req, ax_resp, self._authN, self.session)
1048       
1049        except OpenIDProviderMissingRequiredAXAttrs, e:
1050            log.error("OpenID Provider is unable to set the AX attributes "
1051                      "required by the Relying Party's request: %s" % e)
1052            raise
1053       
1054        except OpenIDProviderReloginRequired, e:
1055            log.exception(e)
1056            raise
1057       
1058        except Exception, e:
1059            log.error("%s exception raised setting requested Attribute "
1060                      "Exchange values: %s", 
1061                      e.__class__.__name__, 
1062                      traceback.format_exc())
1063            raise
1064       
1065        log.debug("Adding AX parameters to response: %s ...", ax_resp)
1066        oidResponse.addExtension(ax_resp)
1067        log.debug("Added AX parameters to response")
1068       
1069    def _identityApprovedPostProcessing(self, oidRequest, identifier=None):
1070        '''Action following approval of a Relying Party by the user.  Add
1071        Simple Registration and/or Attribute Exchange parameters if handlers
1072        were specified - See _addSRegResponse and _addAXResponse methods - and
1073        only if the Relying Party has requested them
1074       
1075        @type oidRequest: openid.server.server.CheckIDRequest
1076        @param oidRequest: OpenID Check ID Request object
1077        @type identifier: basestring
1078        @param identifier: OpenID selected by user - for ID Select mode only
1079        @rtype: openid.server.server.OpenIDResponse
1080        @return: OpenID response object'''
1081
1082        oidResponse = oidRequest.answer(True, identity=identifier)
1083        self._addSRegResponse(oidRequest, oidResponse)
1084        self._addAXResponse(oidRequest, oidResponse)
1085       
1086        return oidResponse
1087
1088    def _handleCheckIDRequest(self, oidRequest):
1089        """Handle "checkid_immediate" and "checkid_setup" type requests from
1090        Relying Party
1091       
1092        @type oidRequest: openid.server.server.CheckIDRequest
1093        @param oidRequest: OpenID Check ID request
1094        @rtype: basestring
1095        @return: WSGI response
1096        """
1097        log.debug("OpenIDProviderMiddleware._handleCheckIDRequest ...")
1098       
1099        # Save request
1100        self.session[
1101            OpenIDProviderMiddleware.LAST_CHECKID_REQUEST_SESSION_KEYNAME
1102        ] = oidRequest
1103       
1104        self.session.save()
1105       
1106        if self._identityIsAuthorized(oidRequest):
1107           
1108            # User is logged in - check for ID Select type request i.e. the
1109            # user entered their IdP address at the Relying Party and not their
1110            # full OpenID URL.  In this case, the identity they wish to use must
1111            # be confirmed.
1112            if oidRequest.idSelect():
1113                # OpenID identifier must be confirmed
1114                return self.do_decide(self.environ, self.start_response)
1115           
1116            elif self._trustRootIsAuthorized(oidRequest.trust_root):
1117                # User entered their full OpenID URL and they have previously
1118                # approved this Relying Party
1119                try:
1120                    oidResponse = self._identityApprovedPostProcessing(
1121                                                                oidRequest)
1122                except (OpenIDProviderMissingRequiredAXAttrs,
1123                        OpenIDProviderMissingAXResponseHandler):
1124                    response = self._render.errorPage(self.environ, 
1125                                                      self.start_response,
1126                        'The site where you wish to signin requires '
1127                        'additional information which this site isn\'t '
1128                        'configured to provide.  Please report this fault to '
1129                        'your site administrator.')
1130                    return response
1131                   
1132                except OpenIDProviderReloginRequired, e:
1133                    response = self._render.errorPage(self.environ, 
1134                                                      self.start_response,
1135                        'An error occurred setting additional parameters '
1136                        'required by the site requesting your ID.  Please '
1137                        'try logging in again.')
1138                    return response
1139                   
1140                except Exception, e:
1141                    log.error("%s type exception raised setting response "
1142                              "following ID Approval: %s", 
1143                              e.__class__.__name__, 
1144                              traceback.format_exc())
1145                    response = self._render.errorPage(self.environ,
1146                                                      self.start_response,
1147                        'An error occurred setting additional parameters '
1148                        'required by the site requesting your ID.  Please '
1149                        'report this fault to your site administrator.')
1150                    return response
1151               
1152                return self._displayResponse(oidResponse)
1153            else:
1154                # This OpenID is being used for a login for the first time. 
1155                # Check with the user whether they want to approval the Relying
1156                # Party's request
1157                return self.do_decide(self.environ, self.start_response)
1158               
1159        elif oidRequest.immediate:
1160            oidResponse = oidRequest.answer(False)
1161            return self._displayResponse(oidResponse)       
1162        else:
1163            # User is not logged in
1164           
1165            # Call login and if successful then call decide page to confirm
1166            # user wishes to trust the Relying Party.
1167            response = self.do_login(self.environ,
1168                                     self.start_response,
1169                                     success_to=self.urls['url_decide'])
1170            return response
1171
1172    def _displayResponse(self, oidResponse):
1173        """Serialize an OpenID Response object, set headers and return WSGI
1174        response.
1175       
1176        If the URL length for a GET request exceeds a maximum, then convert the
1177        response into a HTML form and use POST method.
1178       
1179        @type oidResponse: openid.server.server.OpenIDResponse
1180        @param oidResponse: OpenID response object
1181       
1182        @rtype: basestring
1183        @return: WSGI response'''
1184        """     
1185        try:
1186            webresponse = self.oidserver.encodeResponse(oidResponse)
1187        except server.EncodingError, why:
1188            text = why.response.encodeToKVForm()
1189            response = self._render.errorPage(environ, start_response, text)
1190            return response
1191       
1192        hdr = webresponse.headers.items()
1193       
1194        # If the content length exceeds the maximum to represent on a URL, it's
1195        # rendered as a form instead
1196        # FIXME: Commented out oidResponse.renderAsForm() test as it doesn't
1197        # give consistent answers.  Testing based on body content should work
1198        # OK
1199        if webresponse.body:
1200        #if oidResponse.renderAsForm():
1201            # Wrap in HTML with Javascript OnLoad to submit the form
1202            # automatically without user intervention
1203            response = OpenIDProviderMiddleware.formRespWrapperTmpl % \
1204                                                        webresponse.body
1205        else:
1206            response = webresponse.body
1207           
1208        hdr += [('Content-type', 'text/html' + self.charset),
1209                ('Content-length', str(len(response)))]
1210           
1211        self.start_response('%d %s' % (webresponse.code,
1212                                       httplib.responses[webresponse.code]),
1213                            hdr)
1214        return response
1215
1216    def _redirect(self, start_response, url):
1217        """Do a HTTP 302 redirect
1218       
1219        @type start_response: function
1220        @param start_response: WSGI start response callable
1221        @type url: basestring
1222        @param url: URL to redirect to
1223        @rtype: list
1224        @return: empty HTML body
1225        """
1226        start_response('302 %s' % httplib.responses[302],
1227                       [('Content-type', 'text/html' + self.charset),
1228                        ('Location', url)])
1229        return []
1230
1231   
1232class RenderingInterfaceError(Exception):
1233    """Base class for RenderingInterface exceptions
1234   
1235    A standard message is raised set by the msg class variable but the actual
1236    exception details are logged to the error log.  The use of a standard
1237    message enables callers to use its content for user error messages.
1238   
1239    @type msg: basestring
1240    @cvar msg: standard message to be raised for this exception"""
1241    userMsg = ("An internal error occurred with the page layout,  Please "
1242               "contact your system administrator")
1243    errorMsg = "RenderingInterface error"
1244   
1245    def __init__(self, *arg, **kw):
1246        if len(arg) > 0:
1247            msg = arg[0]
1248        else:
1249            msg = self.__class__.errorMsg
1250           
1251        log.error(msg)
1252        Exception.__init__(self, msg, **kw)
1253       
1254       
1255class RenderingInterfaceInitError(RenderingInterfaceError):
1256    """Error with initialisation of RenderingInterface.  Raise from __init__"""
1257    errorMsg = "RenderingInterface initialisation error"
1258   
1259   
1260class RenderingInterfaceConfigError(RenderingInterfaceError):
1261    """Error with configuration settings.  Raise from __init__"""
1262    errorMsg = "RenderingInterface configuration error"   
1263   
1264   
1265class RenderingInterface(object):
1266    """Interface class for rendering of OpenID Provider pages.  It implements
1267    methods for handling Yadis requests only.  All other interface methods
1268    return a 404 error response.  Create a derivative from this class to
1269    implement the other rendering methods as required. 
1270    ndg.security.server.wsgi.openid.provider.renderingInterface.demo.DemoRenderingInterface
1271    provides an example of how to do this.  To apply a custom
1272    RenderingInterface class pass it's name in the OpenIDProviderMiddleware
1273    app_conf dict or as a keyword argument using the option name
1274    renderingClass.
1275   
1276    @cvar tmplServerYadis: template for returning Yadis document to Relying
1277    Party.  Derived classes can reset this or completely override the
1278    serverYadis method.
1279   
1280    @type tmplServerYadis: basestring
1281   
1282    @cvar tmplYadis: template for returning Yadis document containing user
1283    URL to Relying Party.  Derived classes can reset this or completely
1284    override the yadis method.
1285   
1286    @type tmplYadis: basestring"""
1287   
1288    # Enable slot support for derived classes if they require it
1289    __slots__ = ('_authN', 'base_url', 'urls', 'charset')
1290   
1291    tmplServerYadis = """\
1292<?xml version="1.0" encoding="UTF-8"?>
1293<xrds:XRDS
1294    xmlns:xrds="xri://$xrds"
1295    xmlns="xri://$xrd*($v*2.0)">
1296  <XRD>
1297
1298    <Service priority="0">
1299      <Type>%(openid20type)s</Type>
1300      <URI>%(endpoint_url)s</URI>
1301    </Service>
1302
1303  </XRD>
1304</xrds:XRDS>
1305"""
1306
1307    tmplYadis = """\
1308<?xml version="1.0" encoding="UTF-8"?>
1309<xrds:XRDS
1310    xmlns:xrds="xri://$xrds"
1311    xmlns="xri://$xrd*($v*2.0)">
1312  <XRD>
1313
1314    <Service priority="0">
1315      <Type>%(openid20type)s</Type>
1316      <Type>%(openid10type)s</Type>
1317      <URI>%(endpoint_url)s</URI>
1318      <LocalID>%(user_url)s</LocalID>
1319    </Service>
1320
1321  </XRD>
1322</xrds:XRDS>"""   
1323   
1324    def __init__(self, authN, base_url, urls, **opt):
1325        """
1326        @type authN: AuthNInterface
1327        @param param: reference to authentication interface to enable OpenID
1328        user URL construction from username
1329        @type base_url: basestring
1330        @param base_url: base URL for OpenID Provider to which individual paths
1331        are appended
1332        @type urls: dict
1333        @param urls: full urls for all the paths used by all the exposed
1334        methods - keyed by method name - see OpenIDProviderMiddleware.paths
1335        @type opt: dict
1336        @param opt: additional custom options passed from the
1337        OpenIDProviderMiddleware config
1338        """
1339        self._authN = authN
1340        self.base_url = base_url
1341        self.urls = urls
1342        self.charset = ''
1343   
1344    def serverYadis(self, environ, start_response):
1345        '''Render Yadis info for ID Select mode request
1346       
1347        @type environ: dict
1348        @param environ: dictionary of environment variables
1349        @type start_response: callable
1350        @param start_response: WSGI start response function.  Should be called
1351        from this method to set the response code and HTTP header content
1352        @rtype: basestring
1353        @return: WSGI response
1354        '''
1355        endpoint_url = self.urls['url_openidserver']
1356        response = RenderingInterface.tmplServerYadis % \
1357                                {'openid20type': discover.OPENID_IDP_2_0_TYPE,
1358                                 'endpoint_url': endpoint_url}
1359             
1360        start_response("200 OK",
1361                       [('Content-type', 'application/xrds+xml'),
1362                        ('Content-length', str(len(response)))])
1363        return response
1364
1365    def yadis(self, environ, start_response):
1366        """Render Yadis document containing user URL
1367       
1368        @type environ: dict
1369        @param environ: dictionary of environment variables
1370        @type start_response: callable
1371        @param start_response: WSGI start response function.  Should be called
1372        from this method to set the response code and HTTP header content
1373        @rtype: basestring
1374        @return: WSGI response
1375        """
1376        # Override this method to implement an alternate means to derive the
1377        # username identifier
1378        userIdentifier = OpenIDProviderMiddleware.parseIdentityURI(
1379                                                    environ['PATH_INFO'])[-1]
1380       
1381        endpoint_url = self.urls['url_openidserver']
1382        user_url = self.urls['url_id'] + '/' + userIdentifier
1383       
1384        yadisDict = dict(openid20type=discover.OPENID_2_0_TYPE,
1385                         openid10type=discover.OPENID_1_0_TYPE,
1386                         endpoint_url=endpoint_url,
1387                         user_url=user_url)
1388       
1389        response = RenderingInterface.tmplYadis % yadisDict
1390     
1391        start_response('200 OK',
1392                       [('Content-type', 'application/xrds+xml' + self.charset),
1393                        ('Content-length', str(len(response)))])
1394        return response
1395   
1396    def identityPage(self, environ, start_response):
1397        """Render the identity page.
1398       
1399        @type environ: dict
1400        @param environ: dictionary of environment variables
1401        @type start_response: callable
1402        @param start_response: WSGI start response function.  Should be called
1403        from this method to set the response code and HTTP header content
1404        @rtype: basestring
1405        @return: WSGI response
1406        """
1407        response = "Page is not implemented"
1408        start_response('%d %s' % (404, httplib.responses[404]),
1409                       [('Content-type', 'text/html' + self.charset),
1410                        ('Content-length', str(len(response)))])
1411        return response
1412         
1413    def login(self, environ, start_response,
1414              success_to=None, fail_to=None, msg=''):
1415        """Render the login form.
1416       
1417        @type environ: dict
1418        @param environ: dictionary of environment variables
1419        @type start_response: callable
1420        @param start_response: WSGI start response function.  Should be called
1421        from this method to set the response code and HTTP header content
1422        @type success_to: basestring
1423        @param success_to: URL put into hidden field telling 
1424        OpenIDProviderMiddleware.do_loginsubmit() where to forward to on
1425        successful login
1426        @type fail_to: basestring
1427        @param fail_to: URL put into hidden field telling 
1428        OpenIDProviderMiddleware.do_loginsubmit() where to forward to on
1429        login error
1430        @type msg: basestring
1431        @param msg: display (error) message below login form e.g. following
1432        previous failed login attempt.
1433        @rtype: basestring
1434        @return: WSGI response
1435        """
1436       
1437        response = "Page is not implemented"
1438        start_response('%d %s' % (404, httplib.responses[404]),
1439                       [('Content-type', 'text/html' + self.charset),
1440                        ('Content-length', str(len(response)))])
1441        return response
1442
1443    def mainPage(self, environ, start_response):
1444        """Rendering the main page.
1445       
1446        @type environ: dict
1447        @param environ: dictionary of environment variables
1448        @type start_response: callable
1449        @param start_response: WSGI start response function.  Should be called
1450        from this method to set the response code and HTTP header content
1451        @rtype: basestring
1452        @return: WSGI response
1453        """   
1454        response = "Page is not implemented"
1455        start_response('%d %s' % (404, httplib.responses[404]),
1456                       [('Content-type', 'text/html' + self.charset),
1457                        ('Content-length', str(len(response)))])
1458        return response
1459   
1460
1461    def decidePage(self, environ, start_response, oidRequest):
1462        """Show page giving the user the option to approve the return of their
1463        credentials to the Relying Party.  This page is also displayed for
1464        ID select mode if the user is already logged in at the OpenID Provider.
1465        This enables them to confirm the OpenID to be sent back to the
1466        Relying Party
1467
1468        These fields should be posted by this page ready for
1469        OpenIdProviderMiddleware.do_allow to process:
1470       
1471        'Yes'/'No': for return authentication details back to the RP or
1472        abort return to RP respectively
1473        'remember': remember the decision corresponding to the above 'Yes'
1474        /'No'.
1475        This may be set to 'Yes' or 'No'
1476        'identity': set to the user's identity URL.  This usually is not
1477        required since it can be obtained from oidRequest.identity attribute
1478        but in ID Select mode, the identity URL will have been selected or set
1479        here.
1480       
1481       
1482        @type environ: dict
1483        @param environ: dictionary of environment variables
1484        @type start_response: callable
1485        @param start_response: WSGI start response function.  Should be called
1486        from this method to set the response code and HTTP header content
1487        @type oidRequest: openid.server.server.CheckIDRequest
1488        @param oidRequest: OpenID Check ID Request object
1489        @rtype: basestring
1490        @return: WSGI response
1491        """
1492        response = "Page is not implemented"
1493        start_response('%d %s' % (404, httplib.responses[404]),
1494                       [('Content-type', 'text/html' + self.charset),
1495                        ('Content-length', str(len(response)))])
1496        return response
1497
1498
1499    def errorPage(self, environ, start_response, msg, code=500):
1500        """Display error page
1501       
1502        @type environ: dict
1503        @param environ: dictionary of environment variables
1504        @type start_response: callable
1505        @param start_response: WSGI start response function.  Should be called
1506        from this method to set the response code and HTTP header content
1507        @type msg: basestring
1508        @param msg: optional message for page body
1509        @type code: int
1510        @param code: HTTP Error code to return
1511        @rtype: basestring
1512        @return: WSGI response
1513        """     
1514        response = "Page is not implemented"
1515        start_response('%d %s' % (404, httplib.responses[code]),
1516                       [('Content-type', 'text/html' + self.charset),
1517                        ('Content-length', str(len(response)))])
1518        return response
1519       
Note: See TracBrowser for help on using the repository browser.