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

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

ndg.security.server.wsgi.openid_provider: fixes for login and main page.

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