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

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

Added additional debug logging and improved error handling

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