source: TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/openid/provider/renderinginterface/demo.py @ 5087

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/openid/provider/renderinginterface/demo.py@5087
Revision 5087, 17.7 KB checked in by pjkersha, 12 years ago (diff)

Moved DemoRenderingInterface? from ndg.security.server.wsgi.openid.provider into it's own module ndg.security.server.wsgi.openid.provider.renderinginterface.demo

Line 
1"""NDG Security Demonstration Rendering Interface for OpenIDProviderMiddleware
2
3NERC DataGrid Project
4
5Moved from ndg.security.server.wsgi.openid.provider 10/03/09
6"""
7__author__ = "P J Kershaw"
8__date__ = "01/08/08"
9__copyright__ = "(C) 2009 Science and Technology Facilities Council"
10__contact__ = "Philip.Kershaw@stfc.ac.uk"
11__revision__ = "$Id$"
12__license__ = "BSD - see LICENSE file in top-level directory"
13import logging
14log = logging.getLogger(__name__)
15import httplib
16import cgi
17
18from ndg.security.server.wsgi.openid.provider import RenderingInterface
19
20quoteattr = lambda s: '"%s"' % cgi.escape(s, 1)
21
22class DemoRenderingInterface(RenderingInterface):
23    """Example rendering interface class for demonstration purposes"""
24   
25    def identityPage(self, environ, start_response):
26        """Render the identity page.
27       
28        @type environ: dict
29        @param environ: dictionary of environment variables
30        @type start_response: callable
31        @param start_response: WSGI start response function.  Should be called
32        from this method to set the response code and HTTP header content
33        @rtype: basestring
34        @return: WSGI response
35        """
36        path = environ.get('PATH_INFO').rstrip('/')
37        userIdentifier = path.split('/')[ - 1]
38       
39        link_tag = '<link rel="openid.server" href="%s">' % \
40              self.urls['url_openidserver']
41             
42        yadis_loc_tag = '<meta http-equiv="x-xrds-location" content="%s">' % \
43            (self.urls['url_yadis'] + '/' + userIdentifier)
44           
45        disco_tags = link_tag + yadis_loc_tag
46        ident = self.base_url + path
47
48        response = self._showPage(environ,
49                                  'Identity Page',
50                                  head_extras=disco_tags,
51                                  msg='<p>This is the identity page for %s.'
52                                      '</p>' % ident)
53       
54        start_response("200 OK",
55                       [('Content-type', 'text/html' + self.charset),
56                        ('Content-length', str(len(response)))])
57        return response
58   
59       
60    def login(self, environ, start_response,
61              success_to=None, fail_to=None, msg=''):
62        """Render the login form.
63       
64        @type environ: dict
65        @param environ: dictionary of environment variables
66        @type success_to: basestring
67        @param success_to: URL put into hidden field telling 
68        OpenIDProviderMiddleware.do_loginsubmit() where to forward to on
69        successful login
70        @type fail_to: basestring
71        @param fail_to: URL put into hidden field telling 
72        OpenIDProviderMiddleware.do_loginsubmit() where to forward to on
73        login error
74        @type msg: basestring
75        @param msg: display (error) message below login form e.g. following
76        previous failed login attempt.
77        @rtype: basestring
78        @return: WSGI response
79        """
80       
81        if success_to is None:
82            success_to = self.urls['url_mainpage']
83           
84        if fail_to is None:
85            fail_to = self.urls['url_mainpage']
86       
87        form = '''\
88<h2>Login</h2>
89<form method="GET" action="%s">
90  <input type="hidden" name="success_to" value="%s" />
91  <input type="hidden" name="fail_to" value="%s" />
92  <table cellspacing="0" border="0" cellpadding="5">
93    <tr>
94        <td>Username:</td>
95        <td><input type="text" name="username" value=""/></td>
96    </tr><tr>
97        <td>Password:</td>
98        <td><input type="password" name="password"/></td>
99    </tr><tr>
100        <td colspan="2" align="right">
101            <input type="submit" name="submit" value="Login"/>
102            <input type="submit" name="cancel" value="Cancel"/>
103        </td>
104    </tr>
105  </table>
106</form>
107%s
108''' % (self.urls['url_loginsubmit'], success_to, fail_to, msg)
109
110        response = self._showPage(environ, 'Login Page', form=form)
111        start_response('200 OK',
112                       [('Content-type', 'text/html' + self.charset),
113                        ('Content-length', str(len(response)))])
114        return response
115
116
117    def mainPage(self, environ, start_response):
118        """Rendering the main page.
119       
120        @type environ: dict
121        @param environ: dictionary of environment variables
122        @type start_response: callable
123        @param start_response: WSGI start response function.  Should be called
124        from this method to set the response code and HTTP header content
125        @rtype: basestring
126        @return: WSGI response
127        """
128       
129        yadis_tag = '<meta http-equiv="x-xrds-location" content="%s">' % \
130                    self.urls['url_serveryadis']
131        username = environ['beaker.session'].get('username')   
132        if username:
133            openid_url = self.urls['url_id'] + '/' + username
134            user_message = """\
135            <p>You are logged in as %s. Your OpenID identity URL is
136            <tt><a href=%s>%s</a></tt>. Enter that URL at an OpenID
137            consumer to test this server.</p>
138            """ % (username, quoteattr(openid_url), openid_url)
139        else:
140            user_message = "<p>You are not <a href='%s'>logged in</a>.</p>" % \
141                            self.urls['url_login']
142
143        msg = '''\
144<p>OpenID server</p>
145
146%s
147
148<p>The URL for this server is <a href=%s><tt>%s</tt></a>.</p>
149''' % (user_message, quoteattr(self.base_url), self.base_url)
150        response = self._showPage(environ,
151                                  'Main Page',
152                                  head_extras=yadis_tag,
153                                  msg=msg)
154   
155        start_response('200 OK',
156                       [('Content-type', 'text/html' + self.charset),
157                        ('Content-length', str(len(response)))])
158        return response
159   
160
161    def decidePage(self, environ, start_response, oidRequest):
162        """Show page giving the user the option to approve the return of their
163        credentials to the Relying Party.  This page is also displayed for
164        ID select mode if the user is already logged in at the OpenID Provider.
165        This enables them to confirm the OpenID to be sent back to the
166        Relying Party
167       
168        @type environ: dict
169        @param environ: dictionary of environment variables
170        @type start_response: callable
171        @param start_response: WSGI start response function.  Should be called
172        from this method to set the response code and HTTP header content
173        @type oidRequest: openid.server.server.CheckIDRequest
174        @param oidRequest: OpenID Check ID Request object
175        @rtype: basestring
176        @return: WSGI response
177        """
178        idURLBase = self.urls['url_id'] + '/'
179       
180        # XXX: This may break if there are any synonyms for idURLBase,
181        # such as referring to it by IP address or a CNAME.
182       
183        # TODO: OpenID 2.0 Allows oidRequest.identity to be set to
184        # http://specs.openid.net/auth/2.0/identifier_select.  See,
185        # http://openid.net/specs/openid-authentication-2_0.html.  This code
186        # implements this overriding the behaviour of the example code on
187        # which this is based.  - Check is the example code based on OpenID 1.0
188        # and therefore wrong for this behaviour?
189#        assert oidRequest.identity.startswith(idURLBase), \
190#               repr((oidRequest.identity, idURLBase))
191        userIdentifier = oidRequest.identity[len(idURLBase):]
192        username = environ['beaker.session']['username']
193       
194        if oidRequest.idSelect(): # We are being asked to select an ID
195            userIdentifier = self._authN.username2UserIdentifiers(environ,
196                                                                  username)[0]
197            identity = idURLBase + userIdentifier
198           
199            msg = '''\
200            <p>A site has asked for your identity.  You may select an
201            identifier by which you would like this site to know you.
202            On a production site this would likely be a drop down list
203            of pre-created accounts or have the facility to generate
204            a random anonymous identifier.
205            </p>
206            '''
207            fdata = {
208                'pathAllow': self.urls['url_allow'],
209                'identity': identity,
210                'trust_root': oidRequest.trust_root,
211                }
212            form = '''\
213<form method="POST" action="%(pathAllow)s">
214<table>
215  <tr><td>Identity:</td>
216     <td>%(identity)s</td></tr>
217  <tr><td>Trust Root:</td><td>%(trust_root)s</td></tr>
218</table>
219<p>Allow this authentication to proceed?</p>
220<input type="checkbox" id="remember" name="remember" value="Yes"
221    /><label for="remember">Remember this
222    decision</label><br />
223<input type="hidden" name="identity" value="%(identity)s" />
224<input type="submit" name="Yes" value="Yes" />
225<input type="submit" name="No" value="No" />
226</form>
227''' % fdata
228           
229        elif userIdentifier in self._authN.username2UserIdentifiers(environ,
230                                                                    username):
231            msg = '''\
232            <p>A new site has asked to confirm your identity.  If you
233            approve, the site represented by the trust root below will
234            be told that you control identity URL listed below. (If
235            you are using a delegated identity, the site will take
236            care of reversing the delegation on its own.)</p>'''
237
238            fdata = {
239                'pathAllow': self.urls['url_allow'],
240                'identity': oidRequest.identity,
241                'trust_root': oidRequest.trust_root,
242                }
243            form = '''\
244<table>
245  <tr><td>Identity:</td><td>%(identity)s</td></tr>
246  <tr><td>Trust Root:</td><td>%(trust_root)s</td></tr>
247</table>
248<p>Allow this authentication to proceed?</p>
249<form method="POST" action="%(pathAllow)s">
250  <input type="checkbox" id="remember" name="remember" value="Yes"
251      /><label for="remember">Remember this
252      decision</label><br />
253  <input type="submit" name="Yes" value="Yes" />
254  <input type="submit" name="No" value="No" />
255</form>''' % fdata
256        else:
257            mdata = {
258                'userIdentifier': userIdentifier,
259                'username': username,
260                }
261            msg = '''\
262            <p>A site has asked for an identity belonging to
263            %(userIdentifier)s, but you are logged in as %(username)s.  To
264            log in as %(userIdentifier)s and approve the login oidRequest,
265            hit OK below.  The "Remember this decision" checkbox
266            applies only to the trust root decision.</p>''' % mdata
267
268            fdata = {
269                'pathAllow': self.urls['url_allow'],
270                'identity': oidRequest.identity,
271                'trust_root': oidRequest.trust_root,
272                'username': username,
273                }
274            form = '''\
275<table>
276  <tr><td>Identity:</td><td>%(identity)s</td></tr>
277  <tr><td>Trust Root:</td><td>%(trust_root)s</td></tr>
278</table>
279<p>Allow this authentication to proceed?</p>
280<form method="POST" action="%(pathAllow)s">
281  <input type="checkbox" id="remember" name="remember" value="Yes"
282      /><label for="remember">Remember this
283      decision</label><br />
284  <input type="hidden" name="login_as" value="%(username)s"/>
285  <input type="submit" name="Yes" value="Yes" />
286  <input type="submit" name="No" value="No" />
287</form>''' % fdata
288
289        response = self._showPage(environ, 'Approve OpenID request?',
290                                  msg=msg, form=form)           
291        start_response('200 OK',
292                       [('Content-type', 'text/html' + self.charset),
293                        ('Content-length', str(len(response)))])
294        return response
295   
296
297    def _showPage(self,
298                  environ,
299                  title,
300                  head_extras='',
301                  msg=None,
302                  err=None,
303                  form=None):
304        """Generic page rendering method.  Derived classes may ignore this.
305       
306        @type environ: dict
307        @param environ: dictionary of environment variables
308        @type title: basestring
309        @param title: page title
310        @type head_extras: basestring
311        @param head_extras: add extra HTML header elements
312        @type msg: basestring
313        @param msg: optional message for page body
314        @type err: basestring
315        @param err: optional error message for page body
316        @type form: basestring
317        @param form: optional form for page body       
318        @rtype: basestring
319        @return: WSGI response
320        """
321       
322        username = environ['beaker.session'].get('username')
323        if username is None:
324            user_link = '<a href="/login">not logged in</a>.'
325        else:
326            user_link = 'logged in as <a href="%s/%s">%s</a>.<br />'\
327                        '<a href="%s?submit=true&'\
328                        'success_to=%s">Log out</a>' % \
329                        (self.urls['url_id'], username, username,
330                         self.urls['url_loginsubmit'],
331                         self.urls['url_login'])
332
333        body = ''
334
335        if err is not None:
336            body += '''\
337            <div class="error">
338              %s
339            </div>
340            ''' % err
341
342        if msg is not None:
343            body += '''\
344            <div class="message">
345              %s
346            </div>
347            ''' % msg
348
349        if form is not None:
350            body += '''\
351            <div class="form">
352              %s
353            </div>
354            ''' % form
355
356        contents = {
357            'title': 'Python OpenID Provider - ' + title,
358            'head_extras': head_extras,
359            'body': body,
360            'user_link': user_link,
361            }
362
363        response = '''<html>
364  <head>
365    <title>%(title)s</title>
366    %(head_extras)s
367  </head>
368  <style type="text/css">
369      h1 a:link {
370          color: black;
371          text-decoration: none;
372      }
373      h1 a:visited {
374          color: black;
375          text-decoration: none;
376      }
377      h1 a:hover {
378          text-decoration: underline;
379      }
380      body {
381        font-family: verdana,sans-serif;
382        width: 50em;
383        margin: 1em;
384      }
385      div {
386        padding: .5em;
387      }
388      table {
389        margin: none;
390        padding: none;
391      }
392      .banner {
393        padding: none 1em 1em 1em;
394        width: 100%%;
395      }
396      .leftbanner {
397        text-align: left;
398      }
399      .rightbanner {
400        text-align: right;
401        font-size: smaller;
402      }
403      .error {
404        border: 1px solid #ff0000;
405        background: #ffaaaa;
406        margin: .5em;
407      }
408      .message {
409        border: 1px solid #2233ff;
410        background: #eeeeff;
411        margin: .5em;
412      }
413      .form {
414        border: 1px solid #777777;
415        background: #ddddcc;
416        margin: .5em;
417        margin-top: 1em;
418        padding-bottom: 0em;
419      }
420      dd {
421        margin-bottom: 0.5em;
422      }
423  </style>
424  <body>
425    <table class="banner">
426      <tr>
427        <td class="leftbanner">
428          <h1><a href="/">Python OpenID Provider</a></h1>
429        </td>
430        <td class="rightbanner">
431          You are %(user_link)s
432        </td>
433      </tr>
434    </table>
435%(body)s
436  </body>
437</html>
438''' % contents
439
440        return response
441
442    def errorPage(self, environ, start_response, msg, code=500):
443        """Display error page
444       
445        @type environ: dict
446        @param environ: dictionary of environment variables
447        @type start_response: callable
448        @param start_response: WSGI start response function.  Should be called
449        from this method to set the response code and HTTP header content
450        @type msg: basestring
451        @param msg: optional message for page body
452        @rtype: basestring
453        @return: WSGI response
454        """
455       
456        response = self._showPage(environ, 'Error Processing Request', err='''\
457        <p>%s</p>
458        <!--
459
460        This is a large comment.  It exists to make this page larger.
461        That is unfortunately necessary because of the "smart"
462        handling of pages returned with an error code in IE.
463
464        *************************************************************
465        *************************************************************
466        *************************************************************
467        *************************************************************
468        *************************************************************
469        *************************************************************
470        *************************************************************
471        *************************************************************
472        *************************************************************
473        *************************************************************
474        *************************************************************
475        *************************************************************
476        *************************************************************
477        *************************************************************
478        *************************************************************
479        *************************************************************
480        *************************************************************
481        *************************************************************
482        *************************************************************
483        *************************************************************
484        *************************************************************
485        *************************************************************
486        *************************************************************
487
488        -->
489        ''' % msg)
490       
491        start_response('%d %s' % (code, httplib.responses[code]),
492                       [('Content-type', 'text/html' + self.charset),
493                        ('Content-length', str(len(response)))])
494        return response
Note: See TracBrowser for help on using the repository browser.