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

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg-security/TI12-security/trunk/NDGSecurity/python/ndg_security_server/ndg/security/server/wsgi/openid/provider/renderinginterface/genshi/__init__.py@6265
Revision 6265, 15.7 KB checked in by pjkersha, 11 years ago (diff)

Adding Genshi template interface for access denied result handler.

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