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

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@5087
Revision 5087, 60.9 KB checked in by pjkersha, 12 years ago (diff)

Moved DemoRenderingInterface? from ndg.security.server.wsgi.openid.provider into it's own module ndg.security.server.wsgi.openid.provider.renderinginterface.demo

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