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

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@5189
Revision 5189, 62.7 KB checked in by pjkersha, 13 years ago (diff)

Removed ndg.security.server.wsgi.openid.provider.AuthNInterfaceCtx. This is not needed for passing info between login, logout and AX Response calls. AbstractAuthNInterface derived type can perform the same task.

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