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

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@4680
Revision 4680, 11.9 KB checked in by pjkersha, 11 years ago (diff)

Global replace to fix copyright from STFC & NERC to STFC alone because it's not possible to have copyright held by two orgs.

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