source: TI12-security/trunk/python/ndg.security.server/ndg/security/server/sso/sso/controllers/login.py @ 4081

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.server/ndg/security/server/sso/sso/controllers/login.py@4081
Revision 4081, 11.0 KB checked in by pjkersha, 11 years ago (diff)

First working version of an OpenID Provider as opposed to a Relying Party as avail. with AuthKit?. The code is taken from the HTTPServer example in the Python OpenID package and refactored into WSGI middleware.

  • ndg.security.server.wsgi.openid_provider - WSGI middleware package
  • Tests/openid-provider/op: pylons project test harness for the above

TODO: integrate into AuthKit? and Beaker Session Middleware as required.

Line 
1# _redirect requires this to parse the server name
2from urlparse import urlsplit
3
4
5from ndg.security.server.sso.sso.lib.base import *
6from ndg.security.common.pylons.security_util import setSecuritySession, \
7    SecuritySession, SSOServiceQuery
8from ndg.security.common.AttAuthority import AttAuthorityClient
9from ndg.security.common.SessionMgr import SessionMgrClient, SessionExpired, \
10    AttributeRequestDenied
11from ndg.security.common.m2CryptoSSLUtility import HTTPSConnection, \
12    HostCheck, InvalidCertSignature, InvalidCertDN
13
14from base64 import urlsafe_b64decode, urlsafe_b64decode
15import logging
16
17log = logging.getLogger(__name__)
18
19class LoginController(BaseController): 
20    '''Handle NDG Login and redirect backing to requesting URL if set'''
21         
22    def __before__(self):
23        '''Set-up alias to SSO settings global'''
24        self.cfg = g.ndg.security.server.sso.cfg
25        self.state = g.ndg.security.common.sso.state
26       
27    def index(self):
28        '''Initialise Session Manager client context, check for an existing
29        user session.  If found, redirect back to SSO Client, if not found
30        present login'''
31        log.debug("LoginController.index ...")   
32
33        # Check the return to URL from the 'r' argument in the request
34        self.state.b64encReturnToURL = str(request.params.get('r', ''))
35       
36        if 'ndgSec' not in session: 
37            log.debug('No security session details found - offering login...')
38            return render('ndg.security.kid', 'ndg.security.login')
39       
40        # Session is set in this domain - check it
41        try:   
42            smClnt = SessionMgrClient(uri=session['ndgSec']['h'],
43                        tracefile=self.cfg.tracefile,
44                        httpProxyHost=self.cfg.httpProxyHost,
45                        noHttpProxyList=self.cfg.noHttpProxyList,
46                        sslCACertFilePathList=self.cfg.sslCACertFilePathList,
47                        **self.cfg.wss)
48                               
49        except Exception, e:
50            c.xml='Error establishing security context.  Please report ' + \
51                  'the error to your site administrator'
52            log.error("Initialising SessionMgrClient for " + \
53                      "getSessionStatus call: %s" % e)
54            SecuritySession.delete()
55            response.status_code = 400
56            return render('ndg.security.kid', 'ndg.security.login')
57       
58        # Check session status
59        log.debug('Calling Session Manager "%s" getSessionStatus ' % \
60                  session['ndgSec']['h'] + 'for user "%s" with sid="%s" ...'%\
61                  (session['ndgSec']['u'], session['ndgSec']['sid']))
62
63        try:
64            bSessOK = smClnt.getSessionStatus(sessID=session['ndgSec']['sid'])
65        except Exception, e:
66            c.xml = "Error checking your session details.  Please re-login"
67            log.error("Session Manager getSessionStatus: %s" % e)
68            SecuritySession.delete()
69            response.status_code = 400
70            return render('ndg.security.kid', 'ndg.security.login')
71   
72        if bSessOK:
73            log.debug("Session found - redirect back to site requesting " + \
74                      "credentials ...")
75            # ... Return across http GET passing security parameters...
76            return self._redirect()
77        else:
78            log.debug("Session wasn't found - removing security details " + \
79                      "from cookie and re-displaying login...")
80            SecuritySession.delete()
81            return render('ndg.security.kid', 'ndg.security.login')
82
83
84    def getCredentials(self):
85        """Authenticate user and cache user credentials in Session Manager
86        following user login"""
87        log.debug("LoginController.getCredentials ...")   
88
89        if 'username' not in request.params:
90            log.debug("No username set - rendering login...")
91            return render('ndg.security.kid', 'ndg.security.login')
92       
93        try:   
94            smClnt = SessionMgrClient(uri=self.cfg.smURI,
95                         tracefile=self.cfg.tracefile,
96                         httpProxyHost=self.cfg.httpProxyHost,
97                         noHttpProxyList=self.cfg.noHttpProxyList,
98                         **self.cfg.wss)
99                               
100            username = request.params['username']
101            passphrase = request.params['passphrase']                     
102                               
103        except Exception, e:
104            c.xml='Error establishing security context.  Please report ' + \
105                  'the error to your site administrator'
106            log.error("Login: initialising SessionMgrClient: %s" % e)
107            response.status_code = 400
108            return render('ndg.security.kid', 'ndg.security.login')
109       
110        # Connect to Session Manager
111        log.debug('Calling Session Manager "%s" connect for user "%s" ...' % \
112                  (self.cfg.smURI, username))
113        try:
114            sessID = smClnt.connect(username, passphrase=passphrase)[-1]
115        except Exception, e:
116            c.xml = "Error logging in.  Please check your username/" + \
117                    "pass-phrase and try again.  If the problem persists " + \
118                    "please contact your site administrator."
119            log.error("Session Manager connect returned: %s" % e)
120            response.status_code = 400
121            return render('ndg.security.kid', 'ndg.security.login')
122       
123        # Cache user attributes in Session Manager
124        log.debug("Calling Session Manager getAttCert for Attribute Authority "
125                  "[%s]" % self.cfg.aaURI)
126        try:
127            # Make request for attribute certificate
128            attCert = smClnt.getAttCert(sessID=sessID, 
129                                        attAuthorityURI=self.cfg.aaURI)
130        except SessionExpired, e:
131            log.info("Session expired getting Attribute Certificate: %s" % e)
132            c.xml = "Session has expired, please re-login"
133            response.status_code = 400
134            return render('ndg.security.kid', 'ndg.security.login')
135           
136        except AttributeRequestDenied, e:
137            log.error("Login: attribute Certificate request denied: %s" % e)
138            c.xml = "No authorisation roles are available for your " + \
139                    "account.  Please check with your site administrator."
140            response.status_code = 400
141            return render('ndg.security.kid', 'ndg.security.login')
142           
143        except Exception, e:
144            log.error("Login: attribute Certificate request: %s" % e)
145            c.xml = "An internal error occured.  Please report this to " + \
146                    "your site administrator."
147            response.status_code = 400
148            return render('ndg.security.kid', 'ndg.security.login')
149
150        log.debug('Completing login...')
151       
152        # Make security session details
153        setSecuritySession(h=self.cfg.smURI,
154                           u=username,
155                           org=attCert.issuerName,
156                           roles=attCert.roles,
157                           sid=sessID)
158        session.save()
159
160        log.debug("session = %s" % session)
161        log.info("user %s logged in with roles %s" % (session['ndgSec']['u'],
162                                                  session['ndgSec']['roles']))
163        return self._redirect()
164       
165       
166    def _redirect(self):
167        """Pass security creds back to requestor so that they can make
168        a cookie.  If the requestor is in the same domain as the login then
169        this is not necessary."""
170        log.debug("LoginController._redirect...")
171       
172        # This is set in index and getCredentials
173        if self.state.b64encReturnToURL:
174       
175            # Only add token if return URI is in a different domain
176            thisHostname = request.host.split(':')[0]
177           
178            # Decode return to address
179            returnToURL = urlsafe_b64decode(self.state.b64encReturnToURL)
180            log.debug('Login redirect to [%s]' % returnToURL)
181
182            hostnameWithPortNum = urlsplit(returnToURL)[1]
183           
184            # Require hostname with port number striped to test SSL connection
185            # (will default to 443)
186            returnToURLHostname = hostnameWithPortNum.split(':')[0]
187           
188#            if thisHostname not in returnToURLHostname:
189            if True: # Ensure return args in URL regardless
190                # Returning to a different domain - copy the security session
191                # details into the URL query string
192                if '?' in returnToURL:
193                    returnToURL += '&%s' % SSOServiceQuery()
194                else:
195                    returnToURL += '?%s' % SSOServiceQuery()
196           
197            # Check return-to address by examining peer cert
198            log.debug("Checking return-to URL for valid SSL peer cert. ...")
199
200           
201            # Look-up list of Cert DNs for trusted requestors
202            aaClnt = AttAuthorityClient(uri=self.cfg.aaURI,
203                                    tracefile=self.cfg.tracefile,
204                                    httpProxyHost=self.cfg.httpProxyHost,
205                                    noHttpProxyList=self.cfg.noHttpProxyList,
206                                    **self.cfg.wss)
207           
208            HostInfo = aaClnt.getAllHostsInfo()
209            requestServerDN = [val['loginRequestServerDN'] \
210                               for val in HostInfo.values()]
211            log.debug(\
212            "Attribute Authority [%s] expecting DN for SSL peer one of: %s" % \
213                (self.cfg.aaURI, requestServerDN))
214           
215            hostCheck = HostCheck(acceptedDNs=requestServerDN,
216                            caCertFilePathList=self.cfg.sslCACertFilePathList)
217           
218            testConnection = HTTPSConnection(returnToURLHostname, 
219                                             None, 
220                                             postConnectionCheck=hostCheck)
221
222            log.debug('Testing connection to "%s"' % returnToURLHostname)
223            try:
224                try:
225                    testConnection.connect()
226                except (InvalidCertSignature, InvalidCertDN), e:
227                    log.error("Login: requestor SSL certificate: %s" % e)
228                    c.xml = """Request to redirect back to %s with your
229credentials refused: there is a problem with the SSL certificate of this site.
230  Please report this to your site administrator.""" % returnToURLHostname
231                    response.status_code = 400
232                    return render('ndg.security.kid', 'ndg.security.login')
233            finally:   
234                testConnection.close()
235
236            log.debug("SSL peer cert. is OK - redirecting to [%s] ..." % \
237                                                                returnToURL)
238            # redirect_to doesn't like unicode
239            h.redirect_to(str(returnToURL))
240        else:
241            log.debug(\
242        "LoginController._redirect: no redirect URL set - render login page")
243            c.xml='Logged in'
244            return render('ndg.security.kid', 'ndg.security.login')
Note: See TracBrowser for help on using the repository browser.