Changeset 4132


Ignore:
Timestamp:
21/08/08 16:53:48 (11 years ago)
Author:
pjkersha
Message:

OpenID Provider:

  • Added support for transfer of ESG attributes using OpenID Attribute Exchange extension
  • If content to be returned from OP makes the URL too long the OpenID API converts the response into form via POST. Updated code to wrap this in a Javascript OnLoad? call to automatically submit and return the redirect the user back to the Relying Party.
Location:
TI12-security/trunk/python/ndg.security.server/ndg/security/server
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • TI12-security/trunk/python/ndg.security.server/ndg/security/server/pylons/container/lib/openid_provider_util.py

    r4125 r4132  
    105105        c.xml = "<b><pre>%s</pre></b>" % identityURL 
    106106         
    107         return _render('ndg.security.identityPage', 
    108                        c=c, g=config, h=h)     
    109      
     107        return _render('ndg.security.identityPage', c=c, g=config, h=h)     
     108 
     109    
    110110    def renderDecidePage(self, environ, oidRequest): 
    111111        """Handle user interaction required before final submit back to Relying 
     
    124124        c.xml = msg 
    125125        return _render('ndg.security.error', c=c, g=config, h=h) 
     126 
     127# Earth System Grid interoperability tests 
     128 
     129#esgAxAttr = {'urn:esg.security.gateway': 'BADC', 
     130#        'urn:esg.security.authority': 'group_IPCC_role_default', 
     131#        'http://axschema.org/namePerson/last': 'UserLastName', 
     132#        'http://axschema.org/contact/country/home': 'UK', 
     133#        'http://axschema.org/namePerson/middle': 'UserMiddleName', 
     134#        'urn:esg.security.uuid': '0123456789abcdef', 
     135#        'http://axschema.org/namePerson/first': 'UserFirstName', 
     136#        'http://axschema.org/namePerson/friendly': '', 
     137#        'http://axschema.org/contact/email': 'tester@test.com', 
     138#        'urn:esg.security.organization': 'British Atmospheric Data Centre', 
     139#} 
     140 
     141esgAxAttr = { 
     142 'http://openid.net/schema/contact/state/home': 'Oxfordshire',  
     143 'http://openid.net/schema/namePerson/middle': 'George',  
     144 'http://openid.net/schema/contact/city/home': 'Didcot',  
     145 'http://openid.net/schema/person/guid': '0123456789abcdef',  
     146 'http://openid.net/schema/namePerson/friendly': 'username',  
     147 'http://openid.net/schema/company/name': 'The British Atmospheric Data Centre',  
     148 'http://openid.net/schema/contact/country/home': 'UK',  
     149 'http://openid.net/schema/namePerson/first': 'John',  
     150 'http://openid.net/schema/namePerson/last': 'Smith',  
     151 'http://openid.net/schema/contact/internet/email': 'testABC@rl.ac.uk', 
     152 'http://www.earthsystemgrid.org/authority': 'group_IPCC_role_default', 
     153 'http://www.earthsystemgrid.org/gateway': 'BADC', 
     154} 
     155esgAxAlias = { 
     156 'http://openid.net/schema/contact/state/home': 'state',  
     157 'http://openid.net/schema/namePerson/middle': 'middlename',  
     158 'http://openid.net/schema/contact/city/home': 'city',  
     159 'http://openid.net/schema/person/guid': 'uuid',  
     160 'http://openid.net/schema/namePerson/friendly': 'username',  
     161 'http://openid.net/schema/company/name': 'organization',  
     162 'http://openid.net/schema/contact/country/home': 'country',  
     163 'http://openid.net/schema/namePerson/first': 'firstname',  
     164 'http://openid.net/schema/namePerson/last': 'lastname',  
     165 'http://openid.net/schema/contact/internet/email': 'email', 
     166 'http://www.earthsystemgrid.org/authority': 'authority', 
     167 'http://www.earthsystemgrid.org/gateway': 'gateway', 
     168              } 
     169 
     170esgSRegAttr = { 
     171    'nickname':'', 
     172    'email':'E-mail Address', 
     173    'country':'UK', 
     174    'language':'English', 
     175    'timezone':'BST', 
     176    } 
     177 
     178 
     179def esgSRegResponseHandler(username): 
     180    """Interface function to OpenIdProviderMiddleware to set custom attributes 
     181    """ 
     182    attr = esgSRegAttr.copy() 
     183#    attr['username'] = username 
     184    attr['nickname'] = username 
     185    return attr 
     186 
     187def esgAXResponseHandler(axReq, axResp, username):   
     188    """Respond to attributes requested by Relying Party via the Attribute 
     189    Exchange interface""" 
     190    attr = esgAxAttr.copy() 
     191    attr['http://openid.net/schema/namePerson/friendly'] = username 
     192     
     193    for typeURI, attrInfo in axReq.requested_attributes.items(): 
     194        # Value input must be list type 
     195        axResp.setValues(typeURI, [attr[typeURI]]) 
  • TI12-security/trunk/python/ndg.security.server/ndg/security/server/pylons/development.ini

    r4126 r4132  
    1212 
    1313# Layout 
    14 server = http://localhost:8200 
     14#server = http://localhost:8200 
     15server = https://gabriel.badc.rl.ac.uk 
    1516LeftLogo = %(server)s/layout/NERC_Logo.gif 
    1617LeftAlt = Natural Environment Research Council 
     
    7273openid_provider.session_middleware=beaker.session  
    7374openid_provider.base_url=http://localhost:8200 
    74 openid_provider.consumer_store_dirpath=./ 
     75openid_provider.base_url=https://gabriel.badc.rl.ac.uk 
     76#openid_provider.consumer_store_dirpath=./ 
    7577openid_provider.charset=None 
    7678openid_provider.trace=False 
    7779openid_provider.renderingClass=ndg.security.server.pylons.container.lib.openid_provider_util.OpenIDProviderKidRendering 
    78 openid_provider.getSRegData= 
     80openid_provider.sregResponseHandler=ndg.security.server.pylons.container.lib.openid_provider_util:esgSRegResponseHandler 
     81openid_provider.axResponseHandler=ndg.security.server.pylons.container.lib.openid_provider_util:esgAXResponseHandler 
    7982 
    8083# Logging configuration 
  • TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/openid_provider.py

    r4126 r4132  
    2323from authkit.authenticate import AuthKitConfigError 
    2424 
    25 from openid.extensions import sreg 
     25from openid.extensions import sreg, ax 
    2626from openid.server import server 
    2727from openid.store.filestore import FileOpenIDStore 
     
    3737class OpenIDProviderMiddlewareError(Exception): 
    3838    """OpenID Provider WSGI Middleware Error""" 
    39      
     39 
     40class OpenIDProviderConfigError(Exception): 
     41    """OpenID Provider Configuration Error""" 
     42    
    4043class OpenIDProviderMiddleware(object): 
    4144    """WSGI Middleware to implement an OpenID Provider""" 
     
    5659               trace=False, 
    5760               renderingClass=None, 
    58                getSRegData=None) 
     61               sregResponseHandler=None, 
     62               axResponseHandler=None) 
    5963     
    6064    defPaths=dict([(k,v) for k,v in defKw.items() if k.startswith('path_')]) 
     
    9195                opt['renderingClass'] = eval_import(renderingClassVal) 
    9296             
    93             getSRegDataVal = opt.get('getSRegData', None)   
    94             if getSRegDataVal: 
    95                 opt['getSRegData'] = eval_import(getSRegDataVal)   
     97            sregResponseHandlerVal = opt.get('sregResponseHandler', None)   
     98            if sregResponseHandlerVal: 
     99                opt['sregResponseHandler']=eval_import(sregResponseHandlerVal)   
    96100            else: 
    97                  opt['getSRegData'] = None 
    98                       
     101                 opt['sregResponseHandler'] = None 
     102 
     103            axResponseHandlerVal = opt.get('axResponseHandler', None)   
     104            if axResponseHandlerVal: 
     105                opt['axResponseHandler'] = eval_import(axResponseHandlerVal) 
     106            else: 
     107                opt['axResponseHandler'] = None 
     108                           
    99109        invalidKw = getInvalidKw(kw) 
    100110        if len(invalidKw) > 0: 
     
    104114        # overwritten 
    105115        opt.update(kw) 
    106         log.debug("opt=%r", opt)         
    107116 
    108117        # Paths relative to base URL - Nb. remove trailing '/' 
     
    131140        # If True and debug log level is set display content of response 
    132141        self._trace = opt['trace'] 
    133          
     142 
     143        log.debug("opt=%r", opt)         
     144        
    134145        # Pages can be customised by setting external rendering interface 
    135146        # class 
     
    148159            raise 
    149160             
    150         # Callable for setting of additional attributes in HTTP header of 
    151         # response to Relying Party 
    152         self.getSRegData = opt.get('getSRegData', None) 
    153         if self.getSRegData and not callable(self.getSRegData): 
     161        # Callable for setting of Simple Registration attributes in HTTP header 
     162        # of response to Relying Party 
     163        self.sregResponseHandler = opt.get('sregResponseHandler', None) 
     164        if self.sregResponseHandler and not callable(self.sregResponseHandler): 
    154165            raise OpenIDProviderMiddlewareError("Expecting callable for " 
    155                                                 "getSRegData keyword, got %r"%\ 
    156                                                 self.getSRegData) 
     166                                                "sregResponseHandler keyword, " 
     167                                                "got %r" % \ 
     168                                                self.sregResponseHandler) 
     169             
     170        # Callable to handle OpenID Attribute Exchange (AX) requests from 
     171        # the Relying Party 
     172        self.axResponseHandler = opt.get('axResponseHandler', None) 
     173        if self.axResponseHandler and not callable(self.axResponseHandler): 
     174            raise OpenIDProviderMiddlewareError("Expecting callable for " 
     175                                                "axResponseHandler keyword, " 
     176                                                "got %r" % \ 
     177                                                self.axResponseHandler) 
    157178         
    158179        self.app = app 
     
    166187     
    167188    def __call__(self, environ, start_response): 
     189         
    168190        if not environ.has_key(self.session_middleware): 
    169             raise AuthKitConfigError( 
    170                 'The session middleware %r is not present. ' 
    171                 'Have you set up the session middleware?'%( 
    172                     self.session_middleware 
    173                 ) 
    174             ) 
     191            raise OpenIDProviderConfigError('The session middleware %r is not ' 
     192                                            'present. Have you set up the ' 
     193                                            'session middleware?' % \ 
     194                                            self.session_middleware) 
    175195 
    176196        self.path = environ.get('PATH_INFO').rstrip('/') 
     
    218238        response = self._renderer.renderIdentityPage(environ) 
    219239 
    220         start_response("200 OK", [('Content-length', str(len(response)))]) 
     240        start_response("200 OK",  
     241                       [('Content-type', 'text/html'+self.charset), 
     242                        ('Content-length', str(len(response)))]) 
    221243        return response 
    222244 
     
    272294                self.session['approved'] = {trust_root: 'always'} 
    273295                self.session.save() 
    274                  
     296              
    275297            oidResponse = self._identityApproved(oidRequest, identity) 
    276             return self._displayResponse(oidResponse) 
     298            response = self._displayResponse(oidResponse) 
     299            log.debug("do_allow response = \n%s" % response) 
     300            return response 
    277301         
    278302        elif 'No' in self.query: 
     
    288312                                                'post.  %r' % self.query) 
    289313 
    290      
    291     tmplServerYadis = """\ 
    292 <?xml version="1.0" encoding="UTF-8"?> 
    293 <xrds:XRDS 
    294     xmlns:xrds="xri://$xrds" 
    295     xmlns="xri://$xrd*($v*2.0)"> 
    296   <XRD> 
    297  
    298     <Service priority="0"> 
    299       <Type>%(openid20type)s</Type> 
    300       <URI>%(endpoint_url)s</URI> 
    301     </Service> 
    302  
    303   </XRD> 
    304 </xrds:XRDS> 
    305 """ 
    306314 
    307315    def do_serveryadis(self, environ, start_response): 
    308316        """Handle Server Yadis call""" 
    309         start_response("200 OK", [('Content-type', 'application/xrds+xml')]) 
    310          
    311         endpoint_url = self.urls['url_openidserver'] 
    312         return [OpenIDProviderMiddleware.tmplServerYadis % \ 
    313                 {'openid20type': discover.OPENID_IDP_2_0_TYPE,  
    314                  'endpoint_url': endpoint_url}] 
     317        response = self._renderer.renderServerYadis(environ) 
     318        start_response("200 OK",  
     319                       [('Content-type', 'application/xrds+xml'), 
     320                        ('Content-length', str(len(response)))]) 
     321        return response 
    315322 
    316323 
     
    424431 
    425432    def _addSRegResponse(self, request, response): 
    426         '''Add additional attributes to response to Relying Party''' 
    427         if self.getSRegData is None: 
     433        '''Add Simple Registration attributes to response to Relying Party''' 
     434        if self.sregResponseHandler is None: 
    428435            return 
    429436         
     
    432439        # Callout to external callable sets additional user attributes to be 
    433440        # returned in response to Relying Party         
    434         sreg_data = self.getSRegData(self.session.get('username')) 
    435  
     441        sreg_data = self.sregResponseHandler(self.session.get('username')) 
    436442        sreg_resp = sreg.SRegResponse.extractResponse(sreg_req, sreg_data) 
    437443        response.addExtension(sreg_resp) 
    438444 
    439445 
     446    def _addAXResponse(self, request, response): 
     447        '''Add attributes to response based on the OpenID Attribute Exchange  
     448        interface''' 
     449 
     450        ax_req = ax.FetchRequest.fromOpenIDRequest(request) 
     451        if ax_req is None: 
     452            log.debug("No Attribute Exchange extension set in request") 
     453            return 
     454         
     455        ax_resp = ax.FetchResponse(request=ax_req) 
     456         
     457        if self.axResponseHandler is None: 
     458            requiredAttr = ax_req.getRequiredAttrs() 
     459            if len(requiredAttr) > 0: 
     460                log.error("Relying party requires these attributes: %s; but no" 
     461                          "Attribute exchange handler 'axResponseHandler' has " 
     462                          "been set" % requiredAttr) 
     463            return 
     464         
     465        # Set requested values - need user intervention here to confirm  
     466        # release of attributes + assignment based on required attributes -  
     467        # possibly via FetchRequest.getRequiredAttrs() 
     468#        for typeURI, attrInfo in ax_req.requested_attributes.items(): 
     469#            # Value input must be list type 
     470#            ax_resp.setValues(typeURI, [attrInfo.alias+"Value"]) 
     471        self.axResponseHandler(ax_req, ax_resp, self.session.get('username')) 
     472         
     473        response.addExtension(ax_resp) 
     474         
     475         
    440476    def _identityApproved(self, request, identifier=None): 
    441477        '''Action following approval of a Relying Party by the user''' 
    442478        response = request.answer(True, identity=identifier) 
    443479        self._addSRegResponse(request, response) 
     480        self._addAXResponse(request, response) 
    444481        return response 
    445482 
     
    482519            return response 
    483520 
    484  
    485     def _displayResponse(self, response): 
     521    # If the response to the Relying Party is too long it's rendered as form 
     522    # with the POST method instead of query arguments in a GET 302 redirect. 
     523    # Wrap the form in this document to make the form submit automatically 
     524    # without user intervention.  See _displayResponse method below... 
     525    formRespWrapperTmpl = """<html> 
     526    <head> 
     527        <script type="text/javascript"> 
     528            function doRedirect() 
     529            { 
     530                document.forms[0].submit(); 
     531            } 
     532        </script> 
     533    </head> 
     534    <body onLoad="doRedirect()"> 
     535        %s 
     536    </body> 
     537</html>""" 
     538 
     539    def _displayResponse(self, oidResponse): 
    486540        try: 
    487             webresponse = self.oidserver.encodeResponse(response) 
     541            webresponse = self.oidserver.encodeResponse(oidResponse) 
    488542        except server.EncodingError, why: 
    489543            text = why.response.encodeToKVForm() 
     
    494548        lenWebResponseBody = len(webresponse.body) 
    495549        if lenWebResponseBody: 
    496             response = webresponse.body 
    497         else: 
    498             response = [] 
    499              
    500         hdr += [('Content-length', str(lenWebResponseBody))] 
    501              
     550            # Wrap in HTML with JAvascript OnLoad to submit the form 
     551            # automatically without user intervention 
     552            response = OpenIDProviderMiddleware.formRespWrapperTmpl % \ 
     553                                                        webresponse.body 
     554        else: 
     555            response = '' 
     556             
     557        hdr += [('Content-type', 'text/html'+self.charset), 
     558                ('Content-length', str(lenWebResponseBody))] 
     559             
     560        log.debug("webresponse.code = %d" % webresponse.code) 
    502561        self.start_response('%d %s' % (webresponse.code,  
    503562                                       httplib.responses[webresponse.code]),  
     
    552611                                %s 
    553612                                ''' % (ident, msg)) 
     613     
     614    tmplServerYadis = """\ 
     615<?xml version="1.0" encoding="UTF-8"?> 
     616<xrds:XRDS 
     617    xmlns:xrds="xri://$xrds" 
     618    xmlns="xri://$xrd*($v*2.0)"> 
     619  <XRD> 
     620 
     621    <Service priority="0"> 
     622      <Type>%(openid20type)s</Type> 
     623      <URI>%(endpoint_url)s</URI> 
     624    </Service> 
     625 
     626  </XRD> 
     627</xrds:XRDS> 
     628""" 
     629 
     630    def renderServerYadis(self, environ): 
     631        '''Render Yadis info''' 
     632        endpoint_url = self.urls['url_openidserver'] 
     633        return RenderingInterface.tmplServerYadis % \ 
     634            {'openid20type': discover.OPENID_IDP_2_0_TYPE,  
     635             'endpoint_url': endpoint_url} 
    554636         
    555637    tmplYadis = """\ 
     
    569651  </XRD> 
    570652</xrds:XRDS>"""     
     653     
    571654     
    572655    def renderYadis(self, environ): 
Note: See TracChangeset for help on using the changeset viewer.