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

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

Begun work abstracting out rendering to enable custom Kid templating.

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