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

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

Refactoring of SSO service to enable use of local AA and SM instances via keys to environ.

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                        environKey=self.cfg.smEnvironKey,
59                        attributeAuthorityEnvironKey=self.cfg.aaEnvironKey,
60                        tracefile=self.cfg.tracefile,
61                        httpProxyHost=self.cfg.httpProxyHost,
62                        noHttpProxyList=self.cfg.noHttpProxyList,
63                        sslCACertFilePathList=self.cfg.sslCACertFilePathList,
64                        **self.cfg.wss)                               
65        except Exception, e:
66            c.xml = ('Error establishing security context.  Please report '
67                     'the error to your site administrator')
68            log.error("Initialising SessionManagerClient for getSessionStatus "
69                      "call: %s" % e)
70            SecuritySession.delete()
71            response.status_code = 400
72            return render('ndg.security.kid', 'ndg.security.login')
73       
74        # Check session status
75        log.debug('Calling Session Manager "%s" getSessionStatus for user '
76                  '"%s" with sid="%s" ...' %
77                  (session['ndgSec']['h'], 
78                   session['ndgSec']['u'], 
79                   session['ndgSec']['sid']))
80
81        try:
82            bSessOK = smClnt.getSessionStatus(sessID=session['ndgSec']['sid'])
83        except Exception, e:
84            c.xml = "Error checking your session details.  Please re-login"
85            log.error("Session Manager getSessionStatus: %s" % e)
86            SecuritySession.delete()
87            response.status_code = 400
88            return render('ndg.security.kid', 'ndg.security.login')
89   
90        if bSessOK:
91            log.debug("Session found - redirect back to site requesting "
92                      "credentials ...")
93            # ... Return across http GET passing security parameters...
94            return self._redirect()
95        else:
96            log.debug("Session wasn't found - removing security details "
97                      "from cookie and re-displaying login...")
98            SecuritySession.delete()
99            return render('ndg.security.kid', 'ndg.security.login')
100
101
102    def getCredentials(self):
103        """Authenticate user and cache user credentials in Session Manager
104        following user login"""
105        log.debug("LoginController.getCredentials ...")   
106
107        if 'username' not in request.params:
108            log.debug("No username set - rendering login...")
109            return render('ndg.security.kid', 'ndg.security.login')
110       
111        try:   
112            smClnt = WSGISessionManagerClient(
113                        environ=request.environ,
114                        uri=self.cfg.smURI,
115                        environKey=self.cfg.smEnvironKey,
116                        attributeAuthorityEnvironKey=self.cfg.aaEnvironKey,
117                        tracefile=self.cfg.tracefile,
118                        httpProxyHost=self.cfg.httpProxyHost,
119                        noHttpProxyList=self.cfg.noHttpProxyList,
120                        **self.cfg.wss)
121                               
122            username = request.params['username']
123            passphrase = request.params['passphrase']                     
124                               
125        except Exception, e:
126            c.xml = ('Error establishing security context.  Please report '
127                     'the error to your site administrator')
128            log.error("Login: initialising WSGISessionManagerClient: %s" % e)
129            response.status_code = 400
130            return render('ndg.security.kid', 'ndg.security.login')
131       
132        # Connect to Session Manager
133        log.debug('Calling Session Manager "%s" connect for user "%s" ...' %
134                  (self.cfg.smURI, username))
135        try:
136            sessID = smClnt.connect(username, passphrase=passphrase)[-1]
137        except Exception, e:
138            c.xml = ("Error logging in.  Please check your username/"
139                     "pass-phrase and try again.  If the problem persists "
140                     "please contact your site administrator.")
141            log.error("Session Manager connect returned: %s" % e)
142            response.status_code = 400
143            return render('ndg.security.kid', 'ndg.security.login')
144       
145        # Cache user attributes in Session Manager
146        log.debug("Calling Session Manager getAttCert for Attribute Authority "
147                  "[%s]" % self.cfg.aaURI)
148        try:
149            # Make request for attribute certificate
150            attCert = smClnt.getAttCert(sessID=sessID, 
151                                        attributeAuthorityURI=self.cfg.aaURI)
152        except SessionExpired, e:
153            log.info("Session expired getting Attribute Certificate: %s" % e)
154            c.xml = "Session has expired, please re-login"
155            response.status_code = 400
156            return render('ndg.security.kid', 'ndg.security.login')
157           
158        except AttributeRequestDenied, e:
159            log.error("Login: attribute Certificate request denied: %s" % e)
160            c.xml = ("No authorisation roles are available for your "
161                    "account.  Please check with your site administrator.")
162            response.status_code = 400
163            return render('ndg.security.kid', 'ndg.security.login')
164           
165        except Exception, e:
166            log.error("Login: attribute Certificate request: %s" % e)
167            c.xml = ("An internal error occurred.  Please report this to "
168                    "your site administrator.")
169            response.status_code = 400
170            return render('ndg.security.kid', 'ndg.security.login')
171
172        log.debug('Completing login...')
173       
174        # Make security session details
175        setSecuritySession(h=self.cfg.smURI,
176                           u=username,
177                           org=attCert.issuerName,
178                           roles=attCert.roles,
179                           sid=sessID)
180        session.save()
181
182        log.debug("session = %s" % session)
183        log.info("user %s logged in with roles %s" % (session['ndgSec']['u'],
184                                                  session['ndgSec']['roles']))
185        return self._redirect()
186       
187       
188    def _redirect(self):
189        """Pass security creds back to requestor so that they can make
190        a cookie.  If the requestor is in the same domain as the login then
191        this is not necessary."""
192        log.debug("LoginController._redirect...")
193       
194        # This is set in index and getCredentials
195        if self.state.b64encReturnToURL:
196       
197            # Only add token if return URI is in a different domain
198            thisHostname = request.host.split(':')[0]
199           
200            # Decode return to address
201            returnToURL = urlsafe_b64decode(self.state.b64encReturnToURL)
202            log.debug('Login redirect to [%s]' % returnToURL)
203
204            hostnameWithPortNum = urlsplit(returnToURL)[1]
205           
206            # Require hostname with port number striped to test SSL connection
207            # (will default to 443)
208            returnToURLHostname = hostnameWithPortNum.split(':')[0]
209           
210#            if thisHostname not in returnToURLHostname:
211            if True: # Ensure return args in URL regardless
212                # Returning to a different domain - copy the security session
213                # details into the URL query string
214                if '?' in returnToURL:
215                    returnToURL += '&%s' % SSOServiceQuery()
216                else:
217                    returnToURL += '?%s' % SSOServiceQuery()
218           
219            # Check return-to address by examining peer cert
220            log.debug("Checking return-to URL for valid SSL peer cert. ...")
221
222           
223            # Look-up list of Cert DNs for trusted requestors
224            aaClnt = WSGIAttributeAuthorityClient(
225                                    environ=request.environ,
226                                    uri=self.cfg.aaURI,
227                                    environKey=self.cfg.aaEnvironKey,
228                                    tracefile=self.cfg.tracefile,
229                                    httpProxyHost=self.cfg.httpProxyHost,
230                                    noHttpProxyList=self.cfg.noHttpProxyList,
231                                    **self.cfg.wss)
232           
233            HostInfo = aaClnt.getAllHostsInfo()
234            requestServerDN = [val['loginRequestServerDN']
235                               for val in HostInfo.values()]
236            log.debug("Attribute Authority [%s] expecting DN for SSL peer "
237                      "one of: %s" % (self.cfg.aaURI, requestServerDN))
238           
239            hostCheck = HostCheck(acceptedDNs=requestServerDN,
240                            caCertFilePathList=self.cfg.sslCACertFilePathList)
241           
242            testConnection = HTTPSConnection(returnToURLHostname, 
243                                             None, 
244                                             postConnectionCheck=hostCheck)
245
246            log.debug('Testing connection to "%s"' % returnToURLHostname)
247            try:
248                try:
249                    testConnection.connect()
250                except (InvalidCertSignature, InvalidCertDN), e:
251                    log.error("Login: requestor SSL certificate: %s" % e)
252                    c.xml = ("Request to redirect back to %s with your "
253                             "credentials refused: there is a problem with "
254                             "the SSL certificate of this site.  Please "
255                             "report this to your site administrator." % 
256                             returnToURLHostname)
257                    response.status_code = 400
258                    return render('ndg.security.kid', 'ndg.security.login')
259            finally:   
260                testConnection.close()
261
262            log.debug("SSL peer cert. is OK - redirecting to [%s] ..." %
263                                                                returnToURL)
264            # redirect_to doesn't like unicode
265            h.redirect_to(str(returnToURL))
266        else:
267            log.debug("LoginController._redirect: no redirect URL set - "
268                      "render login page")
269            c.xml='Logged in'
270            return render('ndg.security.kid', 'ndg.security.login')
Note: See TracBrowser for help on using the repository browser.