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

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/NDGSecurity/python/ndg_security_server/ndg/security/server/wsgi/openid/provider/renderinginterface/demo.py@7077
Revision 7077, 17.8 KB checked in by pjkersha, 9 years ago (diff)
  • Property svn:keywords set to Id
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, oidResponse):
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        @type oidResponse: openid.server.server.OpenIDResponse
176        @param oidResponse: OpenID response object
177        @rtype: basestring
178        @return: WSGI response
179        """
180        idURLBase = self.urls['url_id'] + '/'
181       
182        # XXX: This may break if there are any synonyms for idURLBase,
183        # such as referring to it by IP address or a CNAME.
184       
185        # TODO: OpenID 2.0 Allows oidRequest.identity to be set to
186        # http://specs.openid.net/auth/2.0/identifier_select.  See,
187        # http://openid.net/specs/openid-authentication-2_0.html.  This code
188        # implements this overriding the behaviour of the example code on
189        # which this is based.  - Check is the example code based on OpenID 1.0
190        # and therefore wrong for this behaviour?
191#        assert oidRequest.identity.startswith(idURLBase), \
192#               repr((oidRequest.identity, idURLBase))
193        userIdentifier = oidRequest.identity[len(idURLBase):]
194        username = environ['beaker.session']['username']
195       
196        if oidRequest.idSelect(): # We are being asked to select an ID
197            userIdentifier = self._authN.username2UserIdentifiers(environ,
198                                                                  username)[0]
199            identity = idURLBase + userIdentifier
200           
201            msg = '''\
202            <p>A site has asked for your identity.  You may select an
203            identifier by which you would like this site to know you.
204            On a production site this would likely be a drop down list
205            of pre-created accounts or have the facility to generate
206            a random anonymous identifier.
207            </p>
208            '''
209            fdata = {
210                'pathAllow': self.urls['url_allow'],
211                'identity': identity,
212                'trust_root': oidRequest.trust_root,
213                }
214            form = '''\
215<form method="POST" action="%(pathAllow)s">
216<table>
217  <tr><td>Identity:</td>
218     <td>%(identity)s</td></tr>
219  <tr><td>Trust Root:</td><td>%(trust_root)s</td></tr>
220</table>
221<p>Allow this authentication to proceed?</p>
222<input type="checkbox" id="remember" name="remember" value="Yes"
223    /><label for="remember">Remember this
224    decision</label><br />
225<input type="hidden" name="identity" value="%(identity)s" />
226<input type="submit" name="Yes" value="Yes" />
227<input type="submit" name="No" value="No" />
228</form>
229''' % fdata
230           
231        elif userIdentifier in self._authN.username2UserIdentifiers(environ,
232                                                                    username):
233            msg = '''\
234            <p>A new site has asked to confirm your identity.  If you
235            approve, the site represented by the trust root below will
236            be told that you control identity URL listed below. (If
237            you are using a delegated identity, the site will take
238            care of reversing the delegation on its own.)</p>'''
239
240            fdata = {
241                'pathAllow': self.urls['url_allow'],
242                'identity': oidRequest.identity,
243                'trust_root': oidRequest.trust_root,
244                }
245            form = '''\
246<table>
247  <tr><td>Identity:</td><td>%(identity)s</td></tr>
248  <tr><td>Trust Root:</td><td>%(trust_root)s</td></tr>
249</table>
250<p>Allow this authentication to proceed?</p>
251<form method="POST" action="%(pathAllow)s">
252  <input type="checkbox" id="remember" name="remember" value="Yes"
253      /><label for="remember">Remember this
254      decision</label><br />
255  <input type="submit" name="Yes" value="Yes" />
256  <input type="submit" name="No" value="No" />
257</form>''' % fdata
258        else:
259            mdata = {
260                'userIdentifier': userIdentifier,
261                'username': username,
262                }
263            msg = '''\
264            <p>A site has asked for an identity belonging to
265            %(userIdentifier)s, but you are logged in as %(username)s.  To
266            log in as %(userIdentifier)s and approve the login oidRequest,
267            hit OK below.  The "Remember this decision" checkbox
268            applies only to the trust root decision.</p>''' % mdata
269
270            fdata = {
271                'pathAllow': self.urls['url_allow'],
272                'identity': oidRequest.identity,
273                'trust_root': oidRequest.trust_root,
274                'username': username,
275                }
276            form = '''\
277<table>
278  <tr><td>Identity:</td><td>%(identity)s</td></tr>
279  <tr><td>Trust Root:</td><td>%(trust_root)s</td></tr>
280</table>
281<p>Allow this authentication to proceed?</p>
282<form method="POST" action="%(pathAllow)s">
283  <input type="checkbox" id="remember" name="remember" value="Yes"
284      /><label for="remember">Remember this
285      decision</label><br />
286  <input type="hidden" name="login_as" value="%(username)s"/>
287  <input type="submit" name="Yes" value="Yes" />
288  <input type="submit" name="No" value="No" />
289</form>''' % fdata
290
291        response = self._showPage(environ, 'Approve OpenID request?',
292                                  msg=msg, form=form)           
293        start_response('200 OK',
294                       [('Content-type', 'text/html' + self.charset),
295                        ('Content-length', str(len(response)))])
296        return response
297   
298
299    def _showPage(self,
300                  environ,
301                  title,
302                  head_extras='',
303                  msg=None,
304                  err=None,
305                  form=None):
306        """Generic page rendering method.  Derived classes may ignore this.
307       
308        @type environ: dict
309        @param environ: dictionary of environment variables
310        @type title: basestring
311        @param title: page title
312        @type head_extras: basestring
313        @param head_extras: add extra HTML header elements
314        @type msg: basestring
315        @param msg: optional message for page body
316        @type err: basestring
317        @param err: optional error message for page body
318        @type form: basestring
319        @param form: optional form for page body       
320        @rtype: basestring
321        @return: WSGI response
322        """
323       
324        username = environ['beaker.session'].get('username')
325        if username is None:
326            user_link = '<a href="/login">not logged in</a>.'
327        else:
328            user_link = 'logged in as <a href="%s/%s">%s</a>.<br />'\
329                        '<a href="%s?submit=true&'\
330                        'success_to=%s">Log out</a>' % \
331                        (self.urls['url_id'], username, username,
332                         self.urls['url_loginsubmit'],
333                         self.urls['url_login'])
334
335        body = ''
336
337        if err is not None:
338            body += '''\
339            <div class="error">
340              %s
341            </div>
342            ''' % err
343
344        if msg is not None:
345            body += '''\
346            <div class="message">
347              %s
348            </div>
349            ''' % msg
350
351        if form is not None:
352            body += '''\
353            <div class="form">
354              %s
355            </div>
356            ''' % form
357
358        contents = {
359            'title': 'Python OpenID Provider - ' + title,
360            'head_extras': head_extras,
361            'body': body,
362            'user_link': user_link,
363            }
364
365        response = '''<html>
366  <head>
367    <title>%(title)s</title>
368    %(head_extras)s
369  </head>
370  <style type="text/css">
371      h1 a:link {
372          color: black;
373          text-decoration: none;
374      }
375      h1 a:visited {
376          color: black;
377          text-decoration: none;
378      }
379      h1 a:hover {
380          text-decoration: underline;
381      }
382      body {
383        font-family: verdana,sans-serif;
384        width: 50em;
385        margin: 1em;
386      }
387      div {
388        padding: .5em;
389      }
390      table {
391        margin: none;
392        padding: none;
393      }
394      .banner {
395        padding: none 1em 1em 1em;
396        width: 100%%;
397      }
398      .leftbanner {
399        text-align: left;
400      }
401      .rightbanner {
402        text-align: right;
403        font-size: smaller;
404      }
405      .error {
406        border: 1px solid #ff0000;
407        background: #ffaaaa;
408        margin: .5em;
409      }
410      .message {
411        border: 1px solid #2233ff;
412        background: #eeeeff;
413        margin: .5em;
414      }
415      .form {
416        border: 1px solid #777777;
417        background: #ddddcc;
418        margin: .5em;
419        margin-top: 1em;
420        padding-bottom: 0em;
421      }
422      dd {
423        margin-bottom: 0.5em;
424      }
425  </style>
426  <body>
427    <table class="banner">
428      <tr>
429        <td class="leftbanner">
430          <h1><a href="/">Python OpenID Provider</a></h1>
431        </td>
432        <td class="rightbanner">
433          You are %(user_link)s
434        </td>
435      </tr>
436    </table>
437%(body)s
438  </body>
439</html>
440''' % contents
441
442        return response
443
444    def errorPage(self, environ, start_response, msg, code=500):
445        """Display error page
446       
447        @type environ: dict
448        @param environ: dictionary of environment variables
449        @type start_response: callable
450        @param start_response: WSGI start response function.  Should be called
451        from this method to set the response code and HTTP header content
452        @type msg: basestring
453        @param msg: optional message for page body
454        @rtype: basestring
455        @return: WSGI response
456        """
457       
458        response = self._showPage(environ, 'Error Processing Request', err='''\
459        <p>%s</p>
460        <!--
461
462        This is a large comment.  It exists to make this page larger.
463        That is unfortunately necessary because of the "smart"
464        handling of pages returned with an error code in IE.
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
490        -->
491        ''' % msg)
492       
493        start_response('%d %s' % (code, httplib.responses[code]),
494                       [('Content-type', 'text/html' + self.charset),
495                        ('Content-length', str(len(response)))])
496        return response
Note: See TracBrowser for help on using the repository browser.