source: TI12-security/trunk/NDGSecurity/python/ndg_security_server/ndg/security/server/wsgi/openid/provider/renderinginterface/genshi/__init__.py @ 7292

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/genshi/__init__.py@7292
Revision 7292, 17.5 KB checked in by pjkersha, 10 years ago (diff)

Incomplete - task 12: ESG Yadis identity service discovery

  • Altered Python OpenID Provider adding support for custom XRDS document via Gensho templating. This enables service endpoints other than the default OpenID one to be advertised by the Yadis doc incl. the Attribute Service which is now required for ESG.
  • Property svn:keywords set to Id
Line 
1"""NDG Security Genshi based Rendering Interface for
2OpenIDProviderMiddleware
3
4NERC Data Grid Project
5"""
6__author__ = "P J Kershaw"
7__date__ = "14/08/08"
8__copyright__ = "(C) 2009 Science and Technology Facilities Council"
9__contact__ = "Philip.Kershaw@stfc.ac.uk"
10__revision__ = "$Id$"
11__license__ = "BSD - see LICENSE file in top-level directory"
12import logging
13log = logging.getLogger(__name__)
14
15import httplib
16from os import path
17
18from genshi.template import TemplateLoader
19from openid.consumer import discover
20from openid.server.server import CheckIDRequest, OpenIDResponse
21from openid.extensions import ax
22
23# Rendering classes for OpenID Provider must derive from generic render
24# interface
25from ndg.security.server.wsgi.openid.provider import (RenderingInterface, 
26    RenderingInterfaceConfigError)
27   
28from ndg.security.server.wsgi.openid.provider import OpenIDProviderMiddleware
29
30
31class GenshiRendering(RenderingInterface):
32    """Provide Templating for OpenID Provider Middleware using Genshi templating
33    """
34    PROPERTY_NAMES = (
35        'templateRootDir',
36        'baseURL',
37        'leftLogo',
38        'leftAlt',
39        'leftLink',
40        'leftImage',
41        'rightLink',
42        'rightImage',
43        'rightAlt',
44        'footerText',
45        'helpIcon',
46        'tmplServerYadis',
47        'tmplYadis'
48    )
49   
50    # Make a set of defaults with specific settings for the Yadis templates
51    # based on parent class class variables
52    PROPERTY_DEFAULTS = {}.fromkeys(PROPERTY_NAMES, '')
53    PROPERTY_DEFAULTS['tmplServerYadis'] = RenderingInterface.tmplServerYadis
54    PROPERTY_DEFAULTS['tmplYadis'] = RenderingInterface.tmplYadis
55   
56    ATTR_NAMES = (
57        'title', 
58        'heading',
59        'xml', 
60        'headExtras', 
61        'loginStatus',
62        'loader',
63        'session',
64        'success_to',
65        'fail_to',
66        'trust_root',
67        'environ',
68        'identityURI',
69        'oidRequest',
70        'oidResponse'
71    )
72    __slots__ = tuple(["__%s" % name for name in ATTR_NAMES])
73    del name
74    __slots__ += PROPERTY_NAMES
75       
76    LOGIN_TMPL_NAME = 'login.html'
77    DECIDE_PAGE_TMPL_NAME = 'decide.html'
78    MAIN_PAGE_TMPL_NAME = 'main.html'
79    ERROR_PAGE_TMPL_NAME = 'error.html'
80    SERVER_YADIS_TMPL_NAME = 'serveryadis.xml'
81    YADIS_TMPL_NAME = 'yadis.xml'
82   
83    # Approve and reject submit HTML input types for the Relying Party Approval
84    # page
85    APPROVE_RP_SUBMIT = OpenIDProviderMiddleware.APPROVE_RP_SUBMIT
86    REJECT_RP_SUBMIT = OpenIDProviderMiddleware.REJECT_RP_SUBMIT
87
88    DEFAULT_TEMPLATES_DIR = path.join(path.dirname(__file__), 'templates')
89
90   
91    def __init__(self, *arg, **opt):
92        '''Extend RenderingInterface to include config and set-up for Genshi
93        templating
94       
95        @type *arg: tuple
96        @param *arg: RenderingInterface parent class arguments
97        @type **opt: dict
98        @param **opt: additional keywords to set-up Genshi rendering'''
99        super(GenshiRendering, self).__init__(*arg, **opt)
100       
101        # Initialise attributes
102        for i in self.__class__.PROPERTY_NAMES:
103            setattr(self, i, self.__class__.PROPERTY_DEFAULTS[i])
104         
105        # Update from keywords   
106        for i in opt:
107            setattr(self, i, opt[i])
108
109        if not self.templateRootDir:
110            self.templateRootDir = GenshiRendering.DEFAULT_TEMPLATES_DIR
111         
112        self.__loader = TemplateLoader(self.templateRootDir, auto_reload=True)
113       
114        self.title = ''
115        self.heading = ''
116        self.xml = ''
117        self.headExtras = ''
118        self.loginStatus = True
119        self.session = ''
120        self.success_to = ''
121        self.fail_to = ''
122       
123        self.__oidRequest = None
124        self.__oidResponse = None
125        self.__identityURI = None
126        self.__environ = None
127        self.__trust_root = None
128
129    def getEnviron(self):
130        return self.__environ
131
132    def getIdentityURI(self):
133        return self.__identityURI
134
135    def setEnviron(self, value):
136        self.__environ = value
137
138    def setIdentityURI(self, value):
139        self.__identityURI = value
140
141    def getTrust_root(self):
142        return self.__trust_root
143
144    def getOidRequest(self):
145        return self.__oidRequest
146
147    def getOidResponse(self):
148        return self.__oidResponse
149
150    def setTrust_root(self, value):
151        if not isinstance(value, basestring):
152            raise TypeError('Expecting string type for trust_root attribute; '
153                            'got %r' % type(value))
154        self.__trust_root = value
155
156    def setOidRequest(self, value):
157        if not isinstance(value, CheckIDRequest):
158            raise TypeError('Expecting %r type for oidRequest attribute; '
159                            'got %r' % (CheckIDRequest, type(value)))
160        self.__oidRequest = value
161
162    def setOidResponse(self, value):
163        if not isinstance(value, OpenIDResponse):
164            raise TypeError('Expecting %r type for oidResponse attribute; '
165                            'got %r' % (OpenIDResponse, type(value)))
166        self.__oidResponse = value
167
168    def getSuccess_to(self):
169        return self.__success_to
170
171    def getFail_to(self):
172        return self.__fail_to
173
174    def setSuccess_to(self, value):
175        if not isinstance(value, basestring):
176            raise TypeError('Expecting string type for success_to attribute; '
177                            'got %r' % type(value))
178        self.__success_to = value
179
180    def setFail_to(self, value):
181        if not isinstance(value, basestring):
182            raise TypeError('Expecting string type for fail_to attribute; '
183                            'got %r' % type(value))
184        self.__fail_to = value
185
186    def getTitle(self):
187        return self.__title
188
189    def getHeading(self):
190        return self.__heading
191
192    def getXml(self):
193        return self.__xml
194
195    def getHeadExtras(self):
196        return self.__headExtras
197
198    def getLoginStatus(self):
199        return self.__loginStatus
200
201    def getSession(self):
202        return self.__session
203   
204    def setTitle(self, value):
205        if not isinstance(value, basestring):
206            raise TypeError('Expecting string type for title attribute; '
207                            'got %r' % type(value))
208        self.__title = value
209   
210    def setHeading(self, value):
211        if not isinstance(value, basestring):
212            raise TypeError('Expecting string type for heading attribute; '
213                            'got %r' % type(value))
214        self.__heading = value
215
216    def setXml(self, value):
217        if not isinstance(value, basestring):
218            raise TypeError('Expecting string type for xml attribute; '
219                            'got %r' % type(value))
220        self.__xml = value
221
222    def setHeadExtras(self, value):
223        if not isinstance(value, basestring):
224            raise TypeError('Expecting string type for headExtras attribute; '
225                            'got %r' % type(value))
226        self.__headExtras = value
227
228    def setLoginStatus(self, value):
229        if not isinstance(value, bool):
230            raise TypeError('Expecting bool type for loginStatus attribute; '
231                            'got %r' % type(value))
232        self.__loginStatus = value
233
234    def setSession(self, value):
235        self.__session = value
236
237    title = property(getTitle, setTitle, None, "Template title")
238
239    heading = property(getHeading, setHeading, None, "Template heading")
240
241    xml = property(getXml, setXml, None, "Additional XML for template")
242
243    headExtras = property(getHeadExtras, setHeadExtras, None, 
244                          "additional head info for template")
245
246    loginStatus = property(getLoginStatus, setLoginStatus, None, 
247                           "Login Status boolean")
248
249    session = property(getSession, setSession, None, 
250                       "Beaker session")
251
252    success_to = property(getSuccess_to, setSuccess_to, None, 
253                          "URL following successful login")
254
255    fail_to = property(getFail_to, setFail_to, None, 
256                       "URL following an error with login")
257
258    def __setattr__(self, name, value):
259        """Apply some generic type checking"""
260        if name in GenshiRendering.PROPERTY_NAMES:
261            if not isinstance(value, basestring):
262                raise TypeError('Expecting string type for %r attribute; got '
263                                '%r' % (name, type(value)))
264           
265        super(GenshiRendering, self).__setattr__(name, value)
266       
267    def _getLoader(self):
268        return self.__loader
269
270    def _setLoader(self, value):
271        if not isinstance(value, TemplateLoader):
272            raise TypeError('Expecting %r type for "loader"; got %r' % 
273                            (TemplateLoader, type(value)))
274        self.__loader = value
275
276    loader = property(_getLoader, _setLoader, 
277                      doc="Genshi TemplateLoader instance") 
278         
279    def _render(self, templateName, method='html', doctype='html', c=None, **kw):
280        '''Wrapper for Genshi template rendering
281        @type templateName: basestring
282        @param templateName: name of template file to load
283        @type c: None/object
284        @param c: reference to object to pass into template - defaults to self
285        @type kw: dict
286        @param kw: keywords to pass to template
287        @rtype: string
288        @return: rendered template
289        '''
290        if c is None:
291            c = self
292           
293        kw['c'] = c
294       
295        tmpl = self.loader.load(templateName)
296        rendering = tmpl.generate(**kw).render(method=method, doctype=doctype)
297       
298        return rendering
299
300    def yadis(self, environ, start_response):
301        """Render Yadis document containing user URL - override base
302        implementation to specify Yadis based discovery for user URL
303       
304        @type environ: dict
305        @param environ: dictionary of environment variables
306        @type start_response: callable
307        @param start_response: WSGI start response function.  Should be called
308        from this method to set the response code and HTTP header content
309        @rtype: basestring
310        @return: WSGI response
311        """
312        userIdentifier = OpenIDProviderMiddleware.parseIdentityURI(
313                                                    environ['PATH_INFO'])[-1]
314       
315        # This is where this implementation differs from the base class one
316        user_url = OpenIDProviderMiddleware.createIdentityURI(
317                                                        self.urls['url_yadis'],
318                                                        userIdentifier)
319       
320        yadisDict = dict(openid20type=discover.OPENID_2_0_TYPE, 
321                         openid10type=discover.OPENID_1_0_TYPE,
322                         endpoint_url=self.urls['url_openidserver'], 
323                         user_url=user_url)
324       
325        response = self._render(self.__class__.YADIS_TMPL_NAME, 
326                                method='xml',
327                                doctype=None,
328                                **yadisDict)
329     
330        start_response('200 OK',
331                       [('Content-type', 'application/xrds+xml'+self.charset),
332                        ('Content-length', str(len(response)))])
333        return response
334   
335    def serverYadis(self, environ, start_response):
336        '''Render Yadis info for ID Select mode request - Override base
337        implementation to enable custom XRDS document setting
338       
339        @type environ: dict
340        @param environ: dictionary of environment variables
341        @type start_response: callable
342        @param start_response: WSGI start response function.  Should be called
343        from this method to set the response code and HTTP header content
344        @rtype: basestring
345        @return: WSGI response
346        '''
347        endpoint_url = self.urls['url_openidserver']
348        _dict = {
349            'openid20type': discover.OPENID_IDP_2_0_TYPE,
350            'endpoint_url': endpoint_url
351        }
352       
353        response = self._render(self.__class__.SERVER_YADIS_TMPL_NAME, 
354                                method='xml',
355                                doctype=None,
356                                **_dict)
357             
358        start_response("200 OK",
359                       [('Content-type', 'application/xrds+xml'),
360                        ('Content-length', str(len(response)))])
361        return response
362   
363    def login(self, environ, start_response, success_to=None, fail_to=None, 
364              msg=''):
365        """Set-up template for OpenID Provider Login"""
366        self.title = "OpenID Login"
367        self.heading = "Login"
368        self.success_to = success_to or self.urls['url_mainpage']
369        self.fail_to = fail_to or self.urls['url_mainpage'] 
370        self.xml = msg
371       
372        response = self._render(GenshiRendering.LOGIN_TMPL_NAME)
373        start_response('200 OK', 
374                       [('Content-type', 'text/html'+self.charset),
375                        ('Content-length', str(len(response)))])
376        self.xml = ''
377        return response
378               
379    def mainPage(self, environ, start_response):
380        """Set-up template for OpenID Provider Login"""
381        self.title = "OpenID Provider"
382        self.heading = "OpenID Provider"
383        self.headExtras = '<meta http-equiv="x-xrds-location" content="%s"/>'%\
384                        self.urls['url_serveryadis']
385   
386        response = self._render(GenshiRendering.MAIN_PAGE_TMPL_NAME)
387        start_response('200 OK', 
388                       [('Content-type', 'text/html'+self.charset),
389                        ('Content-length', str(len(response)))])
390        return response
391
392    def identityPage(self, environ, start_response):
393        """This page would normally render the user's Identity page but it's
394        not needed for Yadis only based discovery"""
395        self.title = 'OpenID Provider - Error'
396        self.heading = 'OpenID Provider - Invalid Page Requested'
397        self.xml = 'Invalid page requested for OpenID Provider'
398        response = self._render(GenshiRendering.ERROR_PAGE_TMPL_NAME) 
399        self.xml = ''   
400        start_response("404 Not Found", 
401                       [('Content-type', 'text/html'+self.charset),
402                        ('Content-length', str(len(response)))])
403        return response
404 
405    def decidePage(self, environ, start_response, oidRequest, oidResponse):
406        """Handle user interaction required before final submit back to Relying
407        Party"""
408        self.title = 'Approve OpenID Request?'
409        self.heading = 'Approve OpenID Request?'
410        self.trust_root = oidRequest.trust_root
411        self.oidRequest = oidRequest
412       
413        # Get all the content namespaced as AX type
414        axArgs = oidResponse.fields.getArgs(ax.AXMessage.ns_uri)
415       
416        # Add to access object for convenient access based on type URI
417        axFetchResponse = ax.FetchResponse()
418        axFetchResponse.parseExtensionArgs(axArgs) 
419       
420        ax_req = ax.FetchRequest.fromOpenIDRequest(oidRequest)
421        axRequestedAttr = ax_req.requested_attributes
422        self.environ = environ
423       
424        if oidRequest.idSelect():
425            if 'username' not in self.session:
426                log.error("No 'username' key set in session object for "
427                          "idselect mode do decide page")
428                msg = ('An internal error has occurred.  Please contact '
429                       'your system administrator')
430                response = self.errorPage(environ, start_response, msg)
431                return response
432               
433            userIdentifier = self._authN.username2UserIdentifiers(
434                                            environ,
435                                            self.session['username'])[0]
436                                           
437            # Use the Yadis path because we want to use Yadis only
438            # based discovery
439            self.identityURI = OpenIDProviderMiddleware.createIdentityURI(
440                                                        self.urls['url_yadis'],
441                                                        userIdentifier)
442        else:
443            self.identityURI = oidRequest.identity
444       
445        response = self._render(GenshiRendering.DECIDE_PAGE_TMPL_NAME,
446                                axRequestedAttr=axRequestedAttr,
447                                axFetchResponse=axFetchResponse)
448        self.identityURI = ''
449       
450        start_response("200 OK", 
451                       [('Content-type', 'text/html'+self.charset),
452                        ('Content-length', str(len(response)))])
453        return response
454       
455    def errorPage(self, environ, start_response, msg, code=500):
456        '''Display error information'''
457        self.title = 'Error with OpenID Provider'
458        self.heading = 'Error'
459        self.xml = msg
460        response = self._render(GenshiRendering.ERROR_PAGE_TMPL_NAME)
461        start_response('%d %s' % (code, httplib.responses[code]), 
462                       [('Content-type', 'text/html'+self.charset),
463                        ('Content-length', str(len(response)))])
464        self.xml = ''
465        return response
466
467    trust_root = property(getTrust_root, setTrust_root, 
468                          doc="trust_root - dict of user trusted RPs")
469
470    oidRequest = property(getOidRequest, setOidRequest, 
471                          doc="oidRequest - OpenID Request object")
472
473    oidResponse = property(getOidResponse, setOidResponse, 
474                           doc="oidRequest - OpenID Response object")
475   
476    environ = property(getEnviron, setEnviron, None, 
477                       "WSGI environ dict")
478
479    identityURI = property(getIdentityURI, setIdentityURI, 
480                           doc="User OpenID URI")
Note: See TracBrowser for help on using the repository browser.