Changeset 4122


Ignore:
Timestamp:
14/08/08 11:47:20 (11 years ago)
Author:
pjkersha
Message:

Fixes to OpenIDProviderMiddleware to correctly handle ID Select type requests from Relying Party. Still TODO:

  • integrate with AuthKit? and user db via SQLAlchemy
  • Move Pylons wrapper project from test area to ndg.security.server package
  • error handling template
  • refactor rendering interface
  • replace with James Gardner's PassURL OpenID Provider?
Location:
TI12-security/trunk/python
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • TI12-security/trunk/python/Tests/openid-provider/op/development.ini

    r4121 r4122  
    2424 
    2525authkit.setup.method = form, cookie 
    26 authkit.form.template.string = <html><body>%s</body></html> 
    27 #  <head><title>NDG Sign In</title></head> 
    28 #  <body> 
    29 #    <h1>Please Sign In</h1> 
    30 #    <form action="%s" method="post"> 
    31 #      <dl> 
    32 #        <dt>Username:</dt> 
    33 #        <dd><input type="text" name="username"></dd> 
    34 #        <dt>Password:</dt> 
    35 #        <dd><input type="password" name="password"></dd> 
    36 #      </dl> 
    37 #      <input type="submit" name="authform" value="Sign In" /> 
    38 #    </form> 
    39 #  </body>  
    40 #</html> 
    41  
    4226authkit.form.authenticate.user.data = visitor:open_sesame 
    4327authkit.cookie.secret = secret string 
     
    5438#set debug = false 
    5539 
    56  
     40# OpenID Provider config 
     41openid_provider.path.openidserver=/openidserver 
     42openid_provider.path.login=/login 
     43openid_provider.path.loginsubmit=/loginsubmit 
     44openid_provider.path.id=/id 
     45openid_provider.path.yadis=/yadis 
     46openid_provider.path.serveryadis=/serveryadis 
     47openid_provider.path.allow=/allow 
     48openid_provider.path.decide=/decide 
     49openid_provider.path.mainpage=/ 
     50openid_provider.session_middleware=beaker.session  
     51openid_provider.base_url=http://localhost:8700 
     52openid_provider.consumer_store_dirpath=./ 
     53openid_provider.charset=None 
     54openid_provider.trace=False 
     55openid_provider.renderingClass=op.lib.rendering.OpenIDProviderKidRendering 
     56openid_provider.getSRegData= 
     57                    
    5758# Logging configuration 
    5859[loggers] 
    59 keys = root, ndg 
     60keys = root, ndg, op 
    6061 
    6162[handlers] 
  • TI12-security/trunk/python/Tests/openid-provider/op/op/config/middleware.py

    r4105 r4122  
    4444     
    4545    # CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares) 
    46     app = OpenIDProviderMiddleware(app, base_url='http://localhost:8700', 
    47                                    renderingClass=OpenIDProviderKidRendering) 
     46    app = OpenIDProviderMiddleware(app, app_conf) 
     47                                   #renderingClass=OpenIDProviderKidRendering) 
    4848    app = authkit.authenticate.middleware(app, app_conf) 
    4949    app = SessionMiddleware(app) 
  • TI12-security/trunk/python/Tests/openid-provider/op/op/lib/rendering.py

    r4121 r4122  
    33from pylons import config 
    44import ndg.security.server.sso.sso.lib.helpers as h 
     5import logging 
     6log = logging.getLogger(__name__) 
    57 
    68class MyBuffet(Buffet): 
     
    2426 
    2527class State: 
    26     def __init__(self): 
     28    def __init__(self, urls={}, session={}): 
    2729        self.title = '' 
    2830        self.xml = '' 
    2931        self.headExtras = '' 
    30         self.session = {} 
     32        self.session = session 
    3133        self.loginStatus = True 
     34        self.urls = urls 
    3235 
    3336def _render(templateName, **kw): 
     
    4447config['pylons.g'].stfcLink = 'http://ceda.stfc.ac.uk/' 
    4548config['pylons.g'].stfcImage = config['pylons.g'].server+'/layout/stfc-circle-sm.gif' 
     49config['pylons.g'].helpIcon = config['pylons.g'].server+'/layout/icons/help.png' 
    4650 
    4751from ndg.security.server.wsgi.openid_provider import RenderingInterface 
     
    5256    def renderLogin(self, environ, success_to=None, fail_to=None): 
    5357        """Set-up Kid template for OpenID Provider Login""" 
    54         c = State() 
    55         c.loginStatus = False 
     58        c = State(urls=self.urls, session=self.session) 
    5659        c.title = "OpenID Login" 
    57         c.path_loginsubmit = self.paths['path_loginsubmit']        
    58         c.success_to = success_to or self.paths['path_mainpage'] 
    59         c.fail_to = fail_to or self.paths['path_mainpage']  
    60         c.xml = '' 
     60        c.success_to = success_to or self.urls['url_mainpage'] 
     61        c.fail_to = fail_to or self.urls['url_mainpage']  
    6162     
    6263        return _render('ndg.security.login', c=c, g=config['pylons.g'], h=h) 
     
    6566    def renderMainPage(self, environ): 
    6667        """Set-up Kid template for OpenID Provider Login""" 
    67         c = State() 
    68         c.session = environ['beaker.session'] 
     68        c = State(urls=self.urls, session=self.session) 
    6969        c.title = "OpenID Provider" 
    70         c.path_serveryadis = self.paths['path_serveryadis'] 
    71         c.headExtras = '<meta http-equiv="x-xrds-location" content="%s%s"/>'%\ 
    72                         (self.base_url, c.path_serveryadis) 
     70        c.headExtras = '<meta http-equiv="x-xrds-location" content="%s"/>' % \ 
     71                        self.urls['url_serveryadis'] 
    7372     
    7473        return _render('ndg.security.mainPage', c=c, g=config['pylons.g'], h=h) 
    7574     
     75     
    7676    def renderDecidePage(self, environ, oidRequest): 
    77         id_url_base = self.base_url+self.paths['path_id'] + '/' 
    78          
    79         # XXX: This may break if there are any synonyms for id_url_base, 
    80         # such as referring to it by IP address or a CNAME. 
    81          
    82         # TODO: OpenID 2.0 Allows oidRequest.identity to be set to  
    83         # http://specs.openid.net/auth/2.0/identifier_select.  See, 
    84         # http://openid.net/specs/openid-authentication-2_0.html.  This code 
    85         # implements this overriding the behaviour of the example code on 
    86         # which this is based.  - Check is the example code based on OpenID 1.0 
    87         # and therefore wrong for this behaviour? 
    88 #        assert oidRequest.identity.startswith(id_url_base), \ 
    89 #               repr((oidRequest.identity, id_url_base)) 
    90         expected_user = oidRequest.identity[len(id_url_base):] 
    91         c = State() 
     77        """Handle user interaction required before final submit back to Relying 
     78        Party""" 
     79        c = State(urls=self.urls, session=self.session) 
    9280        c.title = 'Approve OpenID Request?' 
    93         c.path_allow = self.paths['path_allow'] 
    94         c.id_url_base = id_url_base 
    9581        c.trust_root = oidRequest.trust_root 
    9682        c.oidRequest = oidRequest 
     83         
     84        return _render('ndg.security.decidePage', c=c,g=config['pylons.g'],h=h) 
    9785 
    98         if oidRequest.idSelect(): # We are being asked to select an ID 
    99             msg = '''\ 
    100             <p>A site has asked for your identity.  You may select an 
    101             identifier by which you would like this site to know you. 
    102             On a production site this would likely be a drop down list 
    103             of pre-created accounts or have the facility to generate 
    104             a random anonymous identifier. 
    105             </p> 
    106             '''             
    107              
    108             fdata = { 
    109                 'path_allow': self.paths['path_allow'], 
    110                 'id_url_base': id_url_base, 
    111                 'trust_root': oidRequest.trust_root, 
    112                 } 
    113             form = '''\ 
    114             <form method="POST" action="%(path_allow)s"> 
    115             <table> 
    116               <tr><td>Identity:</td> 
    117                  <td>%(id_url_base)s<input type='text' name='identifier'></td></tr> 
    118               <tr><td>Trust Root:</td><td>%(trust_root)s</td></tr> 
    119             </table> 
    120             <p>Allow this authentication to proceed?</p> 
    121             <input type="checkbox" id="remember" name="remember" value="yes" 
    122                 /><label for="remember">Remember this 
    123                 decision</label><br /> 
    124             <input type="submit" name="yes" value="yes" /> 
    125             <input type="submit" name="no" value="no" /> 
    126             </form> 
    127             '''%fdata 
    128              
    129         else: 
    130             msg = '''\ 
    131             <p>A new site has asked to confirm your identity.  If you 
    132             approve, the site represented by the trust root below will 
    133             be told that you control identity URL listed below. (If 
    134             you are using a delegated identity, the site will take 
    135             care of reversing the delegation on its own.)</p>''' 
    13686 
    137             fdata = { 
    138                 'path_allow': self.paths['path_allow'], 
    139                 'identity': oidRequest.identity, 
    140                 'trust_root': oidRequest.trust_root, 
    141                 } 
    142             form = '''\ 
    143             <table> 
    144               <tr><td>Identity:</td><td>%(identity)s</td></tr> 
    145               <tr><td>Trust Root:</td><td>%(trust_root)s</td></tr> 
    146             </table> 
    147             <p>Allow this authentication to proceed?</p> 
    148             <form method="POST" action="%(path_allow)s"> 
    149               <input type="checkbox" id="remember" name="remember" value="yes" 
    150                   /><label for="remember">Remember this 
    151                   decision</label><br /> 
    152               <input type="submit" name="yes" value="yes" /> 
    153               <input type="submit" name="no" value="no" /> 
    154             </form>''' % fdata 
    155  
    156         return _render('ndg.security.decidePage', c=c,g=config['pylons.g'],h=h) 
     87    def renderErrorPage(self, environ, msg): 
     88        c = State(urls=self.urls, session=self.session) 
     89        c.title = 'Error with OpenID Provider' 
     90        c.xml = msg 
     91        return _render('ndg.security.error', c=c, g=config['pylons.g'], h=h) 
  • TI12-security/trunk/python/Tests/openid-provider/op/op/templates/ndg/security/decidePage.kid

    r4121 r4122  
    22    <head> 
    33            <replace py:replace="pagehead()"/> 
     4        <script src="$g.server/js/wmsc.js"></script> 
     5        <script src="$g.server/js/prototype.js"></script> 
     6        <script src="http://www.openlayers.org/api/OpenLayers.js"></script> 
     7        <script src="$g.server/js/openlayers-x.js"/> 
     8        <script src="$g.server/js/dimensionControl.js"/> 
     9        <script src="$g.server/js/mapControl.js"/> 
    410    </head> 
    511    <body> 
    612        <div py:replace="header()"/> 
    7         <div class="decidePageContent" style="text-indent:5px">         
     13        <div class="decidePageContent" style="text-indent:5px">   
     14                <?python 
     15                        if c.oidRequest.idSelect(): 
     16                                identityURL = c.urls['url_id']+'/'+c.session['username'] 
     17                        else: 
     18                                identityURL = c.oidRequest.identity 
     19                ?> 
    820            <h2>Login to $c.oidRequest.trust_root?</h2> 
    9                 <form method="POST" action="$c.path_allow"> 
     21<!--            <div class="loginHdr">Login<span py:replace="helpIcon('fts_help')"/></div> 
     22                <div id="fts_help" class="hidden"> 
     23                        <div class="helptxt"> 
     24                                <p> 
     25                                        Although you are logged into this site, you also need  
     26                                        to decide whether to allow your details to be 
     27                                        returned to $c.oidRequest.trust_root so that you are  
     28                                        logged in there too.  The details passed back include 
     29                                        the OpenID identifier given below.  The details don't 
     30                                        include you login password. 
     31                                </p> 
     32                        </div> 
     33                </div> 
     34--> 
     35                <form method="POST" action="${c.urls['url_allow']}"> 
    1036                        <table> 
    1137                                <tr> 
     
    1743                                <tr> 
    1844                                <td> 
    19                                         $c.oidRequest.identity 
     45                                        <pre><b>$identityURL</b></pre> 
    2046                                </td> 
    2147                        </tr> 
     
    3561                                <tr> 
    3662                                        <td align="right"> 
    37                                                 <input type="checkbox" id="remember" name="remember" value="yes"/> 
    38                                                 <label for="remember">Remember this decision</label> 
     63                                                <div py:if="c.oidRequest.trust_root not in c.session.get('approved', {})"> 
     64                                                        <input type="checkbox" id="remember" name="remember" value="yes"/> 
     65                                                        <label for="remember">Remember this decision</label> 
     66                                                </div> 
    3967                                        </td> 
    4068                                </tr> 
  • TI12-security/trunk/python/Tests/openid-provider/op/op/templates/ndg/security/error.kid

    r4105 r4122  
    66    <div id="entirepage"> 
    77        <div py:replace="header()"/> 
    8         <div id="${id}"> 
     8        <div id="errorContent"> 
    99            <div class="error" py:if="c.xml"> 
    1010            $c.xml 
    1111            </div> 
    12             <pre py:if="c.doc is not None"> 
    13 $c.doc 
    14             </pre> 
    1512        </div> 
    1613        <div py:replace="footer()"/> 
  • TI12-security/trunk/python/Tests/openid-provider/op/op/templates/ndg/security/login.kid

    r4120 r4122  
    22     
    33    <div py:def="loginForm()" class="loginForm"> 
    4         <form action="$c.path_loginsubmit" method="POST"> 
     4        <form action="${c.urls['url_loginsubmit']}" method="POST"> 
    55            <input type="hidden" name="success_to" value="$c.success_to" /> 
    66            <input type="hidden" name="fail_to" value="$c.fail_to" /> 
     
    3232            <p>${XML(c.xml)}</p> 
    3333        </div> 
    34         <div py:replace="footer(showLoginStatus=False)"/> 
     34        <div py:replace="footer()"/> 
    3535    </body> 
    3636 
  • TI12-security/trunk/python/Tests/openid-provider/op/op/templates/ndg/security/ndgPage.kid

    r4120 r4122  
    6565                                --> 
    6666                                <?python 
    67                                     logOutLink='%s/loginsubmit?submit=true&success_to=%s/login' % ((g.server,)*2) 
     67                                    logOutLink='%s?submit=true&success_to=%s&fail_to=%s' % \ 
     68                                    (c.urls['url_loginsubmit'],  
     69                                     c.urls['url_login'], 
     70                                     c.urls['url_mainpage']) 
    6871                                ?> 
    6972                                Logged in as ${c.session['username']}.  
     
    7578                        <div py:if="'username' not in c.session" id="loggedOut"> 
    7679                            <table><tbody><tr><td>  
    77                                 Other services may be available if you <a href="$g.server/login">login</a>. 
     80                                Other services may be available if you <a href="${c.urls['url_login']}">login</a>. 
    7881                            </td></tr></tbody></table> 
    7982                        </div> 
  • TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/openid_provider.py

    r4121 r4122  
    1919 
    2020import paste.request 
     21from paste.util.import_string import eval_import 
     22 
    2123from authkit.authenticate import AuthKitConfigError 
    2224 
     
    3032import cgi 
    3133quoteattr = lambda s: '"%s"' % cgi.escape(s, 1) 
    32  
     34getInvalidKw = lambda kw: [k for k in kw if k not in \ 
     35                           OpenIDProviderMiddleware.defKw] 
    3336 
    3437class OpenIDProviderMiddlewareError(Exception): 
     
    3841    """WSGI Middleware to implement an OpenID Provider""" 
    3942 
    40     defPaths = dict(path_openidserver='/openidserver', 
    41                    path_login='/login', 
    42                    path_loginsubmit='/loginsubmit', 
    43                    path_id='/id', 
    44                    path_yadis='/yadis', 
    45                    path_serveryadis='/serveryadis', 
    46                    path_allow='/allow', 
    47                    path_decide='/decide', 
    48                    path_mainpage='/')  
    49         
    50     def __init__(self, app,  
    51                  session_middleware='beaker.session',  
    52                  base_url=None, 
    53                  data_path='./', 
    54                  charset=None, 
    55                  trace=False, 
    56                  renderingClass=None, 
    57                  **kw): 
    58         invalidKw=[k for k in kw if k not in OpenIDProviderMiddleware.defPaths] 
     43    defKw = dict(path_openidserver='/openidserver', 
     44               path_login='/login', 
     45               path_loginsubmit='/loginsubmit', 
     46               path_id='/id', 
     47               path_yadis='/yadis', 
     48               path_serveryadis='/serveryadis', 
     49               path_allow='/allow', 
     50               path_decide='/decide', 
     51               path_mainpage='/', 
     52               session_middleware='beaker.session',  
     53               base_url='', 
     54               consumer_store_dirpath='./', 
     55               charset=None, 
     56               trace=False, 
     57               renderingClass=None, 
     58               getSRegData=None) 
     59     
     60    defPaths=dict([(k,v) for k,v in defKw.items() if k.startswith('path_')]) 
     61    method = dict([(v, k.replace('path_', 'do_')) for k,v in defPaths.items()]) 
     62      
     63    def __init__(self, app, app_conf=None, prefix='openid_provider.', **kw): 
     64        '''        
     65        @type app_conf: dict         
     66        @param app_conf: PasteDeploy application configuration dictionary 
     67        ''' 
     68 
     69        opt = OpenIDProviderMiddleware.defKw.copy() 
     70        kw2AppConfOpt = {} 
     71        if app_conf is not None: 
     72            # Update from application config dictionary - filter from using 
     73            # prefix 
     74            for k,v in app_conf.items(): 
     75                if k.startswith(prefix): 
     76                    subK = k.replace(prefix, '')                     
     77                    filtK = '_'.join(subK.split('.'))                     
     78                    opt[filtK] = v 
     79                    kw2AppConfOpt[filtK] = k 
     80                     
     81            invalidOpt = getInvalidKw(opt) 
     82            if len(invalidOpt) > 0: 
     83                raise TypeError("Unexpected app_conf option(s): %s" % \ 
     84                        (", ".join([kw2AppConfOpt[i] for i in invalidOpt]))) 
     85             
     86            # Convert from string type where required    
     87            opt['charset'] = eval(opt.get('charset', 'None'))      
     88            opt['trace'] = bool(eval(opt.get('trace', 'False')))  
     89              
     90            renderingClassVal = opt.get('renderingClass', None)       
     91            if renderingClassVal: 
     92                opt['renderingClass'] = eval_import(renderingClassVal) 
     93             
     94            getSRegDataVal = opt.get('getSRegData', None)   
     95            if getSRegDataVal: 
     96                opt['getSRegData'] = eval_import(getSRegDataVal)   
     97            else: 
     98                 opt['getSRegData'] = None 
     99                      
     100        invalidKw = getInvalidKw(kw) 
    59101        if len(invalidKw) > 0: 
    60             raise TypeError("Unexpected keywords: %s" % ", ".join(invalidKw)) 
    61          
    62         self.paths = OpenIDProviderMiddleware.defPaths.copy() 
    63         self.paths.update(kw) 
    64          
    65          
    66         self.method = dict([(v, k.replace('path_', 'do_')) \ 
    67                          for k,v in OpenIDProviderMiddleware.defPaths.items()]) 
    68          
    69         self.session_middleware = session_middleware 
    70         self.base_url = base_url 
    71  
    72         if charset is None: 
     102            raise TypeError("Unexpected keyword(s): %s" % ", ".join(invalidKw)) 
     103         
     104        # Update options from keywords - matching app_conf ones will be  
     105        # overwritten 
     106        opt.update(kw) 
     107        log.debug("opt=%r", opt)         
     108 
     109        # Paths relative to base URL 
     110        self.paths = dict([(k, opt[k]) \ 
     111                           for k in OpenIDProviderMiddleware.defPaths]) 
     112         
     113        if not opt['base_url']: 
     114            raise TypeError("base_url is not set") 
     115         
     116        self.base_url = opt['base_url'] 
     117 
     118        # Full Paths 
     119        self.urls = dict([(k.replace('path_', 'url_'), self.base_url+v) \ 
     120                          for k,v in self.paths.items()]) 
     121 
     122        self.session_middleware = opt['session_middleware'] 
     123 
     124        if opt['charset'] is None: 
    73125            self.charset = '' 
    74126        else: 
     
    76128         
    77129        # If True and debug log level is set display content of response 
    78         self._trace = trace 
     130        self._trace = opt['trace'] 
    79131         
    80132        # Pages can be customised by setting external rendering interface 
    81133        # class 
    82         if renderingClass is None: 
    83             renderingClass = RenderingInterface 
    84              
    85         elif not issubclass(renderingClass, RenderingInterface): 
     134        renderingClass = opt.get('renderingClass', None) or RenderingInterface          
     135        if not issubclass(renderingClass, RenderingInterface): 
    86136            raise OpenIDProviderMiddlewareError("Rendering interface " 
    87137                                                "class %r is not a %r " 
     
    91141 
    92142        try: 
    93             self._renderer = renderingClass(self.base_url, self.paths) 
     143            self._renderer = renderingClass(self.base_url, self.urls) 
    94144        except Exception, e: 
    95145            log.error("Error instantiating rendering interface...") 
    96146            raise 
    97147             
     148        # Callable for setting of additional attributes in HTTP header of 
     149        # response to Relying Party 
     150        self.getSRegData = opt.get('getSRegData', None) 
     151        if self.getSRegData and not callable(self.getSRegData): 
     152            raise OpenIDProviderMiddlewareError("Expecting callable for " 
     153                                                "getSRegData keyword, got %r"%\ 
     154                                                self.getSRegData) 
     155         
    98156        self.app = app 
    99157         
     
    101159        # were connecting to a database, you would create the database 
    102160        # connection and instantiate an appropriate store here. 
    103         store = FileOpenIDStore(data_path) 
    104         self.oidserver=server.Server(store,  
    105                                      base_url+self.paths['path_openidserver']) 
    106          
    107         self.approved = {} 
    108         self.lastCheckIDRequest = {} 
     161        store = FileOpenIDStore(opt['consumer_store_dirpath']) 
     162        self.oidserver = server.Server(store, self.urls['url_openidserver']) 
    109163 
    110164     
     
    121175        self.environ = environ 
    122176        self.start_response = start_response 
     177        self.session = environ[self.session_middleware] 
     178        self._renderer.session = self.session 
    123179         
    124180        # Match against the first level in the path only to allow for the 'id' 
     
    180236        username = self.path.split('/')[-1] 
    181237         
    182         endpoint_url = self.base_url + self.paths['path_openidserver'] 
    183         user_url = self.base_url + self.paths['path_id'] + '/' + username 
     238        endpoint_url = self.urls['url_openidserver'] 
     239        user_url = self.urls['url_id'] + '/' + username 
    184240         
    185241        yadisDict = dict(openid20type=discover.OPENID_2_0_TYPE,  
     
    224280        the Relying Party?""" 
    225281         
    226         oidRequest = environ[self.session_middleware].get('lastCheckIDRequest') 
     282        oidRequest = self.session.get('lastCheckIDRequest') 
    227283 
    228284        if 'Yes' in self.query: 
    229285            if oidRequest.idSelect(): 
    230                 identity = self.base_url+self.paths['path_id']+'/'+\ 
    231                             self.query['identifier'] 
     286                identity = self.urls['url_id']+'/'+self.session['username'] 
    232287            else: 
    233288                identity = oidRequest.identity 
     
    235290            trust_root = oidRequest.trust_root 
    236291            if self.query.get('remember', 'no') == 'yes': 
    237                 session = environ[self.session_middleware] 
    238                 session['approved'] = {trust_root: 'always'} 
    239                 session.save() 
     292                self.session['approved'] = {trust_root: 'always'} 
     293                self.session.save() 
    240294                 
    241295            oidResponse = self._identityApproved(oidRequest, identity) 
     
    275329        start_response("200 OK", [('Content-type', 'application/xrds+xml')]) 
    276330         
    277         endpoint_url = self.base_url + self.paths['path_openidserver'] 
     331        endpoint_url = self.urls['url_openidserver'] 
    278332        return [OpenIDProviderMiddleware.tmplServerYadis % \ 
    279333                {'openid20type': discover.OPENID_IDP_2_0_TYPE,  
     
    294348        """Handle user submission from login and logout""" 
    295349         
    296         session = environ[self.session_middleware] 
    297          
    298350        if 'submit' in self.query: 
    299351            if 'username' in self.query: 
    300352                # login 
    301                 if 'username' in session: 
     353                if 'username' in self.session: 
    302354                    log.error("Attempting login for user %s: user %s is " 
    303                               "already logged in", session['username'], 
    304                               session['username']) 
    305                     # TODO: display error page 
    306                     return self._redirect(start_response,  
    307                                           self.query['fail_to']) 
     355                              "already logged in", self.session['username'], 
     356                              self.session['username']) 
     357                    return self._redirect(start_response,self.query['fail_to']) 
    308358                     
    309                 session['username'] = self.query['username'] 
    310                 session.save() 
     359                self.session['username'] = self.query['username'] 
     360                self.session['approved'] = {} 
     361                self.session.save() 
    311362            else: 
    312363                # logout 
    313                 if 'username' not in session: 
     364                if 'username' not in self.session: 
    314365                    log.error("No user is logged in") 
    315                     # TODO: display error page 
    316                     return self._redirect(start_response,  
    317                                           self.query['fail_to']) 
    318                 del session['username'] 
    319                 session.save() 
     366                    return self._redirect(start_response,self.query['fail_to']) 
     367                 
     368                del self.session['username'] 
     369                self.session.pop('approved', None) 
     370                self.session.save() 
    320371                 
    321372            return self._redirect(start_response, self.query['success_to']) 
     
    324375            return self._redirect(start_response, self.query['fail_to']) 
    325376        else: 
    326             # TODO: display error page 
    327             raise OpenIDProviderMiddlewareError('Login input not recognised ' 
    328                                                 '%r' % self.query)    
     377            log.error('Login input not recognised %r' % self.query) 
     378            return self._redirect(start_response, self.query['fail_to']) 
    329379             
    330380 
     
    341391        """Display page prompting the user to decide whether to trust the site 
    342392        requesting their credentials""" 
    343         session = environ[self.session_middleware] 
    344         oidRequest = session.get('lastCheckIDRequest') 
     393 
     394        oidRequest = self.session.get('lastCheckIDRequest') 
    345395        if oidRequest is None: 
    346396            log.error("No OpenID request set in session") 
    347397            return self.do_mainpage(environ, start_response) 
    348398         
    349         approvedRoots = session.get('approved', {}) 
    350          
    351         if oidRequest.trust_root in approvedRoots: 
     399        approvedRoots = self.session.get('approved', {}) 
     400         
     401        if oidRequest.trust_root in approvedRoots and \ 
     402           not oidRequest.idSelect(): 
    352403            response = self._identityApproved(oidRequest, oidRequest.identity) 
    353404            return self._displayResponse(response) 
     
    360411            return response 
    361412         
    362     def _identityIsAuthorized(self, identity_url): 
     413         
     414    def _identityIsAuthorized(self, oidRequest): 
    363415        '''The given identity URL matches with a logged in user''' 
    364         session = self.environ[self.session_middleware] 
    365         username = session.get('username') 
     416 
     417        username = self.session.get('username') 
    366418        if username is None: 
    367419            return False 
    368420 
    369         if identity_url != self.base_url+self.paths['path_id']+'/'+username: 
     421        if oidRequest.idSelect(): 
     422            log.debug("OpenIDProviderMiddleware._identityIsAuthorized - " 
     423                      "ID Select mode set but user is already logged in") 
     424            return True 
     425         
     426        identityURL = self.urls['url_id']+'/'+username 
     427        if oidRequest.identity != identityURL: 
     428            log.debug("OpenIDProviderMiddleware._identityIsAuthorized - " 
     429                      "user is already logged in with a different ID=%s" % \ 
     430                      username) 
    370431            return False 
    371  
     432         
     433        log.debug("OpenIDProviderMiddleware._identityIsAuthorized - " 
     434                  "user is logged in with ID matching ID URL") 
    372435        return True 
     436     
    373437     
    374438    def _trustRootIsAuthorized(self, trust_root): 
    375439        '''The given trust root (Relying Party) has previously been approved 
    376440        by the user''' 
    377         session = self.environ[self.session_middleware] 
    378         approvedRoots = session.get('approved', {}) 
     441        approvedRoots = self.session.get('approved', {}) 
    379442        return approvedRoots.get(trust_root) is not None 
    380443 
    381444 
    382445    def _addSRegResponse(self, request, response): 
     446        '''Add additional attributes to response to Relying Party''' 
     447        if self.getSRegData is None: 
     448            return 
     449         
    383450        sreg_req = sreg.SRegRequest.fromOpenIDRequest(request) 
    384451 
    385         # In a real application, this data would be user-specific, 
    386         # and the user should be asked for permission to release 
    387         # it. 
    388         # TODO: role/user attribute look-up in database vs. username? 
    389         sreg_data = { 
    390             'nickname':self.environ[self.session_middleware]['username'] 
    391             } 
     452        # Callout to external callable sets additional user attributes to be 
     453        # returned in response to Relying Party         
     454        sreg_data = self.getSRegData(self.session.get('username')) 
    392455 
    393456        sreg_resp = sreg.SRegResponse.extractResponse(sreg_req, sreg_data) 
     
    405468         
    406469        log.debug("OpenIDProviderMiddleware._handleCheckIDRequest ...") 
    407         session = self.environ[self.session_middleware] 
    408          
    409         if self._identityIsAuthorized(oidRequest.identity): 
    410             # User is logged in 
    411             if self._trustRootIsAuthorized(oidRequest.trust_root): 
     470 
     471        if self._identityIsAuthorized(oidRequest): 
     472             
     473            # User is logged in - check for ID Select type request i.e. the 
     474            # user entered their IdP address at the Relying Party and not their 
     475            # OpenID Identifier.  In this case, the identity they wish to use 
     476            # must be confirmed. 
     477            if oidRequest.idSelect(): 
     478                # OpenID identifier must be confirmed 
     479                return self.do_decide(self.environ, self.start_response) 
     480             
     481            elif self._trustRootIsAuthorized(oidRequest.trust_root): 
    412482                # User has approved this Relying Party 
    413483                oidResponse = self._identityApproved(oidRequest) 
     
    421491         
    422492        else: 
    423             session = self.environ[self.session_middleware] 
    424             session['lastCheckIDRequest'] = oidRequest 
    425             session.save() 
     493            # User is not logged in - save request 
     494            self.session['lastCheckIDRequest'] = oidRequest 
     495            self.session.save() 
     496             
     497            # Call login and if successful then call decide page to confirm 
     498            # user wishes to trust the Relying Party. 
    426499            response = self.do_login(self.environ, 
    427500                                     self.start_response, 
    428                                      success_to=self.paths['path_decide']) 
     501                                     success_to=self.urls['url_decide']) 
    429502            return response 
    430503 
     
    435508        except server.EncodingError, why: 
    436509            text = why.response.encodeToKVForm() 
    437             # TODO: Error template page 
    438             return self._renderer.renderErrorPage(self.environ) 
    439  
     510            return self.showErrorPage(text) 
     511         
    440512        hdr = webresponse.headers.items() 
    441513         
     
    461533 
    462534 
     535    def _showErrorPage(self, msg, code=500): 
     536        response = self._renderer.renderErrorPage(self.environ,cgi.escape(msg)) 
     537        self.start_response('%d %s' % (code, httplib.responses[code]),  
     538                            [('Content-type', 'text/html'+self.charset), 
     539                             ('Content-length', str(len(msg)))]) 
     540        return response 
     541     
     542     
    463543class RenderingInterface(object): 
    464544    """Interface class for rendering of OpenID Provider pages.  Create a 
     
    467547    keyword to OpenIDProviderMiddleware.__init__""" 
    468548     
    469     def __init__(self, base_url, paths): 
     549    def __init__(self, base_url, urls): 
    470550        self.base_url = base_url 
    471         self.paths = paths 
     551        self.urls = urls 
    472552 
    473553    def renderIdentityPage(self, environ): 
     
    475555        path = environ.get('PATH_INFO') 
    476556         
    477         link_tag = '<link rel="openid.server" href="%s%s">' % \ 
    478               (self.base_url, self.paths['path_openidserver']) 
     557        link_tag = '<link rel="openid.server" href="%s">' % \ 
     558              self.urls['url_openidserver'] 
    479559               
    480560        yadis_loc_tag = '<meta http-equiv="x-xrds-location" content="%s">'%\ 
    481             (self.base_url+self.paths['path_yadis']+'/'+path[4:]) 
     561            (self.urls['url_yadis']+'/'+path[4:]) 
    482562             
    483563        disco_tags = link_tag + yadis_loc_tag 
    484564        ident = self.base_url + path 
    485565 
    486 #        approved_trust_roots = [] 
    487 #        for (aident, trust_root) in self.approved.keys(): 
    488 #            if aident == ident: 
    489 #                trs = '<li><tt>%s</tt></li>\n' % cgi.escape(trust_root) 
    490 #                approved_trust_roots.append(trs) 
    491 # 
    492 #        if approved_trust_roots: 
    493 #            prepend = '<p>Approved trust roots:</p>\n<ul>\n' 
    494 #            approved_trust_roots.insert(0, prepend) 
    495 #            approved_trust_roots.append('</ul>\n') 
    496 #            msg = ''.join(approved_trust_roots) 
    497 #        else: 
    498 #            msg = '' 
    499566        msg = '' 
    500567        return self._showPage(environ,  
     
    510577         
    511578        if success_to is None: 
    512             success_to = self.paths['path_mainpage'] 
     579            success_to = self.urls['url_mainpage'] 
    513580             
    514581        if fail_to is None: 
    515             fail_to = self.paths['path_mainpage'] 
     582            fail_to = self.urls['url_mainpage'] 
    516583         
    517584        return self._showPage(environ, 
     
    525592              <input type="submit" name="cancel" value="Cancel" /> 
    526593            </form> 
    527             ''' % (self.paths['path_loginsubmit'], success_to, fail_to)) 
     594            ''' % (self.urls['url_loginsubmit'], success_to, fail_to)) 
    528595 
    529596 
     
    532599         
    533600        yadis_tag = '<meta http-equiv="x-xrds-location" content="%s">' % \ 
    534                     (self.base_url + self.paths['path_serveryadis']) 
     601                    self.urls['url_serveryadis'] 
    535602        username = environ['beaker.session']['username']     
    536603        if username: 
    537             openid_url = self.base_url + self.paths['path_id'] + '/' + \ 
    538                         username 
     604            openid_url = self.urls['url_id'] + '/' + username 
    539605            user_message = """\ 
    540606            <p>You are logged in as %s. Your OpenID identity URL is 
     
    544610        else: 
    545611            user_message = "<p>You are not <a href='%s'>logged in</a>.</p>" % \ 
    546                             self.paths['path_login'] 
     612                            self.urls['url_login'] 
    547613 
    548614        return self._showPage(environ, 
     
    556622     
    557623    def renderDecidePage(self, environ, oidRequest): 
    558         id_url_base = self.base_url+self.paths['path_id'] + '/' 
     624        id_url_base = self.urls['url_id'] + '/' 
    559625         
    560626        # XXX: This may break if there are any synonyms for id_url_base, 
     
    582648            ''' 
    583649            fdata = { 
    584                 'path_allow': self.paths['path_allow'], 
     650                'path_allow': self.urls['url_allow'], 
    585651                'id_url_base': id_url_base, 
    586652                'trust_root': oidRequest.trust_root, 
     
    611677 
    612678            fdata = { 
    613                 'path_allow': self.paths['path_allow'], 
     679                'path_allow': self.urls['url_allow'], 
    614680                'identity': oidRequest.identity, 
    615681                'trust_root': oidRequest.trust_root, 
     
    641707 
    642708            fdata = { 
    643                 'path_allow': self.paths['path_allow'], 
     709                'path_allow': self.urls['url_allow'], 
    644710                'identity': oidRequest.identity, 
    645711                'trust_root': oidRequest.trust_root, 
     
    676742                        '<a href="%s?submit=true&'\ 
    677743                        'success_to=%s">Log out</a>' % \ 
    678                         (self.paths['path_id'], username, username,  
    679                          self.paths['path_loginsubmit'], 
    680                          self.paths['path_login']) 
     744                        (self.urls['url_id'], username, username,  
     745                         self.urls['url_loginsubmit'], 
     746                         self.urls['url_login']) 
    681747 
    682748        body = '' 
     
    788854 
    789855        return response 
     856 
     857    def renderErrorPage(self, environ, msg): 
     858        response = self._showPage(environ, 'Error Processing Request', err='''\ 
     859        <p>%s</p> 
     860        <!-- 
     861 
     862        This is a large comment.  It exists to make this page larger. 
     863        That is unfortunately necessary because of the "smart" 
     864        handling of pages returned with an error code in IE. 
     865 
     866        ************************************************************* 
     867        ************************************************************* 
     868        ************************************************************* 
     869        ************************************************************* 
     870        ************************************************************* 
     871        ************************************************************* 
     872        ************************************************************* 
     873        ************************************************************* 
     874        ************************************************************* 
     875        ************************************************************* 
     876        ************************************************************* 
     877        ************************************************************* 
     878        ************************************************************* 
     879        ************************************************************* 
     880        ************************************************************* 
     881        ************************************************************* 
     882        ************************************************************* 
     883        ************************************************************* 
     884        ************************************************************* 
     885        ************************************************************* 
     886        ************************************************************* 
     887        ************************************************************* 
     888        ************************************************************* 
     889 
     890        --> 
     891        ''' % msg) 
     892         
     893        return response 
Note: See TracChangeset for help on using the changeset viewer.