source: TI12-security/branches/ndg-security-1.5.x/ndg_security_server/ndg/security/server/wsgi/openid/provider/renderinginterface/genshi/__init__.py @ 7858

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/branches/ndg-security-1.5.x/ndg_security_server/ndg/security/server/wsgi/openid/provider/renderinginterface/genshi/__init__.py@7858
Revision 7858, 17.4 KB checked in by pjkersha, 9 years ago (diff)

Incomplete - task 21: Fix Yadis for 1.5.x branch to include Attribute Service and MyProxy? entries

  • Fixed and re-ran unit tests and authz_lite integration tests, releasing 1.5.9
  • 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    )
47    ATTR_NAMES = (
48        'title', 
49        'heading',
50        'xml', 
51        'headExtras', 
52        'loginStatus',
53        'loader',
54        'session',
55        'success_to',
56        'fail_to',
57        'trust_root',
58        'environ',
59        'identityURI',
60        'oidRequest',
61        'oidResponse'
62    )
63    __slots__ = tuple(["__%s" % name for name in ATTR_NAMES])
64    del name
65    __slots__ += PROPERTY_NAMES
66       
67    LOGIN_TMPL_NAME = 'login.html'
68    DECIDE_PAGE_TMPL_NAME = 'decide.html'
69    MAIN_PAGE_TMPL_NAME = 'main.html'
70    ERROR_PAGE_TMPL_NAME = 'error.html'
71    SERVER_YADIS_TMPL_NAME = 'serveryadis.xml'
72    YADIS_TMPL_NAME = 'yadis.xml'
73   
74    # Approve and reject submit HTML input types for the Relying Party Approval
75    # page
76    APPROVE_RP_SUBMIT = OpenIDProviderMiddleware.APPROVE_RP_SUBMIT
77    REJECT_RP_SUBMIT = OpenIDProviderMiddleware.REJECT_RP_SUBMIT
78
79    DEFAULT_TEMPLATES_DIR = path.join(path.dirname(__file__), 'templates')
80   
81    def __init__(self, *arg, **opt):
82        '''Extend RenderingInterface to include config and set-up for Genshi
83        templating
84       
85        @type *arg: tuple
86        @param *arg: RenderingInterface parent class arguments
87        @type **opt: dict
88        @param **opt: additional keywords to set-up Genshi rendering'''
89        super(GenshiRendering, self).__init__(*arg, **opt)
90       
91        # Initialise attributes
92        for i in GenshiRendering.PROPERTY_NAMES:
93            setattr(self, i, '')
94         
95        # Update from keywords   
96        for i in opt:
97            setattr(self, i, opt[i])
98
99        if not self.templateRootDir:
100            self.templateRootDir = GenshiRendering.DEFAULT_TEMPLATES_DIR
101         
102        self.__loader = TemplateLoader(self.templateRootDir, auto_reload=True)
103       
104        self.title = ''
105        self.heading = ''
106        self.xml = ''
107        self.headExtras = ''
108        self.loginStatus = True
109        self.session = ''
110        self.success_to = ''
111        self.fail_to = ''
112       
113        self.__oidRequest = None
114        self.__oidResponse = None
115        self.__identityURI = None
116        self.__environ = None
117        self.__trust_root = None
118
119    def getEnviron(self):
120        return self.__environ
121
122    def getIdentityURI(self):
123        return self.__identityURI
124
125    def setEnviron(self, value):
126        self.__environ = value
127
128    def setIdentityURI(self, value):
129        self.__identityURI = value
130
131    def getTrust_root(self):
132        return self.__trust_root
133
134    def getOidRequest(self):
135        return self.__oidRequest
136
137    def getOidResponse(self):
138        return self.__oidResponse
139
140    def setTrust_root(self, value):
141        if not isinstance(value, basestring):
142            raise TypeError('Expecting string type for trust_root attribute; '
143                            'got %r' % type(value))
144        self.__trust_root = value
145
146    def setOidRequest(self, value):
147        if not isinstance(value, CheckIDRequest):
148            raise TypeError('Expecting %r type for oidRequest attribute; '
149                            'got %r' % (CheckIDRequest, type(value)))
150        self.__oidRequest = value
151
152    def setOidResponse(self, value):
153        if not isinstance(value, OpenIDResponse):
154            raise TypeError('Expecting %r type for oidResponse attribute; '
155                            'got %r' % (OpenIDResponse, type(value)))
156        self.__oidResponse = value
157
158    def getSuccess_to(self):
159        return self.__success_to
160
161    def getFail_to(self):
162        return self.__fail_to
163
164    def setSuccess_to(self, value):
165        if not isinstance(value, basestring):
166            raise TypeError('Expecting string type for success_to attribute; '
167                            'got %r' % type(value))
168        self.__success_to = value
169
170    def setFail_to(self, value):
171        if not isinstance(value, basestring):
172            raise TypeError('Expecting string type for fail_to attribute; '
173                            'got %r' % type(value))
174        self.__fail_to = value
175
176    def getTitle(self):
177        return self.__title
178
179    def getHeading(self):
180        return self.__heading
181
182    def getXml(self):
183        return self.__xml
184
185    def getHeadExtras(self):
186        return self.__headExtras
187
188    def getLoginStatus(self):
189        return self.__loginStatus
190
191    def getSession(self):
192        return self.__session
193   
194    def setTitle(self, value):
195        if not isinstance(value, basestring):
196            raise TypeError('Expecting string type for title attribute; '
197                            'got %r' % type(value))
198        self.__title = value
199   
200    def setHeading(self, value):
201        if not isinstance(value, basestring):
202            raise TypeError('Expecting string type for heading attribute; '
203                            'got %r' % type(value))
204        self.__heading = value
205
206    def setXml(self, value):
207        if not isinstance(value, basestring):
208            raise TypeError('Expecting string type for xml attribute; '
209                            'got %r' % type(value))
210        self.__xml = value
211
212    def setHeadExtras(self, value):
213        if not isinstance(value, basestring):
214            raise TypeError('Expecting string type for headExtras attribute; '
215                            'got %r' % type(value))
216        self.__headExtras = value
217
218    def setLoginStatus(self, value):
219        if not isinstance(value, bool):
220            raise TypeError('Expecting bool type for loginStatus attribute; '
221                            'got %r' % type(value))
222        self.__loginStatus = value
223
224    def setSession(self, value):
225        self.__session = value
226
227    title = property(getTitle, setTitle, None, "Template title")
228
229    heading = property(getHeading, setHeading, None, "Template heading")
230
231    xml = property(getXml, setXml, None, "Additional XML for template")
232
233    headExtras = property(getHeadExtras, setHeadExtras, None, 
234                          "additional head info for template")
235
236    loginStatus = property(getLoginStatus, setLoginStatus, None, 
237                           "Login Status boolean")
238
239    session = property(getSession, setSession, None, 
240                       "Beaker session")
241
242    success_to = property(getSuccess_to, setSuccess_to, None, 
243                          "URL following successful login")
244
245    fail_to = property(getFail_to, setFail_to, None, 
246                       "URL following an error with login")
247
248    def __setattr__(self, name, value):
249        """Apply some generic type checking"""
250        if name in GenshiRendering.PROPERTY_NAMES:
251            if not isinstance(value, basestring):
252                raise TypeError('Expecting string type for %r attribute; got '
253                                '%r' % (name, type(value)))
254           
255        super(GenshiRendering, self).__setattr__(name, value)
256       
257    def _getLoader(self):
258        return self.__loader
259
260    def _setLoader(self, value):
261        if not isinstance(value, TemplateLoader):
262            raise TypeError('Expecting %r type for "loader"; got %r' % 
263                            (TemplateLoader, type(value)))
264        self.__loader = value
265
266    loader = property(_getLoader, _setLoader, 
267                      doc="Genshi TemplateLoader instance") 
268         
269    def _render(self, templateName, method='html', doctype='html', c=None, **kw):
270        '''Wrapper for Genshi template rendering
271        @type templateName: basestring
272        @param templateName: name of template file to load
273        @type c: None/object
274        @param c: reference to object to pass into template - defaults to self
275        @type kw: dict
276        @param kw: keywords to pass to template
277        @rtype: string
278        @return: rendered template
279        '''
280        if c is None:
281            c = self
282           
283        kw['c'] = c
284       
285        tmpl = self.loader.load(templateName)
286        rendering = tmpl.generate(**kw).render(method=method, doctype=doctype)
287       
288        return rendering
289
290    def yadis(self, environ, start_response):
291        """Render Yadis document containing user URL - override base
292        implementation to specify Yadis based discovery for user URL
293       
294        @type environ: dict
295        @param environ: dictionary of environment variables
296        @type start_response: callable
297        @param start_response: WSGI start response function.  Should be called
298        from this method to set the response code and HTTP header content
299        @rtype: basestring
300        @return: WSGI response
301        """
302        userIdentifier = OpenIDProviderMiddleware.parseIdentityURI(
303                                                    environ['PATH_INFO'])[-1]
304       
305        # This is where this implementation differs from the base class one
306        user_url = OpenIDProviderMiddleware.createIdentityURI(
307                                                        self.urls['url_yadis'],
308                                                        userIdentifier)
309       
310        yadisDict = dict(openid20type=discover.OPENID_2_0_TYPE, 
311                         openid10type=discover.OPENID_1_0_TYPE,
312                         endpoint_url=self.urls['url_openidserver'], 
313                         user_url=user_url)
314       
315        response = self._render(self.__class__.YADIS_TMPL_NAME, 
316                                method='xml',
317                                doctype=None,
318                                **yadisDict)
319     
320        start_response('200 OK',
321                       [('Content-type', 'application/xrds+xml'+self.charset),
322                        ('Content-length', str(len(response)))])
323        return response
324   
325    def serverYadis(self, environ, start_response):
326        '''Render Yadis info for ID Select mode request - Override base
327        implementation to enable custom XRDS document setting
328       
329        @type environ: dict
330        @param environ: dictionary of environment variables
331        @type start_response: callable
332        @param start_response: WSGI start response function.  Should be called
333        from this method to set the response code and HTTP header content
334        @rtype: basestring
335        @return: WSGI response
336        '''
337        endpoint_url = self.urls['url_openidserver']
338        _dict = {
339            'openid20type': discover.OPENID_IDP_2_0_TYPE,
340            'endpoint_url': endpoint_url
341        }
342       
343        response = self._render(self.__class__.SERVER_YADIS_TMPL_NAME, 
344                                method='xml',
345                                doctype=None,
346                                **_dict)
347             
348        start_response("200 OK",
349                       [('Content-type', 'application/xrds+xml'),
350                        ('Content-length', str(len(response)))])
351        return response
352   
353 
354    def login(self, environ, start_response, success_to=None, fail_to=None, 
355              msg=''):
356        """Set-up template for OpenID Provider Login"""
357        self.title = "OpenID Login"
358        self.heading = "Login"
359        self.success_to = success_to or self.urls['url_mainpage']
360        self.fail_to = fail_to or self.urls['url_mainpage'] 
361        self.xml = msg
362       
363        response = self._render(GenshiRendering.LOGIN_TMPL_NAME)
364        start_response('200 OK', 
365                       [('Content-type', 'text/html'+self.charset),
366                        ('Content-length', str(len(response)))])
367        self.xml = ''
368        return response
369               
370    def mainPage(self, environ, start_response):
371        """Set-up template for OpenID Provider Login"""
372        self.title = "OpenID Provider"
373        self.heading = "OpenID Provider"
374        self.headExtras = '<meta http-equiv="x-xrds-location" content="%s"/>'%\
375                        self.urls['url_serveryadis']
376   
377        response = self._render(GenshiRendering.MAIN_PAGE_TMPL_NAME)
378        start_response('200 OK', 
379                       [('Content-type', 'text/html'+self.charset),
380                        ('Content-length', str(len(response)))])
381        return response
382
383    def identityPage(self, environ, start_response):
384        """This page would normally render the user's Identity page but it's
385        not needed for Yadis only based discovery"""
386        self.title = 'OpenID Provider - Error'
387        self.heading = 'OpenID Provider - Invalid Page Requested'
388        self.xml = 'Invalid page requested for OpenID Provider'
389        response = self._render(GenshiRendering.ERROR_PAGE_TMPL_NAME) 
390        self.xml = ''   
391        start_response("404 Not Found", 
392                       [('Content-type', 'text/html'+self.charset),
393                        ('Content-length', str(len(response)))])
394        return response
395 
396    def decidePage(self, environ, start_response, oidRequest, oidResponse):
397        """Handle user interaction required before final submit back to Relying
398        Party"""
399        self.title = 'Approve OpenID Request?'
400        self.heading = 'Approve OpenID Request?'
401        self.trust_root = oidRequest.trust_root
402        self.oidRequest = oidRequest
403       
404        # Get all the content namespaced as AX type
405        axArgs = oidResponse.fields.getArgs(ax.AXMessage.ns_uri)
406        if not axArgs:
407            log.debug('No AX parameters were requested by the Relying Party %r',
408                      oidRequest.trust_root)
409            axRequestedAttr = {}
410            axFetchResponse = None
411        else:
412            log.debug('Relying Party %r requested the AX parameters %r',
413                      oidRequest.trust_root, axArgs)
414
415            # Add to access object for convenient access based on type URI
416            axFetchResponse = ax.FetchResponse()
417            axFetchResponse.parseExtensionArgs(axArgs) 
418           
419            ax_req = ax.FetchRequest.fromOpenIDRequest(oidRequest)
420            axRequestedAttr = ax_req.requested_attributes
421           
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.