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

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@5786
Revision 5786, 62.5 KB checked in by pjkersha, 10 years ago (diff)

Updated OpenID AX (Attribute Exchange) interface. Attributes passed over this interface are now stored in the authentication session at the Relying Party.

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