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

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

ndg.security.server.wsgi.openid_provider: fixes for setting custom paths
ndg.security.server.pylons.container: generic pylons project to contain security services: OpenID Provider, Attribute Authority and Session Manager

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    tmplYadis = """\
224<?xml version="1.0" encoding="UTF-8"?>
225<xrds:XRDS
226    xmlns:xrds="xri://$xrds"
227    xmlns="xri://$xrd*($v*2.0)">
228  <XRD>
229
230    <Service priority="0">
231      <Type>%(openid20type)s</Type>
232      <Type>%(openid10type)s</Type>
233      <URI>%(endpoint_url)s</URI>
234      <LocalID>%(user_url)s</LocalID>
235    </Service>
236
237  </XRD>
238</xrds:XRDS>"""
239
240    def do_yadis(self, environ, start_response):
241        """Generate Yadis document"""
242
243        username = self.path.split('/')[-1]
244       
245        endpoint_url = self.urls['url_openidserver']
246        user_url = self.urls['url_id'] + '/' + username
247       
248        yadisDict = dict(openid20type=discover.OPENID_2_0_TYPE, 
249                         openid10type=discover.OPENID_1_0_TYPE,
250                         endpoint_url=endpoint_url, 
251                         user_url=user_url)
252       
253        response = OpenIDProviderMiddleware.tmplYadis % yadisDict
254     
255        start_response('200 OK',
256                    [('Content-type', 'application/xrds+xml' + self.charset),
257                     ('Content-length', str(len(response)))])
258        return response
259
260
261    def do_openidserver(self, environ, start_response):
262        """Handle OpenID Server Request"""
263
264        try:
265            oidRequest = self.oidserver.decodeRequest(self.query)
266           
267        except server.ProtocolError, why:
268            response = self._displayResponse(why)
269           
270        else:
271            if oidRequest is None:
272                # Display text indicating that this is an endpoint.
273                response = self.do_mainpage(environ, start_response)
274               
275            # Check mode is one of "checkid_immediate", "checkid_setup"
276            if oidRequest.mode in server.BROWSER_REQUEST_MODES:
277                response = self._handleCheckIDRequest(oidRequest)
278            else:
279                oidResponse = self.oidserver.handleRequest(oidRequest)
280                response = self._displayResponse(oidResponse)
281           
282        return response
283           
284
285    def do_allow(self, environ, start_response):
286        """Handle allow request - user allow credentials to be passed back to
287        the Relying Party?"""
288       
289        oidRequest = self.session.get('lastCheckIDRequest')
290
291        if 'Yes' in self.query:
292            if oidRequest.idSelect():
293                identity = self.urls['url_id']+'/'+self.session['username']
294            else:
295                identity = oidRequest.identity
296
297            trust_root = oidRequest.trust_root
298            if self.query.get('remember', 'no') == 'yes':
299                self.session['approved'] = {trust_root: 'always'}
300                self.session.save()
301               
302            oidResponse = self._identityApproved(oidRequest, identity)
303            return self._displayResponse(oidResponse)
304       
305        elif 'No' in self.query:
306            # TODO: Check 'no' response is OK - no causes AuthKit's Relying
307            # Party implementation to crash with 'openid.return_to' KeyError
308            # in Authkit.authenticate.open_id.process
309            oidResponse = oidRequest.answer(False)
310            return self._displayResponse(oidResponse)
311            #response = self._renderer.renderMainPage(environ)
312
313        else:
314            raise OpenIDProviderMiddlewareError('Expecting yes/no in allow '
315                                                'post.  %r' % self.query)
316
317   
318    tmplServerYadis = """\
319<?xml version="1.0" encoding="UTF-8"?>
320<xrds:XRDS
321    xmlns:xrds="xri://$xrds"
322    xmlns="xri://$xrd*($v*2.0)">
323  <XRD>
324
325    <Service priority="0">
326      <Type>%(openid20type)s</Type>
327      <URI>%(endpoint_url)s</URI>
328    </Service>
329
330  </XRD>
331</xrds:XRDS>
332"""
333
334    def do_serveryadis(self, environ, start_response):
335        """Handle Server Yadis call"""
336        start_response("200 OK", [('Content-type', 'application/xrds+xml')])
337       
338        endpoint_url = self.urls['url_openidserver']
339        return [OpenIDProviderMiddleware.tmplServerYadis % \
340                {'openid20type': discover.OPENID_IDP_2_0_TYPE, 
341                 'endpoint_url': endpoint_url}]
342
343
344    def do_login(self, environ, start_response, **kw):
345        """Display Login form"""
346       
347        response = self._renderer.renderLogin(environ, **kw)
348        start_response('200 OK', 
349                       [('Content-type', 'text/html'+self.charset),
350                        ('Content-length', str(len(response)))])
351        return response
352
353
354    def do_loginsubmit(self, environ, start_response):
355        """Handle user submission from login and logout"""
356       
357        if 'submit' in self.query:
358            if 'username' in self.query:
359                # login
360                if 'username' in self.session:
361                    log.error("Attempting login for user %s: user %s is "
362                              "already logged in", self.session['username'],
363                              self.session['username'])
364                    return self._redirect(start_response,self.query['fail_to'])
365                   
366                self.session['username'] = self.query['username']
367                self.session['approved'] = {}
368                self.session.save()
369            else:
370                # logout
371                if 'username' not in self.session:
372                    log.error("No user is logged in")
373                    return self._redirect(start_response,self.query['fail_to'])
374               
375                del self.session['username']
376                self.session.pop('approved', None)
377                self.session.save()
378               
379            return self._redirect(start_response, self.query['success_to'])
380       
381        elif 'cancel' in self.query:
382            return self._redirect(start_response, self.query['fail_to'])
383        else:
384            log.error('Login input not recognised %r' % self.query)
385            return self._redirect(start_response, self.query['fail_to'])
386           
387
388    def do_mainpage(self, environ, start_response):
389
390        response = self._renderer.renderMainPage(environ)
391        start_response('200 OK', 
392                       [('Content-type', 'text/html'+self.charset),
393                        ('Content-length', str(len(response)))])
394        return response
395
396
397    def do_decide(self, environ, start_response):
398        """Display page prompting the user to decide whether to trust the site
399        requesting their credentials"""
400
401        oidRequest = self.session.get('lastCheckIDRequest')
402        if oidRequest is None:
403            log.error("No OpenID request set in session")
404            return self.do_mainpage(environ, start_response)
405       
406        approvedRoots = self.session.get('approved', {})
407       
408        if oidRequest.trust_root in approvedRoots and \
409           not oidRequest.idSelect():
410            response = self._identityApproved(oidRequest, oidRequest.identity)
411            return self._displayResponse(response)
412        else:
413            response = self._renderer.renderDecidePage(environ, oidRequest)
414           
415            start_response('200 OK', 
416                           [('Content-type', 'text/html'+self.charset),
417                            ('Content-length', str(len(response)))])
418            return response
419       
420       
421    def _identityIsAuthorized(self, oidRequest):
422        '''The given identity URL matches with a logged in user'''
423
424        username = self.session.get('username')
425        if username is None:
426            return False
427
428        if oidRequest.idSelect():
429            log.debug("OpenIDProviderMiddleware._identityIsAuthorized - "
430                      "ID Select mode set but user is already logged in")
431            return True
432       
433        identityURL = self.urls['url_id']+'/'+username
434        if oidRequest.identity != identityURL:
435            log.debug("OpenIDProviderMiddleware._identityIsAuthorized - "
436                      "user is already logged in with a different ID=%s" % \
437                      username)
438            return False
439       
440        log.debug("OpenIDProviderMiddleware._identityIsAuthorized - "
441                  "user is logged in with ID matching ID URL")
442        return True
443   
444   
445    def _trustRootIsAuthorized(self, trust_root):
446        '''The given trust root (Relying Party) has previously been approved
447        by the user'''
448        approvedRoots = self.session.get('approved', {})
449        return approvedRoots.get(trust_root) is not None
450
451
452    def _addSRegResponse(self, request, response):
453        '''Add additional attributes to response to Relying Party'''
454        if self.getSRegData is None:
455            return
456       
457        sreg_req = sreg.SRegRequest.fromOpenIDRequest(request)
458
459        # Callout to external callable sets additional user attributes to be
460        # returned in response to Relying Party       
461        sreg_data = self.getSRegData(self.session.get('username'))
462
463        sreg_resp = sreg.SRegResponse.extractResponse(sreg_req, sreg_data)
464        response.addExtension(sreg_resp)
465
466
467    def _identityApproved(self, request, identifier=None):
468        '''Action following approval of a Relying Party by the user'''
469        response = request.answer(True, identity=identifier)
470        self._addSRegResponse(request, response)
471        return response
472
473
474    def _handleCheckIDRequest(self, oidRequest):
475       
476        log.debug("OpenIDProviderMiddleware._handleCheckIDRequest ...")
477
478        if self._identityIsAuthorized(oidRequest):
479           
480            # User is logged in - check for ID Select type request i.e. the
481            # user entered their IdP address at the Relying Party and not their
482            # OpenID Identifier.  In this case, the identity they wish to use
483            # must be confirmed.
484            if oidRequest.idSelect():
485                # OpenID identifier must be confirmed
486                return self.do_decide(self.environ, self.start_response)
487           
488            elif self._trustRootIsAuthorized(oidRequest.trust_root):
489                # User has approved this Relying Party
490                oidResponse = self._identityApproved(oidRequest)
491                return self._displayResponse(oidResponse)
492            else:
493                return self.do_decide(self.environ, self.start_response)
494               
495        elif oidRequest.immediate:
496            oidResponse = oidRequest.answer(False)
497            return self._displayResponse(oidResponse)
498       
499        else:
500            # User is not logged in - save request
501            self.session['lastCheckIDRequest'] = oidRequest
502            self.session.save()
503           
504            # Call login and if successful then call decide page to confirm
505            # user wishes to trust the Relying Party.
506            response = self.do_login(self.environ,
507                                     self.start_response,
508                                     success_to=self.urls['url_decide'])
509            return response
510
511
512    def _displayResponse(self, response):
513        try:
514            webresponse = self.oidserver.encodeResponse(response)
515        except server.EncodingError, why:
516            text = why.response.encodeToKVForm()
517            return self.showErrorPage(text)
518       
519        hdr = webresponse.headers.items()
520       
521        lenWebResponseBody = len(webresponse.body)
522        if lenWebResponseBody:
523            response = webresponse.body
524        else:
525            response = []
526           
527        hdr += [('Content-length', str(lenWebResponseBody))]
528           
529        self.start_response('%d %s' % (webresponse.code, 
530                                       httplib.responses[webresponse.code]), 
531                            hdr)
532        return response
533
534
535    def _redirect(self, start_response, url):
536        start_response('302 %s' % httplib.responses[302], 
537                       [('Content-type', 'text/html'+self.charset),
538                        ('Location', url)])
539        return []
540
541
542    def _showErrorPage(self, msg, code=500):
543        response = self._renderer.renderErrorPage(self.environ,cgi.escape(msg))
544        self.start_response('%d %s' % (code, httplib.responses[code]), 
545                            [('Content-type', 'text/html'+self.charset),
546                             ('Content-length', str(len(msg)))])
547        return response
548   
549   
550class RenderingInterface(object):
551    """Interface class for rendering of OpenID Provider pages.  Create a
552    derivative from this class to override the default look and feel and
553    behaviour of these pages.  Pass the new class name via the renderClass
554    keyword to OpenIDProviderMiddleware.__init__"""
555   
556    def __init__(self, base_url, urls):
557        self.base_url = base_url
558        self.urls = urls
559
560    def renderIdentityPage(self, environ):
561        """Render the identity page."""
562        path = environ.get('PATH_INFO').rstrip('/')
563        username = path[len(self.paths['path_id'])+1:]
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.