source: TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/openid_provider.py @ 4126

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg-security/TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/openid_provider.py@4126
Revision 4126, 33.6 KB checked in by pjkersha, 12 years ago (diff)

ndg.security.server.wsgi.openid_provider: move Yadis rendering into RenderingInterface? class.

Line 
1"""NDG Security OpenID Provider Middleware
2
3Compliments AuthKit OpenID Middleware used for OpenID *Relying Party*
4
5NERC Data Grid Project
6
7This software may be distributed under the terms of the Q Public License,
8version 1.0 or later.
9"""
10__author__ = "P J Kershaw"
11__date__ = "01/08/08"
12__copyright__ = "(C) 2008 STFC & NERC"
13__contact__ = "P.J.Kershaw@rl.ac.uk"
14__revision__ = "$Id$"
15
16import logging
17log = logging.getLogger(__name__)
18_debugLevel = log.getEffectiveLevel() <= logging.DEBUG
19
20import paste.request
21from paste.util.import_string import eval_import
22
23from authkit.authenticate import AuthKitConfigError
24
25from openid.extensions import sreg
26from openid.server import server
27from openid.store.filestore import FileOpenIDStore
28from openid.consumer import discover
29
30import httplib
31import sys
32import cgi
33quoteattr = lambda s: '"%s"' % cgi.escape(s, 1)
34getInvalidKw = lambda kw: [k for k in kw if k not in \
35                           OpenIDProviderMiddleware.defKw]
36
37class OpenIDProviderMiddlewareError(Exception):
38    """OpenID Provider WSGI Middleware Error"""
39   
40class OpenIDProviderMiddleware(object):
41    """WSGI Middleware to implement an OpenID Provider"""
42
43    defKw = dict(path_openidserver='/openidserver',
44               path_login='/login',
45               path_loginsubmit='/loginsubmit',
46               path_id='/id',
47               path_yadis='/yadis',
48               path_serveryadis='/serveryadis',
49               path_allow='/allow',
50               path_decide='/decide',
51               path_mainpage='/',
52               session_middleware='beaker.session', 
53               base_url='',
54               consumer_store_dirpath='./',
55               charset=None,
56               trace=False,
57               renderingClass=None,
58               getSRegData=None)
59   
60    defPaths=dict([(k,v) for k,v in defKw.items() if k.startswith('path_')])
61     
62    def __init__(self, app, app_conf=None, prefix='openid_provider.', **kw):
63        '''       
64        @type app_conf: dict       
65        @param app_conf: PasteDeploy application configuration dictionary
66        '''
67
68        opt = OpenIDProviderMiddleware.defKw.copy()
69        kw2AppConfOpt = {}
70        if app_conf is not None:
71            # Update from application config dictionary - filter from using
72            # prefix
73            for k,v in app_conf.items():
74                if k.startswith(prefix):
75                    subK = k.replace(prefix, '')                   
76                    filtK = '_'.join(subK.split('.'))                   
77                    opt[filtK] = v
78                    kw2AppConfOpt[filtK] = k
79                   
80            invalidOpt = getInvalidKw(opt)
81            if len(invalidOpt) > 0:
82                raise TypeError("Unexpected app_conf option(s): %s" % \
83                        (", ".join([kw2AppConfOpt[i] for i in invalidOpt])))
84           
85            # Convert from string type where required   
86            opt['charset'] = eval(opt.get('charset', 'None'))     
87            opt['trace'] = bool(eval(opt.get('trace', 'False'))) 
88             
89            renderingClassVal = opt.get('renderingClass', None)     
90            if renderingClassVal:
91                opt['renderingClass'] = eval_import(renderingClassVal)
92           
93            getSRegDataVal = opt.get('getSRegData', None) 
94            if getSRegDataVal:
95                opt['getSRegData'] = eval_import(getSRegDataVal) 
96            else:
97                 opt['getSRegData'] = None
98                     
99        invalidKw = getInvalidKw(kw)
100        if len(invalidKw) > 0:
101            raise TypeError("Unexpected keyword(s): %s" % ", ".join(invalidKw))
102       
103        # Update options from keywords - matching app_conf ones will be
104        # overwritten
105        opt.update(kw)
106        log.debug("opt=%r", opt)       
107
108        # Paths relative to base URL - Nb. remove trailing '/'
109        self.paths = dict([(k, opt[k].rstrip('/')) \
110                           for k in OpenIDProviderMiddleware.defPaths])
111       
112        if not opt['base_url']:
113            raise TypeError("base_url is not set")
114       
115        self.base_url = opt['base_url']
116
117        # Full Paths
118        self.urls = dict([(k.replace('path_', 'url_'), self.base_url+v) \
119                          for k,v in self.paths.items()])
120
121        self.method = dict([(v, k.replace('path_', 'do_')) \
122                            for k,v in self.paths.items()])
123
124        self.session_middleware = opt['session_middleware']
125
126        if opt['charset'] is None:
127            self.charset = ''
128        else:
129            self.charset = '; charset='+charset
130       
131        # If True and debug log level is set display content of response
132        self._trace = opt['trace']
133       
134        # Pages can be customised by setting external rendering interface
135        # class
136        renderingClass = opt.get('renderingClass', None) or RenderingInterface         
137        if not issubclass(renderingClass, RenderingInterface):
138            raise OpenIDProviderMiddlewareError("Rendering interface "
139                                                "class %r is not a %r "
140                                                "derived type" % \
141                                                (renderingClass, 
142                                                 RenderingInterface))
143
144        try:
145            self._renderer = renderingClass(self.base_url, self.urls)
146        except Exception, e:
147            log.error("Error instantiating rendering interface...")
148            raise
149           
150        # Callable for setting of additional attributes in HTTP header of
151        # response to Relying Party
152        self.getSRegData = opt.get('getSRegData', None)
153        if self.getSRegData and not callable(self.getSRegData):
154            raise OpenIDProviderMiddlewareError("Expecting callable for "
155                                                "getSRegData keyword, got %r"%\
156                                                self.getSRegData)
157       
158        self.app = app
159       
160        # Instantiate OpenID consumer store and OpenID consumer.  If you
161        # were connecting to a database, you would create the database
162        # connection and instantiate an appropriate store here.
163        store = FileOpenIDStore(opt['consumer_store_dirpath'])
164        self.oidserver = server.Server(store, self.urls['url_openidserver'])
165
166   
167    def __call__(self, environ, start_response):
168        if not environ.has_key(self.session_middleware):
169            raise AuthKitConfigError(
170                'The session middleware %r is not present. '
171                'Have you set up the session middleware?'%(
172                    self.session_middleware
173                )
174            )
175
176        self.path = environ.get('PATH_INFO').rstrip('/')
177        self.environ = environ
178        self.start_response = start_response
179        self.session = environ[self.session_middleware]
180        self._renderer.session = self.session
181       
182        if self.path in (self.paths['path_id'], self.paths['path_yadis']):
183            log.debug("No user id given in URL %s" % self.path)
184           
185            # Disallow identifier and yadis URIs where no ID was specified
186            return self.app(environ, start_response)
187           
188        elif self.path.startswith(self.paths['path_id']) or \
189           self.path.startswith(self.paths['path_yadis']):
190           
191            # Match against path minus ID as this is not known in advance           
192            pathMatch = self.path[:self.path.rfind('/')]
193        else:
194            pathMatch = self.path
195           
196        if pathMatch in self.method:
197            self.query = dict(paste.request.parse_formvars(environ)) 
198            log.debug("Calling method %s ..." % self.method[pathMatch]) 
199           
200            action = getattr(self, self.method[pathMatch])
201            response = action(environ, start_response) 
202            if self._trace and _debugLevel:
203                if isinstance(response, list):
204                    log.debug('Output for %s:\n%s', self.method[pathMatch],
205                                                    ''.join(response))
206                else:
207                    log.debug('Output for %s:\n%s', self.method[pathMatch],
208                                                    response)
209                   
210            return response
211        else:
212            log.debug("No match for path %s" % self.path)
213            return self.app(environ, start_response)
214
215
216    def do_id(self, environ, start_response):
217        '''Handle ID request'''
218        response = self._renderer.renderIdentityPage(environ)
219
220        start_response("200 OK", [('Content-length', str(len(response)))])
221        return response
222
223
224    def do_yadis(self, environ, start_response):
225        """Generate Yadis document"""
226        response = self._renderer.renderYadis(environ)
227     
228        start_response('200 OK',
229                       [('Content-type', 'application/xrds+xml'+self.charset),
230                        ('Content-length', str(len(response)))])
231        return response
232
233
234    def do_openidserver(self, environ, start_response):
235        """Handle OpenID Server Request"""
236
237        try:
238            oidRequest = self.oidserver.decodeRequest(self.query)
239           
240        except server.ProtocolError, why:
241            response = self._displayResponse(why)
242           
243        else:
244            if oidRequest is None:
245                # Display text indicating that this is an endpoint.
246                response = self.do_mainpage(environ, start_response)
247               
248            # Check mode is one of "checkid_immediate", "checkid_setup"
249            if oidRequest.mode in server.BROWSER_REQUEST_MODES:
250                response = self._handleCheckIDRequest(oidRequest)
251            else:
252                oidResponse = self.oidserver.handleRequest(oidRequest)
253                response = self._displayResponse(oidResponse)
254           
255        return response
256           
257
258    def do_allow(self, environ, start_response):
259        """Handle allow request - user allow credentials to be passed back to
260        the Relying Party?"""
261       
262        oidRequest = self.session.get('lastCheckIDRequest')
263
264        if 'Yes' in self.query:
265            if oidRequest.idSelect():
266                identity = self.urls['url_id']+'/'+self.session['username']
267            else:
268                identity = oidRequest.identity
269
270            trust_root = oidRequest.trust_root
271            if self.query.get('remember', 'no') == 'yes':
272                self.session['approved'] = {trust_root: 'always'}
273                self.session.save()
274               
275            oidResponse = self._identityApproved(oidRequest, identity)
276            return self._displayResponse(oidResponse)
277       
278        elif 'No' in self.query:
279            # TODO: Check 'no' response is OK - no causes AuthKit's Relying
280            # Party implementation to crash with 'openid.return_to' KeyError
281            # in Authkit.authenticate.open_id.process
282            oidResponse = oidRequest.answer(False)
283            return self._displayResponse(oidResponse)
284            #response = self._renderer.renderMainPage(environ)
285
286        else:
287            raise OpenIDProviderMiddlewareError('Expecting yes/no in allow '
288                                                'post.  %r' % self.query)
289
290   
291    tmplServerYadis = """\
292<?xml version="1.0" encoding="UTF-8"?>
293<xrds:XRDS
294    xmlns:xrds="xri://$xrds"
295    xmlns="xri://$xrd*($v*2.0)">
296  <XRD>
297
298    <Service priority="0">
299      <Type>%(openid20type)s</Type>
300      <URI>%(endpoint_url)s</URI>
301    </Service>
302
303  </XRD>
304</xrds:XRDS>
305"""
306
307    def do_serveryadis(self, environ, start_response):
308        """Handle Server Yadis call"""
309        start_response("200 OK", [('Content-type', 'application/xrds+xml')])
310       
311        endpoint_url = self.urls['url_openidserver']
312        return [OpenIDProviderMiddleware.tmplServerYadis % \
313                {'openid20type': discover.OPENID_IDP_2_0_TYPE, 
314                 'endpoint_url': endpoint_url}]
315
316
317    def do_login(self, environ, start_response, **kw):
318        """Display Login form"""
319       
320        response = self._renderer.renderLogin(environ, **kw)
321        start_response('200 OK', 
322                       [('Content-type', 'text/html'+self.charset),
323                        ('Content-length', str(len(response)))])
324        return response
325
326
327    def do_loginsubmit(self, environ, start_response):
328        """Handle user submission from login and logout"""
329       
330        if 'submit' in self.query:
331            if 'username' in self.query:
332                # login
333                if 'username' in self.session:
334                    log.error("Attempting login for user %s: user %s is "
335                              "already logged in", self.session['username'],
336                              self.session['username'])
337                    return self._redirect(start_response,self.query['fail_to'])
338                   
339                self.session['username'] = self.query['username']
340                self.session['approved'] = {}
341                self.session.save()
342            else:
343                # logout
344                if 'username' not in self.session:
345                    log.error("No user is logged in")
346                    return self._redirect(start_response,self.query['fail_to'])
347               
348                del self.session['username']
349                self.session.pop('approved', None)
350                self.session.save()
351               
352            return self._redirect(start_response, self.query['success_to'])
353       
354        elif 'cancel' in self.query:
355            return self._redirect(start_response, self.query['fail_to'])
356        else:
357            log.error('Login input not recognised %r' % self.query)
358            return self._redirect(start_response, self.query['fail_to'])
359           
360
361    def do_mainpage(self, environ, start_response):
362
363        response = self._renderer.renderMainPage(environ)
364        start_response('200 OK', 
365                       [('Content-type', 'text/html'+self.charset),
366                        ('Content-length', str(len(response)))])
367        return response
368
369
370    def do_decide(self, environ, start_response):
371        """Display page prompting the user to decide whether to trust the site
372        requesting their credentials"""
373
374        oidRequest = self.session.get('lastCheckIDRequest')
375        if oidRequest is None:
376            log.error("No OpenID request set in session")
377            return self.do_mainpage(environ, start_response)
378       
379        approvedRoots = self.session.get('approved', {})
380       
381        if oidRequest.trust_root in approvedRoots and \
382           not oidRequest.idSelect():
383            response = self._identityApproved(oidRequest, oidRequest.identity)
384            return self._displayResponse(response)
385        else:
386            response = self._renderer.renderDecidePage(environ, oidRequest)
387           
388            start_response('200 OK', 
389                           [('Content-type', 'text/html'+self.charset),
390                            ('Content-length', str(len(response)))])
391            return response
392       
393       
394    def _identityIsAuthorized(self, oidRequest):
395        '''The given identity URL matches with a logged in user'''
396
397        username = self.session.get('username')
398        if username is None:
399            return False
400
401        if oidRequest.idSelect():
402            log.debug("OpenIDProviderMiddleware._identityIsAuthorized - "
403                      "ID Select mode set but user is already logged in")
404            return True
405       
406        identityURL = self.urls['url_id']+'/'+username
407        if oidRequest.identity != identityURL:
408            log.debug("OpenIDProviderMiddleware._identityIsAuthorized - "
409                      "user is already logged in with a different ID=%s" % \
410                      username)
411            return False
412       
413        log.debug("OpenIDProviderMiddleware._identityIsAuthorized - "
414                  "user is logged in with ID matching ID URL")
415        return True
416   
417   
418    def _trustRootIsAuthorized(self, trust_root):
419        '''The given trust root (Relying Party) has previously been approved
420        by the user'''
421        approvedRoots = self.session.get('approved', {})
422        return approvedRoots.get(trust_root) is not None
423
424
425    def _addSRegResponse(self, request, response):
426        '''Add additional attributes to response to Relying Party'''
427        if self.getSRegData is None:
428            return
429       
430        sreg_req = sreg.SRegRequest.fromOpenIDRequest(request)
431
432        # Callout to external callable sets additional user attributes to be
433        # returned in response to Relying Party       
434        sreg_data = self.getSRegData(self.session.get('username'))
435
436        sreg_resp = sreg.SRegResponse.extractResponse(sreg_req, sreg_data)
437        response.addExtension(sreg_resp)
438
439
440    def _identityApproved(self, request, identifier=None):
441        '''Action following approval of a Relying Party by the user'''
442        response = request.answer(True, identity=identifier)
443        self._addSRegResponse(request, response)
444        return response
445
446
447    def _handleCheckIDRequest(self, oidRequest):
448       
449        log.debug("OpenIDProviderMiddleware._handleCheckIDRequest ...")
450
451        if self._identityIsAuthorized(oidRequest):
452           
453            # User is logged in - check for ID Select type request i.e. the
454            # user entered their IdP address at the Relying Party and not their
455            # OpenID Identifier.  In this case, the identity they wish to use
456            # must be confirmed.
457            if oidRequest.idSelect():
458                # OpenID identifier must be confirmed
459                return self.do_decide(self.environ, self.start_response)
460           
461            elif self._trustRootIsAuthorized(oidRequest.trust_root):
462                # User has approved this Relying Party
463                oidResponse = self._identityApproved(oidRequest)
464                return self._displayResponse(oidResponse)
465            else:
466                return self.do_decide(self.environ, self.start_response)
467               
468        elif oidRequest.immediate:
469            oidResponse = oidRequest.answer(False)
470            return self._displayResponse(oidResponse)
471       
472        else:
473            # User is not logged in - save request
474            self.session['lastCheckIDRequest'] = oidRequest
475            self.session.save()
476           
477            # Call login and if successful then call decide page to confirm
478            # user wishes to trust the Relying Party.
479            response = self.do_login(self.environ,
480                                     self.start_response,
481                                     success_to=self.urls['url_decide'])
482            return response
483
484
485    def _displayResponse(self, response):
486        try:
487            webresponse = self.oidserver.encodeResponse(response)
488        except server.EncodingError, why:
489            text = why.response.encodeToKVForm()
490            return self.showErrorPage(text)
491       
492        hdr = webresponse.headers.items()
493       
494        lenWebResponseBody = len(webresponse.body)
495        if lenWebResponseBody:
496            response = webresponse.body
497        else:
498            response = []
499           
500        hdr += [('Content-length', str(lenWebResponseBody))]
501           
502        self.start_response('%d %s' % (webresponse.code, 
503                                       httplib.responses[webresponse.code]), 
504                            hdr)
505        return response
506
507
508    def _redirect(self, start_response, url):
509        start_response('302 %s' % httplib.responses[302], 
510                       [('Content-type', 'text/html'+self.charset),
511                        ('Location', url)])
512        return []
513
514
515    def _showErrorPage(self, msg, code=500):
516        response = self._renderer.renderErrorPage(self.environ,cgi.escape(msg))
517        self.start_response('%d %s' % (code, httplib.responses[code]), 
518                            [('Content-type', 'text/html'+self.charset),
519                             ('Content-length', str(len(msg)))])
520        return response
521   
522   
523class RenderingInterface(object):
524    """Interface class for rendering of OpenID Provider pages.  Create a
525    derivative from this class to override the default look and feel and
526    behaviour of these pages.  Pass the new class name via the renderClass
527    keyword to OpenIDProviderMiddleware.__init__"""
528   
529    def __init__(self, base_url, urls):
530        self.base_url = base_url
531        self.urls = urls
532
533    def renderIdentityPage(self, environ):
534        """Render the identity page."""
535        path = environ.get('PATH_INFO').rstrip('/')
536        username = path[len(self.paths['path_id'])+1:]
537       
538        link_tag = '<link rel="openid.server" href="%s">' % \
539              self.urls['url_openidserver']
540             
541        yadis_loc_tag = '<meta http-equiv="x-xrds-location" content="%s">'%\
542            (self.urls['url_yadis']+'/'+path[4:])
543           
544        disco_tags = link_tag + yadis_loc_tag
545        ident = self.base_url + path
546
547        msg = ''
548        return self._showPage(environ, 
549                              'Identity Page', 
550                              head_extras=disco_tags, 
551                              msg='''<p>This is the identity page for %s.</p>
552                                %s
553                                ''' % (ident, msg))
554       
555    tmplYadis = """\
556<?xml version="1.0" encoding="UTF-8"?>
557<xrds:XRDS
558    xmlns:xrds="xri://$xrds"
559    xmlns="xri://$xrd*($v*2.0)">
560  <XRD>
561
562    <Service priority="0">
563      <Type>%(openid20type)s</Type>
564      <Type>%(openid10type)s</Type>
565      <URI>%(endpoint_url)s</URI>
566      <LocalID>%(user_url)s</LocalID>
567    </Service>
568
569  </XRD>
570</xrds:XRDS>"""   
571   
572    def renderYadis(self, environ):
573        """Render Yadis document"""
574        username = environ['PATH_INFO'].rstrip('/').split('/')[-1]
575       
576        endpoint_url = self.urls['url_openidserver']
577        user_url = self.urls['url_id'] + '/' + username
578       
579        yadisDict = dict(openid20type=discover.OPENID_2_0_TYPE, 
580                         openid10type=discover.OPENID_1_0_TYPE,
581                         endpoint_url=endpoint_url, 
582                         user_url=user_url)
583       
584        return RenderingInterface.tmplYadis % yadisDict
585
586       
587    def renderLogin(self, environ, success_to=None, fail_to=None):
588        """Render the login form."""
589       
590        if success_to is None:
591            success_to = self.urls['url_mainpage']
592           
593        if fail_to is None:
594            fail_to = self.urls['url_mainpage']
595       
596        return self._showPage(environ,
597                              'Login Page', form='''\
598            <h2>Login</h2>
599            <form method="GET" action="%s">
600              <input type="hidden" name="success_to" value="%s" />
601              <input type="hidden" name="fail_to" value="%s" />
602              <input type="text" name="username" value="" />
603              <input type="submit" name="submit" value="Log In" />
604              <input type="submit" name="cancel" value="Cancel" />
605            </form>
606            ''' % (self.urls['url_loginsubmit'], success_to, fail_to))
607
608
609    def renderMainPage(self, environ):
610        """Rendering the main page."""
611       
612        yadis_tag = '<meta http-equiv="x-xrds-location" content="%s">' % \
613                    self.urls['url_serveryadis']
614        username = environ['beaker.session']['username']   
615        if username:
616            openid_url = self.urls['url_id'] + '/' + username
617            user_message = """\
618            <p>You are logged in as %s. Your OpenID identity URL is
619            <tt><a href=%s>%s</a></tt>. Enter that URL at an OpenID
620            consumer to test this server.</p>
621            """ % (username, quoteattr(openid_url), openid_url)
622        else:
623            user_message = "<p>You are not <a href='%s'>logged in</a>.</p>" % \
624                            self.urls['url_login']
625
626        return self._showPage(environ,
627                              'Main Page', head_extras=yadis_tag, msg='''\
628            <p>OpenID server</p>
629   
630            %s
631   
632            <p>The URL for this server is <a href=%s><tt>%s</tt></a>.</p>
633        ''' % (user_message, quoteattr(self.base_url), self.base_url))
634   
635    def renderDecidePage(self, environ, oidRequest):
636        id_url_base = self.urls['url_id'] + '/'
637       
638        # XXX: This may break if there are any synonyms for id_url_base,
639        # such as referring to it by IP address or a CNAME.
640       
641        # TODO: OpenID 2.0 Allows oidRequest.identity to be set to
642        # http://specs.openid.net/auth/2.0/identifier_select.  See,
643        # http://openid.net/specs/openid-authentication-2_0.html.  This code
644        # implements this overriding the behaviour of the example code on
645        # which this is based.  - Check is the example code based on OpenID 1.0
646        # and therefore wrong for this behaviour?
647#        assert oidRequest.identity.startswith(id_url_base), \
648#               repr((oidRequest.identity, id_url_base))
649        expected_user = oidRequest.identity[len(id_url_base):]
650        username = environ['beaker.session']['username']
651       
652        if oidRequest.idSelect(): # We are being asked to select an ID
653            msg = '''\
654            <p>A site has asked for your identity.  You may select an
655            identifier by which you would like this site to know you.
656            On a production site this would likely be a drop down list
657            of pre-created accounts or have the facility to generate
658            a random anonymous identifier.
659            </p>
660            '''
661            fdata = {
662                'path_allow': self.urls['url_allow'],
663                'id_url_base': id_url_base,
664                'trust_root': oidRequest.trust_root,
665                }
666            form = '''\
667            <form method="POST" action="%(path_allow)s">
668            <table>
669              <tr><td>Identity:</td>
670                 <td>%(id_url_base)s<input type='text' name='identifier'></td></tr>
671              <tr><td>Trust Root:</td><td>%(trust_root)s</td></tr>
672            </table>
673            <p>Allow this authentication to proceed?</p>
674            <input type="checkbox" id="remember" name="remember" value="yes"
675                /><label for="remember">Remember this
676                decision</label><br />
677            <input type="submit" name="yes" value="yes" />
678            <input type="submit" name="no" value="no" />
679            </form>
680            '''%fdata
681           
682        elif expected_user == username:
683            msg = '''\
684            <p>A new site has asked to confirm your identity.  If you
685            approve, the site represented by the trust root below will
686            be told that you control identity URL listed below. (If
687            you are using a delegated identity, the site will take
688            care of reversing the delegation on its own.)</p>'''
689
690            fdata = {
691                'path_allow': self.urls['url_allow'],
692                'identity': oidRequest.identity,
693                'trust_root': oidRequest.trust_root,
694                }
695            form = '''\
696            <table>
697              <tr><td>Identity:</td><td>%(identity)s</td></tr>
698              <tr><td>Trust Root:</td><td>%(trust_root)s</td></tr>
699            </table>
700            <p>Allow this authentication to proceed?</p>
701            <form method="POST" action="%(path_allow)s">
702              <input type="checkbox" id="remember" name="remember" value="yes"
703                  /><label for="remember">Remember this
704                  decision</label><br />
705              <input type="submit" name="yes" value="yes" />
706              <input type="submit" name="no" value="no" />
707            </form>''' % fdata
708        else:
709            mdata = {
710                'expected_user': expected_user,
711                'username': username,
712                }
713            msg = '''\
714            <p>A site has asked for an identity belonging to
715            %(expected_user)s, but you are logged in as %(username)s.  To
716            log in as %(expected_user)s and approve the login oidRequest,
717            hit OK below.  The "Remember this decision" checkbox
718            applies only to the trust root decision.</p>''' % mdata
719
720            fdata = {
721                'path_allow': self.urls['url_allow'],
722                'identity': oidRequest.identity,
723                'trust_root': oidRequest.trust_root,
724                'expected_user': expected_user,
725                }
726            form = '''\
727            <table>
728              <tr><td>Identity:</td><td>%(identity)s</td></tr>
729              <tr><td>Trust Root:</td><td>%(trust_root)s</td></tr>
730            </table>
731            <p>Allow this authentication to proceed?</p>
732            <form method="POST" action="%(path_allow)s">
733              <input type="checkbox" id="remember" name="remember" value="yes"
734                  /><label for="remember">Remember this
735                  decision</label><br />
736              <input type="hidden" name="login_as" value="%(expected_user)s"/>
737              <input type="submit" name="yes" value="yes" />
738              <input type="submit" name="no" value="no" />
739            </form>''' % fdata
740
741        return self._showPage(environ,
742                              'Approve OpenID request?', 
743                              msg=msg, form=form)
744       
745
746    def _showPage(self, environ, 
747                  title, head_extras='', msg=None, err=None, form=None):
748
749        username = environ['beaker.session'].get('username')
750        if username is None:
751            user_link = '<a href="/login">not logged in</a>.'
752        else:
753            user_link = 'logged in as <a href="%s/%s">%s</a>.<br />'\
754                        '<a href="%s?submit=true&'\
755                        'success_to=%s">Log out</a>' % \
756                        (self.urls['url_id'], username, username, 
757                         self.urls['url_loginsubmit'],
758                         self.urls['url_login'])
759
760        body = ''
761
762        if err is not None:
763            body +=  '''\
764            <div class="error">
765              %s
766            </div>
767            ''' % err
768
769        if msg is not None:
770            body += '''\
771            <div class="message">
772              %s
773            </div>
774            ''' % msg
775
776        if form is not None:
777            body += '''\
778            <div class="form">
779              %s
780            </div>
781            ''' % form
782
783        contents = {
784            'title': 'Python OpenID Server - ' + title,
785            'head_extras': head_extras,
786            'body': body,
787            'user_link': user_link,
788            }
789
790        response = '''<html>
791  <head>
792    <title>%(title)s</title>
793    %(head_extras)s
794  </head>
795  <style type="text/css">
796      h1 a:link {
797          color: black;
798          text-decoration: none;
799      }
800      h1 a:visited {
801          color: black;
802          text-decoration: none;
803      }
804      h1 a:hover {
805          text-decoration: underline;
806      }
807      body {
808        font-family: verdana,sans-serif;
809        width: 50em;
810        margin: 1em;
811      }
812      div {
813        padding: .5em;
814      }
815      table {
816        margin: none;
817        padding: none;
818      }
819      .banner {
820        padding: none 1em 1em 1em;
821        width: 100%%;
822      }
823      .leftbanner {
824        text-align: left;
825      }
826      .rightbanner {
827        text-align: right;
828        font-size: smaller;
829      }
830      .error {
831        border: 1px solid #ff0000;
832        background: #ffaaaa;
833        margin: .5em;
834      }
835      .message {
836        border: 1px solid #2233ff;
837        background: #eeeeff;
838        margin: .5em;
839      }
840      .form {
841        border: 1px solid #777777;
842        background: #ddddcc;
843        margin: .5em;
844        margin-top: 1em;
845        padding-bottom: 0em;
846      }
847      dd {
848        margin-bottom: 0.5em;
849      }
850  </style>
851  <body>
852    <table class="banner">
853      <tr>
854        <td class="leftbanner">
855          <h1><a href="/">Python OpenID Server</a></h1>
856        </td>
857        <td class="rightbanner">
858          You are %(user_link)s
859        </td>
860      </tr>
861    </table>
862%(body)s
863  </body>
864</html>
865''' % contents
866
867        return response
868
869    def renderErrorPage(self, environ, msg):
870        response = self._showPage(environ, 'Error Processing Request', err='''\
871        <p>%s</p>
872        <!--
873
874        This is a large comment.  It exists to make this page larger.
875        That is unfortunately necessary because of the "smart"
876        handling of pages returned with an error code in IE.
877
878        *************************************************************
879        *************************************************************
880        *************************************************************
881        *************************************************************
882        *************************************************************
883        *************************************************************
884        *************************************************************
885        *************************************************************
886        *************************************************************
887        *************************************************************
888        *************************************************************
889        *************************************************************
890        *************************************************************
891        *************************************************************
892        *************************************************************
893        *************************************************************
894        *************************************************************
895        *************************************************************
896        *************************************************************
897        *************************************************************
898        *************************************************************
899        *************************************************************
900        *************************************************************
901
902        -->
903        ''' % msg)
904       
905        return response
Note: See TracBrowser for help on using the repository browser.