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

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

Added a Policy Information Point to encapsulate subject attribute retrieval.

Line 
1"""Single Sign On Service Login Controller
2
3NERC DataGrid Project
4"""
5__author__ = "P J Kershaw"
6__date__ = "10/12/08"
7__copyright__ = "(C) 2009 Science and Technology Facilities Council"
8__license__ = "BSD - see LICENSE file in top-level directory"
9__contact__ = "Philip.Kershaw@stfc.ac.uk"
10__revision__ = '$Id$'
11import logging
12log = logging.getLogger(__name__)
13
14# _redirect requires this to parse the server name
15from urlparse import urlsplit
16
17
18from ndg.security.server.sso.sso.lib.base import *
19from ndg.security.common.pylons.security_util import setSecuritySession, \
20    SecuritySession, SSOServiceQuery
21from ndg.security.server.wsgi.utils.attributeauthorityclient import \
22    WSGIAttributeAuthorityClient
23from ndg.security.server.wsgi.utils.sessionmanagerclient import \
24    WSGISessionManagerClient, SessionExpired, AttributeRequestDenied
25from ndg.security.common.m2CryptoSSLUtility import HTTPSConnection, \
26    HostCheck, InvalidCertSignature, InvalidCertDN
27
28from base64 import urlsafe_b64decode, urlsafe_b64decode
29
30class LoginController(BaseController): 
31    '''Handle NDG Login and redirect backing to requesting URL if set'''
32         
33    def __before__(self):
34        '''Set-up alias to SSO settings global'''
35        self.cfg = g.ndg.security.server.sso.cfg
36        self.state = g.ndg.security.common.sso.state
37       
38    def index(self):
39        '''Initialise Session Manager client context, check for an existing
40        user session.  If found, redirect back to SSO Client, if not found
41        present login'''
42        log.debug("LoginController.index ...")   
43
44        # Check the return to URL from the 'r' argument in the request
45        self.state.b64encReturnToURL = str(request.params.get('r', ''))
46       
47        if 'ndgSec' not in session: 
48            log.debug('No security session details found - offering login...')
49            return render('ndg.security.kid', 'ndg.security.login')
50       
51        # Session is set in this domain - check it
52        try:   
53            smClnt = WSGISessionManagerClient(
54                    environ=request.environ,
55                    uri=session['ndgSec']['h'],
56                    environKeyName=self.cfg.smEnvironKeyName,
57                    attributeAuthorityEnvironKeyName=self.cfg.aaEnvironKeyName,
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            c.loggedIn=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 c.loggedIn:
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                    environKeyName=self.cfg.smEnvironKeyName,
114                    attributeAuthorityEnvironKeyName=self.cfg.aaEnvironKeyName,
115                    tracefile=self.cfg.tracefile,
116                    httpProxyHost=self.cfg.httpProxyHost,
117                    noHttpProxyList=self.cfg.noHttpProxyList,
118                    **self.cfg.wss)
119                               
120            username = request.params['username']
121            passphrase = request.params['passphrase']                     
122                               
123        except Exception, e:
124            c.xml = ('Error establishing security context.  Please report '
125                     'the error to your site administrator')
126            log.error("Login: initialising WSGISessionManagerClient: %s" % e)
127            response.status_code = 400
128            return render('ndg.security.kid', 'ndg.security.login')
129       
130        # Connect to Session Manager
131        log.debug('Calling Session Manager "%s" connect for user "%s" ...' %
132                  (self.cfg.smURI, username))
133        try:
134            sessID = smClnt.connect(username, passphrase=passphrase)[-1]
135        except Exception, e:
136            c.xml = ("Error logging in.  Please check your username/"
137                     "pass-phrase and try again.  If the problem persists "
138                     "please contact your site administrator.")
139            log.error("Session Manager connect returned: %s" % e)
140            response.status_code = 400
141            return render('ndg.security.kid', 'ndg.security.login')
142       
143        # Cache user attributes in Session Manager
144        log.debug("Calling Session Manager getAttCert for Attribute Authority "
145                  "[%s]" % self.cfg.aaURI)
146        try:
147            # Make request for attribute certificate
148            attCert = smClnt.getAttCert(sessID=sessID, 
149                                        attributeAuthorityURI=self.cfg.aaURI)
150        except SessionExpired, e:
151            log.info("Session expired getting Attribute Certificate: %s" % e)
152            c.xml = "Session has expired, please re-login"
153            response.status_code = 400
154            return render('ndg.security.kid', 'ndg.security.login')
155           
156        except AttributeRequestDenied, e:
157            log.error("Login: attribute Certificate request denied: %s" % e)
158            c.xml = ("No authorisation roles are available for your "
159                    "account.  Please check with your site administrator.")
160            response.status_code = 400
161            return render('ndg.security.kid', 'ndg.security.login')
162           
163        except Exception, e:
164            log.error("Login: attribute Certificate request: %s" % e)
165            c.xml = ("An internal error occurred.  Please report this to "
166                    "your site administrator.")
167            response.status_code = 400
168            return render('ndg.security.kid', 'ndg.security.login')
169
170        log.debug('Completing login...')
171       
172        c.loggedIn = True
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                                    environKeyName=self.cfg.aaEnvironKeyName,
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.