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

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

First working version of an OpenID Provider as opposed to a Relying Party as avail. with AuthKit?. The code is taken from the HTTPServer example in the Python OpenID package and refactored into WSGI middleware.

  • ndg.security.server.wsgi.openid_provider - WSGI middleware package
  • Tests/openid-provider/op: pylons project test harness for the above

TODO: integrate into AuthKit? and Beaker Session Middleware as required.

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