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

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

Created ndg.security.server.pylons.container: generic pylons based container for NDG Security services. This is intended for OpenID Provider and Attribute Authority and Session Manager WSGI middleware. The latter two will replace Twisted based platform for these services.

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