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

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@5222
Revision 5222, 62.8 KB checked in by pjkersha, 11 years ago (diff)
  • added ndg_security_test to buildout scripts
  • added BasicSessionManagerOpenIdAuthNInterface class to OpenID Provider Authentication interfaces ndg.security.server.wsgi.openid.provider.authninterface.basic
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("Setting response following ID Approval: %s" % e)
749                return self._render.errorPage(environ, start_response,
750                        'An error occurred setting additional parameters '
751                        'required by the site requesting your ID.  Please '
752                        'report this fault to your site administrator.')
753            else:
754                return self._displayResponse(oidResponse)
755       
756        elif 'No' in self.query:
757            # TODO: Check 'No' response is OK - No causes AuthKit's Relying
758            # Party implementation to crash with 'openid.return_to' KeyError
759            # in Authkit.authenticate.open_id.process
760            oidResponse = oidRequest.answer(False)
761            #return self._displayResponse(oidResponse)
762            return self._render.mainPage(environ, start_response)           
763        else:
764            log.error('Setting response following ID Approval: expecting '
765                      'Yes/No in allow post. %r' % self.query)
766            return self._render.errorPage(environ, start_response,
767                                          'Error setting Yes/No response to '
768                                          'return your credentials back to '
769                                          'the requesting site.  Please '
770                                          'report this fault to your site '
771                                          'administrator.',
772                                          code=400)
773
774
775    def do_login(self, environ, start_response, **kw):
776        """Display Login form
777       
778        @type environ: dict
779        @param environ: dictionary of environment variables
780        @type start_response: callable
781        @param start_response: standard WSGI callable to set HTTP headers
782        @type kw: dict
783        @param kw: keywords to login renderer - see RenderingInterface class
784        @rtype: basestring
785        @return: WSGI response
786        """
787       
788        if 'fail_to' not in kw:
789            kw['fail_to'] = self.urls['url_login']
790           
791        response = self._render.login(environ, start_response, **kw)
792        return response
793
794
795    def do_loginsubmit(self, environ, start_response):
796        """Handle user submission from login and logout
797       
798        @type environ: dict
799        @param environ: dictionary of environment variables
800        @type start_response: callable
801        @param start_response: standard WSGI callable to set HTTP headers
802        @rtype: basestring
803        @return: WSGI response
804        """
805       
806        if 'submit' in self.query:
807            if 'username' in self.query:
808                # login
809                if 'username' in self.session:
810                    log.error("Attempting login for user %s: user %s is "
811                              "already logged in", self.session['username'],
812                              self.session['username'])
813                    return self._redirect(start_response,self.query['fail_to'])
814               
815                oidRequest = self.session.get('lastCheckIDRequest')
816                if oidRequest is None:
817                    log.error("Getting OpenID request for login - No request "
818                              "found in session")
819                    return self._render.errorPage(environ, start_response,
820                        "An internal error occurred possibly due to a request "
821                        "that's expired.  Please retry from the site where "
822                        "you entered your OpenID.  If the problem persists "
823                        "report it to your site administrator.")
824                   
825                # Get user identifier to check against credentials provided
826                if oidRequest.idSelect():
827                    # ID select mode enables the user to request specifying
828                    # their OpenID Provider without giving a personal user URL
829                    userIdentifier = None
830                else:
831                    # Get the unique user identifier from the user's OpenID URL
832                    userIdentifier = OpenIDProviderMiddleware.parseIdentityURI(
833                                                    oidRequest.identity)[-1]
834                   
835                # Invoke custom authentication interface plugin
836                try:
837                    self._authN.logon(environ,
838                                      userIdentifier,
839                                      self.query['username'],
840                                      self.query.get('password', ''))
841                   
842                except AuthNInterfaceError, e:
843                    return self._render.login(environ, start_response,
844                                          msg=e.userMsg,
845                                          success_to=self.urls['url_decide'])                   
846                except Exception, e:
847                    log.error("Unexpected exception raised during "
848                              "authentication: %s" % e)
849                    msg = ("An internal error occurred.  "
850                           "Please try again or if the problems persists "
851                           "contact your system administrator.")
852
853                    response = self._render.login(environ, start_response,
854                                      msg=msg,
855                                      success_to=self.urls['url_decide'])
856                    return response
857                       
858                self.session['username'] = self.query['username']
859                self.session['approved'] = {}
860                self.session.save()
861            else:
862                # logout
863                if 'username' not in self.session:
864                    log.error("No user is logged in")
865                    return self._redirect(start_response,self.query['fail_to'])
866               
867                del self.session['username']
868                self.session.pop('approved', None)
869                self.session.save()
870               
871                try:
872                    self._authN.logout()
873                   
874                except Exception, e:
875                    log.error("Unexpected exception raised during "
876                              "logout: %s" % e)
877                    msg = ("An internal error  during logout.  If the "
878                           "problem persists contact your system "
879                           "administrator.")
880
881                    response = self._render.errorPage(environ, start_response,
882                                                      msg) 
883                    return response
884               
885               
886            return self._redirect(start_response, self.query['success_to'])
887       
888        elif 'cancel' in self.query:
889            return self._redirect(start_response, self.query['fail_to'])
890        else:
891            log.error('Login input not recognised %r' % self.query)
892            return self._redirect(start_response, self.query['fail_to'])
893           
894
895    def do_mainpage(self, environ, start_response):
896        '''Show an information page about the OpenID Provider
897       
898        @type environ: dict
899        @param environ: dictionary of environment variables
900        @type start_response: callable
901        @param start_response: standard WSGI callable to set HTTP headers
902        @rtype: basestring
903        @return: WSGI response
904        '''   
905        response = self._render.mainPage(environ, start_response)
906        return response
907
908    def _getRender(self):
909        """Get method for rendering interface object
910        @rtype: RenderingInterface
911        @return: rendering interface object
912        """
913        return self._render
914   
915    render = property(fget=_getRender, doc="Rendering interface instance")
916   
917   
918    def do_decide(self, environ, start_response):
919        """Display page prompting the user to decide whether to trust the site
920        requesting their credentials
921       
922        @type environ: dict
923        @param environ: dictionary of environment variables
924        @type start_response: callable
925        @param start_response: standard WSGI callable to set HTTP headers
926        @rtype: basestring
927        @return: WSGI response
928        """
929
930        oidRequest = self.session.get('lastCheckIDRequest')
931        if oidRequest is None:
932            log.error("No OpenID request set in session")
933            return self._render.errorPage(environ, start_response,
934                                          "Invalid request.  Please report "
935                                          "the error to your site "
936                                          "administrator.",
937                                          code=400)
938       
939        approvedRoots = self.session.get('approved', {})
940       
941        if oidRequest.trust_root in approvedRoots and \
942           not oidRequest.idSelect():
943            try:
944                response = self._identityApprovedPostProcessing(oidRequest,
945                                                        oidRequest.identity)
946            except (OpenIDProviderMissingRequiredAXAttrs,
947                    OpenIDProviderMissingAXResponseHandler):
948                response = self._render.errorPage(environ, start_response,
949                    'The site where you wish to signin requires '
950                    'additional information which this site isn\'t '
951                    'configured to provide.  Please report this fault to '
952                    'your site administrator.')
953                return response
954                   
955            except Exception, e:
956                log.error("Setting response following ID Approval: %s" % e)
957                response = self._render.errorPage(environ, start_response,
958                        'An error occurred setting additional parameters '
959                        'required by the site requesting your ID.  Please '
960                        'report this fault to your site administrator.')
961                return response
962
963            return self.oidResponse(response)
964        else:
965            return self._render.decidePage(environ, start_response, oidRequest)
966       
967       
968    def _identityIsAuthorized(self, oidRequest):
969        '''Check that a user is authorized i.e. does a session exist for their
970        username and if so does it correspond to the identity URL provided.
971        This last check doesn't apply for ID Select mode where No ID was input
972        at the Relying Party.
973       
974        @type oidRequest: openid.server.server.CheckIDRequest
975        @param oidRequest: OpenID Request object
976        @rtype: bool
977        @return: True/False is user authorized
978        '''
979        username = self.session.get('username')
980        if username is None:
981            return False
982
983        if oidRequest.idSelect():
984            log.debug("OpenIDProviderMiddleware._identityIsAuthorized - "
985                      "ID Select mode set but user is already logged in")
986            return True
987       
988        identifiers = self._authN.username2UserIdentifiers(self.environ,
989                                                           username)
990
991        # Take two passes to allow for yadis and non-based discovery
992        identityURIs = [self.createIdentityURI(self.urls['url_id'], i)
993                        for i in identifiers]
994       
995        identityURIs += [self.createIdentityURI(self.urls['url_yadis'], i)
996                         for i in identifiers]
997       
998        if oidRequest.identity not in identityURIs:
999            log.debug("OpenIDProviderMiddleware._identityIsAuthorized - "
1000                      "user is already logged in with a different ID=%s" % \
1001                      username)
1002            return False
1003       
1004        log.debug("OpenIDProviderMiddleware._identityIsAuthorized - "
1005                  "user is logged in with ID matching ID URI")
1006        return True
1007   
1008   
1009    def _trustRootIsAuthorized(self, trust_root):
1010        '''Return True/False for the given trust root (Relying Party)
1011        previously been approved by the user
1012       
1013        @type trust_root: dict
1014        @param trust_root: keyed by trusted root (Relying Party) URL and
1015        containing string item 'always' if approved
1016        @rtype: bool
1017        @return: True - trust has already been approved, False - trust root is
1018        not approved'''
1019        approvedRoots = self.session.get('approved', {})
1020        return approvedRoots.get(trust_root) is not None
1021
1022
1023    def _addSRegResponse(self, oidRequest, oidResponse):
1024        '''Add Simple Registration attributes to response to Relying Party
1025       
1026        @type oidRequest: openid.server.server.CheckIDRequest
1027        @param oidRequest: OpenID Check ID Request object
1028        @type oidResponse: openid.server.server.OpenIDResponse
1029        @param oidResponse: OpenID response object'''
1030       
1031        if self.sregResponseHandler is None:
1032            # No Simple Registration response object was set
1033            return
1034       
1035        sreg_req = sreg.SRegRequest.fromOpenIDRequest(oidRequest)
1036
1037        # Callout to external callable sets additional user attributes to be
1038        # returned in response to Relying Party       
1039        sreg_data = self.sregResponseHandler(self.session.get('username'))
1040        sreg_resp = sreg.SRegResponse.extractResponse(sreg_req, sreg_data)
1041        oidResponse.addExtension(sreg_resp)
1042
1043
1044    def _addAXResponse(self, oidRequest, oidResponse):
1045        '''Add attributes to response based on the OpenID Attribute Exchange
1046        interface
1047       
1048        @type oidRequest: openid.server.server.CheckIDRequest
1049        @param oidRequest: OpenID Check ID Request object
1050        @type oidResponse: openid.server.server.OpenIDResponse
1051        @param oidResponse: OpenID response object'''
1052
1053
1054        ax_req = ax.FetchRequest.fromOpenIDRequest(oidRequest)
1055        if ax_req is None:
1056            log.debug("No Attribute Exchange extension set in request")
1057            return
1058       
1059        ax_resp = ax.FetchResponse(request=ax_req)
1060       
1061        if self.axResponse is None:
1062            requiredAttr = ax_req.getRequiredAttrs()
1063            if len(requiredAttr) > 0:
1064                msg = ("Relying party requires these attributes: %s; but No"
1065                       "Attribute exchange handler 'axResponseHandler' has "
1066                       "been set" % requiredAttr)
1067                log.error(msg)
1068                raise OpenIDProviderMissingAXResponseHandler(msg)
1069           
1070            return
1071       
1072        # Set requested values - need user intervention here to confirm
1073        # release of attributes + assignment based on required attributes -
1074        # possibly via FetchRequest.getRequiredAttrs()
1075        try:
1076            self.axResponse(ax_req, ax_resp, self._authN)
1077           
1078        except OpenIDProviderMissingRequiredAXAttrs, e:
1079            log.error("OpenID Provider is unable to set the AX attributes "
1080                      "required by the Relying Party's request: %s" % e)
1081            raise
1082       
1083        except Exception, e:
1084            log.error("%s exception raised setting requested Attribute "
1085                      "Exchange values: %s" % (e.__class__, e))
1086            raise
1087       
1088        oidResponse.addExtension(ax_resp)
1089       
1090       
1091    def _identityApprovedPostProcessing(self, oidRequest, identifier=None):
1092        '''Action following approval of a Relying Party by the user.  Add
1093        Simple Registration and/or Attribute Exchange parameters if handlers
1094        were specified - See _addSRegResponse and _addAXResponse methods - and
1095        only if the Relying Party has requested them
1096       
1097        @type oidRequest: openid.server.server.CheckIDRequest
1098        @param oidRequest: OpenID Check ID Request object
1099        @type identifier: basestring
1100        @param identifier: OpenID selected by user - for ID Select mode only
1101        @rtype: openid.server.server.OpenIDResponse
1102        @return: OpenID response object'''
1103
1104        oidResponse = oidRequest.answer(True, identity=identifier)
1105        self._addSRegResponse(oidRequest, oidResponse)
1106        self._addAXResponse(oidRequest, oidResponse)
1107       
1108        return oidResponse
1109
1110
1111    def _handleCheckIDRequest(self, oidRequest):
1112        """Handle "checkid_immediate" and "checkid_setup" type requests from
1113        Relying Party
1114       
1115        @type oidRequest: openid.server.server.CheckIDRequest
1116        @param oidRequest: OpenID Check ID request
1117        @rtype: basestring
1118        @return: WSGI response
1119        """
1120        log.debug("OpenIDProviderMiddleware._handleCheckIDRequest ...")
1121       
1122        # Save request
1123        self.session['lastCheckIDRequest'] = oidRequest
1124        self.session.save()
1125       
1126        if self._identityIsAuthorized(oidRequest):
1127           
1128            # User is logged in - check for ID Select type request i.e. the
1129            # user entered their IdP address at the Relying Party and not their
1130            # OpenID Identifier.  In this case, the identity they wish to use
1131            # must be confirmed.
1132            if oidRequest.idSelect():
1133                # OpenID identifier must be confirmed
1134                return self.do_decide(self.environ, self.start_response)
1135           
1136            elif self._trustRootIsAuthorized(oidRequest.trust_root):
1137                # User has approved this Relying Party
1138                try:
1139                    oidResponse = self._identityApprovedPostProcessing(
1140                                                                    oidRequest)
1141                except (OpenIDProviderMissingRequiredAXAttrs,
1142                        OpenIDProviderMissingAXResponseHandler):
1143                    response = self._render.errorPage(environ, start_response,
1144                        'The site where you wish to signin requires '
1145                        'additional information which this site isn\'t '
1146                        'configured to provide.  Please report this fault to '
1147                        'your site administrator.')
1148                    return response
1149                   
1150                except Exception, e:
1151                    log.error("Setting response following ID Approval: %s" % e)
1152                    response = self._render.errorPage(environ, start_response,
1153                        'An error occurred setting additional parameters '
1154                        'required by the site requesting your ID.  Please '
1155                        'report this fault to your site administrator.')
1156                    return response
1157               
1158                return self._displayResponse(oidResponse)
1159            else:
1160                return self.do_decide(self.environ, self.start_response)
1161               
1162        elif oidRequest.immediate:
1163            oidResponse = oidRequest.answer(False)
1164            return self._displayResponse(oidResponse)
1165       
1166        else:
1167            # User is not logged in
1168           
1169            # Call login and if successful then call decide page to confirm
1170            # user wishes to trust the Relying Party.
1171            response = self.do_login(self.environ,
1172                                     self.start_response,
1173                                     success_to=self.urls['url_decide'])
1174            return response
1175
1176
1177    def _displayResponse(self, oidResponse):
1178        """Serialize an OpenID Response object, set headers and return WSGI
1179        response.
1180       
1181        If the URL length for a GET request exceeds a maximum, then convert the
1182        response into a HTML form and use POST method.
1183       
1184        @type oidResponse: openid.server.server.OpenIDResponse
1185        @param oidResponse: OpenID response object
1186       
1187        @rtype: basestring
1188        @return: WSGI response'''
1189        """
1190       
1191        try:
1192            webresponse = self.oidserver.encodeResponse(oidResponse)
1193        except server.EncodingError, why:
1194            text = why.response.encodeToKVForm()
1195            return self.showErrorPage(text)
1196       
1197        hdr = webresponse.headers.items()
1198       
1199        # If the content length exceeds the maximum to represent on a URL, it's
1200        # rendered as a form instead
1201        # FIXME: Commented out oidResponse.renderAsForm() test as it doesn't
1202        # give consistent answers.  Testing based on body content should work
1203        # OK
1204        if webresponse.body:
1205        #if oidResponse.renderAsForm():
1206            # Wrap in HTML with Javascript OnLoad to submit the form
1207            # automatically without user intervention
1208            response = OpenIDProviderMiddleware.formRespWrapperTmpl % \
1209                                                        webresponse.body
1210        else:
1211            response = webresponse.body
1212           
1213        hdr += [('Content-type', 'text/html' + self.charset),
1214                ('Content-length', str(len(response)))]
1215           
1216        self.start_response('%d %s' % (webresponse.code,
1217                                       httplib.responses[webresponse.code]),
1218                            hdr)
1219        return response
1220
1221
1222    def _redirect(self, start_response, url):
1223        """Do a HTTP 302 redirect
1224       
1225        @type start_response: builtin_function_or_method
1226        @param start_response: WSGI start response callable
1227        @type url: basestring
1228        @param url: URL to redirect to
1229        @rtype: list
1230        @return: empty HTML body
1231        """
1232        start_response('302 %s' % httplib.responses[302],
1233                       [('Content-type', 'text/html' + self.charset),
1234                        ('Location', url)])
1235        return []
1236   
1237   
1238class RenderingInterfaceError(Exception):
1239    """Base class for RenderingInterface exceptions
1240   
1241    A standard message is raised set by the msg class variable but the actual
1242    exception details are logged to the error log.  The use of a standard
1243    message enables callers to use its content for user error messages.
1244   
1245    @type msg: basestring
1246    @cvar msg: standard message to be raised for this exception"""
1247    userMsg = ("An internal error occurred with the page layout,  Please "
1248               "contact your system administrator")
1249    errorMsg = "RenderingInterface error"
1250   
1251    def __init__(self, *arg, **kw):
1252        if len(arg) > 0:
1253            msg = arg[0]
1254        else:
1255            msg = self.__class__.errorMsg
1256           
1257        log.error(msg)
1258        Exception.__init__(self, msg, **kw)
1259       
1260class RenderingInterfaceInitError(RenderingInterfaceError):
1261    """Error with initialisation of RenderingInterface.  Raise from __init__"""
1262    errorMsg = "RenderingInterface initialisation error"
1263   
1264class RenderingInterfaceConfigError(RenderingInterfaceError):
1265    """Error with configuration settings.  Raise from __init__"""
1266    errorMsg = "RenderingInterface configuration error"   
1267   
1268class RenderingInterface(object):
1269    """Interface class for rendering of OpenID Provider pages.  It implements
1270    methods for handling Yadis requests only.  All other interface methods
1271    return a 404 error response.  Create a derivative from this class to
1272    implement the other rendering methods as required.  DemoRenderingInterface
1273    provides an example of how to do this.  To apply a custom
1274    RenderingInterface class pass it's name in the OpenIDProviderMiddleware
1275    app_conf dict or as a keyword argument using the option name
1276    renderingClass.
1277   
1278    @cvar tmplServerYadis: template for returning Yadis document to Relying
1279    Party.  Derived classes can reset this or completely override the
1280    serverYadis method.
1281   
1282    @type tmplServerYadis: basestring
1283   
1284    @cvar tmplYadis: template for returning Yadis document containing user
1285    URL to Relying Party.  Derived classes can reset this or completely
1286    override the yadis method.
1287   
1288    @type tmplYadis: basestring"""
1289   
1290    tmplServerYadis = """\
1291<?xml version="1.0" encoding="UTF-8"?>
1292<xrds:XRDS
1293    xmlns:xrds="xri://$xrds"
1294    xmlns="xri://$xrd*($v*2.0)">
1295  <XRD>
1296
1297    <Service priority="0">
1298      <Type>%(openid20type)s</Type>
1299      <URI>%(endpoint_url)s</URI>
1300    </Service>
1301
1302  </XRD>
1303</xrds:XRDS>
1304"""
1305
1306    tmplYadis = """\
1307<?xml version="1.0" encoding="UTF-8"?>
1308<xrds:XRDS
1309    xmlns:xrds="xri://$xrds"
1310    xmlns="xri://$xrd*($v*2.0)">
1311  <XRD>
1312
1313    <Service priority="0">
1314      <Type>%(openid20type)s</Type>
1315      <Type>%(openid10type)s</Type>
1316      <URI>%(endpoint_url)s</URI>
1317      <LocalID>%(user_url)s</LocalID>
1318    </Service>
1319
1320  </XRD>
1321</xrds:XRDS>"""   
1322   
1323    def __init__(self, authN, base_url, urls, **opt):
1324        """
1325        @type authN: AuthNInterface
1326        @param param: reference to authentication interface to enable OpenID
1327        user URL construction from username
1328        @type base_url: basestring
1329        @param base_url: base URL for OpenID Provider to which individual paths
1330        are appended
1331        @type urls: dict
1332        @param urls: full urls for all the paths used by all the exposed
1333        methods - keyed by method name - see OpenIDProviderMiddleware.paths
1334        @type opt: dict
1335        @param opt: additional custom options passed from the
1336        OpenIDProviderMiddleware config
1337        """
1338        self._authN = authN
1339        self.base_url = base_url
1340        self.urls = urls
1341        self.charset = ''
1342   
1343   
1344    def serverYadis(self, environ, start_response):
1345        '''Render Yadis info for ID Select mode request
1346       
1347        @type environ: dict
1348        @param environ: dictionary of environment variables
1349        @type start_response: callable
1350        @param start_response: WSGI start response function.  Should be called
1351        from this method to set the response code and HTTP header content
1352        @rtype: basestring
1353        @return: WSGI response
1354        '''
1355        endpoint_url = self.urls['url_openidserver']
1356        response = RenderingInterface.tmplServerYadis % \
1357                                {'openid20type': discover.OPENID_IDP_2_0_TYPE,
1358                                 'endpoint_url': endpoint_url}
1359             
1360        start_response("200 OK",
1361                       [('Content-type', 'application/xrds+xml'),
1362                        ('Content-length', str(len(response)))])
1363        return response
1364
1365
1366    def yadis(self, environ, start_response):
1367        """Render Yadis document containing user URL
1368       
1369        @type environ: dict
1370        @param environ: dictionary of environment variables
1371        @type start_response: callable
1372        @param start_response: WSGI start response function.  Should be called
1373        from this method to set the response code and HTTP header content
1374        @rtype: basestring
1375        @return: WSGI response
1376        """
1377        # Override this method to implement an alternate means to derive the
1378        # username identifier
1379        userIdentifier = OpenIDProviderMiddleware.parseIdentityURI(
1380                                                    environ['PATH_INFO'])[ - 1]
1381       
1382        endpoint_url = self.urls['url_openidserver']
1383        user_url = self.urls['url_id'] + '/' + userIdentifier
1384       
1385        yadisDict = dict(openid20type=discover.OPENID_2_0_TYPE,
1386                         openid10type=discover.OPENID_1_0_TYPE,
1387                         endpoint_url=endpoint_url,
1388                         user_url=user_url)
1389       
1390        response = RenderingInterface.tmplYadis % yadisDict
1391     
1392        start_response('200 OK',
1393                       [('Content-type', 'application/xrds+xml' + self.charset),
1394                        ('Content-length', str(len(response)))])
1395        return response
1396   
1397
1398    def identityPage(self, environ, start_response):
1399        """Render the identity page.
1400       
1401        @type environ: dict
1402        @param environ: dictionary of environment variables
1403        @type start_response: callable
1404        @param start_response: WSGI start response function.  Should be called
1405        from this method to set the response code and HTTP header content
1406        @rtype: basestring
1407        @return: WSGI response
1408        """
1409        response = "Page is not implemented"
1410        start_response('%d %s' % (404, httplib.responses[code]),
1411                       [('Content-type', 'text/html' + self.charset),
1412                        ('Content-length', str(len(response)))])
1413        return response
1414   
1415       
1416    def login(self, environ, start_response,
1417              success_to=None, fail_to=None, msg=''):
1418        """Render the login form.
1419       
1420        @type environ: dict
1421        @param environ: dictionary of environment variables
1422        @type start_response: callable
1423        @param start_response: WSGI start response function.  Should be called
1424        from this method to set the response code and HTTP header content
1425        @type success_to: basestring
1426        @param success_to: URL put into hidden field telling 
1427        OpenIDProviderMiddleware.do_loginsubmit() where to forward to on
1428        successful login
1429        @type fail_to: basestring
1430        @param fail_to: URL put into hidden field telling 
1431        OpenIDProviderMiddleware.do_loginsubmit() where to forward to on
1432        login error
1433        @type msg: basestring
1434        @param msg: display (error) message below login form e.g. following
1435        previous failed login attempt.
1436        @rtype: basestring
1437        @return: WSGI response
1438        """
1439       
1440        response = "Page is not implemented"
1441        start_response('%d %s' % (404, httplib.responses[code]),
1442                       [('Content-type', 'text/html' + self.charset),
1443                        ('Content-length', str(len(response)))])
1444        return response
1445
1446
1447    def mainPage(self, environ, start_response):
1448        """Rendering the main page.
1449       
1450        @type environ: dict
1451        @param environ: dictionary of environment variables
1452        @type start_response: callable
1453        @param start_response: WSGI start response function.  Should be called
1454        from this method to set the response code and HTTP header content
1455        @rtype: basestring
1456        @return: WSGI response
1457        """   
1458        response = "Page is not implemented"
1459        start_response('%d %s' % (404, httplib.responses[code]),
1460                       [('Content-type', 'text/html' + self.charset),
1461                        ('Content-length', str(len(response)))])
1462        return response
1463   
1464
1465    def decidePage(self, environ, start_response, oidRequest):
1466        """Show page giving the user the option to approve the return of their
1467        credentials to the Relying Party.  This page is also displayed for
1468        ID select mode if the user is already logged in at the OpenID Provider.
1469        This enables them to confirm the OpenID to be sent back to the
1470        Relying Party
1471
1472        These fields should be posted by this page ready for
1473        OpenIdProviderMiddleware.do_allow to process:
1474       
1475        'Yes'/'No': for return authentication details back to the RP or
1476        abort return to RP respectively
1477        'remember': remember the decision corresponding to the above 'Yes'
1478        /'No'.
1479        This may be set to 'Yes' or 'No'
1480        'identity': set to the user's identity URL.  This usually is not
1481        required since it can be obtained from oidRequest.identity attribute
1482        but in ID Select mode, the identity URL will have been selected or set
1483        here.
1484       
1485       
1486        @type environ: dict
1487        @param environ: dictionary of environment variables
1488        @type start_response: callable
1489        @param start_response: WSGI start response function.  Should be called
1490        from this method to set the response code and HTTP header content
1491        @type oidRequest: openid.server.server.CheckIDRequest
1492        @param oidRequest: OpenID Check ID Request object
1493        @rtype: basestring
1494        @return: WSGI response
1495        """
1496        response = "Page is not implemented"
1497        start_response('%d %s' % (404, httplib.responses[code]),
1498                       [('Content-type', 'text/html' + self.charset),
1499                        ('Content-length', str(len(response)))])
1500        return response
1501
1502
1503    def errorPage(self, environ, start_response, msg, code=500):
1504        """Display error page
1505       
1506        @type environ: dict
1507        @param environ: dictionary of environment variables
1508        @type start_response: callable
1509        @param start_response: WSGI start response function.  Should be called
1510        from this method to set the response code and HTTP header content
1511        @type msg: basestring
1512        @param msg: optional message for page body
1513        @type code: int
1514        @param code: HTTP Error code to return
1515        @rtype: basestring
1516        @return: WSGI response
1517        """     
1518        response = "Page is not implemented"
1519        start_response('%d %s' % (404, httplib.responses[code]),
1520                       [('Content-type', 'text/html' + self.charset),
1521                        ('Content-length', str(len(response)))])
1522        return response
1523       
Note: See TracBrowser for help on using the repository browser.