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

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

sso: refactoring redirects from NDG Browse version.

Line 
1import logging
2
3from sso.lib.base import *
4from sso.lib.security_util import setSecuritySession, SecuritySession, \
5    LoginServiceQuery
6from ndg.security.common.AttAuthority import AttAuthorityClient
7from ndg.security.common.SessionMgr import SessionMgrClient, SessionExpired, \
8    AttributeRequestDenied
9from ndg.security.common.m2CryptoSSLUtility import HTTPSConnection, \
10    HostCheck, InvalidCertSignature, InvalidCertDN
11
12from base64 import urlsafe_b64decode, urlsafe_b64decode
13
14log = logging.getLogger(__name__)
15
16class LoginController(BaseController):
17   
18#    def __before__(self, action):
19#        """For each action, get 'r' return to URL argument from current URL
20#        query string.  c.returnTo is used in some of the .kid files"""
21#        log.debug("LoginController.__before__ ...")   
22#
23#        c.returnTo = request.params.get('r', '')
24       
25#        # Check return to address - getCredentials should NOT be returned to
26#        # with its query args intact
27#        b64decReturnTo = base64.urlsafe_b64decode(c.returnTo)
28#        scheme, netloc, pathInfo, query, frag = urlsplit(b64decReturnTo)
29#        if 'getCredentials' in pathInfo:
30#            # Swap to discovery and remove sensitive creds query args
31#            #
32#            # TODO: re-write to be more robust and modular.  Nb.
33#            # BaseController.__call__ should filter out 'getCredentials'
34#            # calls from c.requestURL so this code should never need to be
35#            # executed.
36#            filteredReturnTo = urlunsplit((scheme,netloc,'/login','',''))
37#            c.returnTo = base64.urlsafe_b64encode(filteredReturnTo)
38#       
39#        # Check return to address - getCredentials should NOT be returned to
40#        # with its query args intact
41#        log.debug("LoginController.__before__: Decoded c.returnTo = %s" % \
42#                                      base64.urlsafe_b64decode(c.returnTo))
43       
44       
45    def index(self):
46        ''' '''
47        log.debug("LoginController.index ...")   
48
49        # Check the return to URL
50        c.b64encReturnTo = str(request.params.get('r', ''))
51       
52        if 'ndgSec' not in session: 
53            log.debug('No security session details found - offering login...')
54            return render('ndg.security.login')
55
56        # Inclusive namespace prefixes for WS-Security digital signature
57        # (Exclusive C14N only)
58        refC14nKw={'unsuppressedPrefixes':g.securityCfg.wssRefInclNS}
59        signedInfoC14nKw = {'unsuppressedPrefixes':
60                            g.securityCfg.wssSignedInfoInclNS}
61       
62        # Session is set in this domain - check it
63        try:   
64            smClnt = SessionMgrClient(uri=session['ndgSec']['h'],
65                    sslCACertFilePathList=g.securityCfg.sslCACertFilePathList,
66                    sslPeerCertCN=g.securityCfg.sslPeerCertCN,
67                    signingCertFilePath=g.securityCfg.wssCertFilePath,
68                    signingPriKeyFilePath=g.securityCfg.wssPriKeyFilePath,
69                    signingPriKeyPwd=g.securityCfg.wssPriKeyPwd,
70                    caCertFilePathList=g.securityCfg.wssCACertFilePathList,
71                    refC14nKw=refC14nKw,
72                    signedInfoC14nKw=signedInfoC14nKw,
73                    tracefile=g.securityCfg.tracefile)
74                               
75        except Exception, e:
76            c.xml='Error establishing security context.  Please report ' + \
77                  'the error to your site administrator'
78            log.error("Initialising SessionMgrClient for " + \
79                      "getSessionStatus call: %s" % e)
80            SecuritySession.delete()
81            response.status_code = 400
82            return render('ndg.security.login')
83       
84        # Check session status
85        log.debug('Calling Session Manager "%s" getSessionStatus ' % \
86                  session['ndgSec']['h'] + 'for user "%s" with sid="%s" ...'%\
87                  (session['ndgSec']['u'], session['ndgSec']['sid']))
88        try:
89            bSessOK = smClnt.getSessionStatus(sessID=session['ndgSec']['sid'])
90        except Exception, e:
91            c.xml = "Error checking your session details.  Please re-login"
92            log.error("Session Manager getSessionStatus returned: %s" % e)
93            SecuritySession.delete()
94            response.status_code = 400
95            return render('ndg.security.login')
96   
97        if bSessOK:
98            log.debug("Session found - redirect back to site requesting " + \
99                      "credentials ...")
100            # ... Return across http GET passing security parameters...
101            return self._redirect()
102        else:
103            log.debug("Session wasn't found - removing security details " + \
104                      "from cookie and re-displaying login...")
105            SecuritySession.delete()
106            return render('ndg.security.login')
107
108
109    def getCredentials(self):
110        """Authenticate user and cache user credentials in
111        Session Manager following user login"""
112        log.debug("LoginController.getCredentials ...")   
113
114        # Check the return to URL
115        c.b64encReturnTo = str(request.params.get('r', ''))
116
117        if 'username' not in request.params:
118            log.debug("No username set - rendering login...")
119            return render('ndg.security.login')
120       
121        # Inclusive namespace prefixes for WS-Security digital signature
122        # (Exclusive C14N only)
123        refC14nKw={'unsuppressedPrefixes':g.securityCfg.wssRefInclNS}
124        signedInfoC14nKw = {'unsuppressedPrefixes':
125                            g.securityCfg.wssSignedInfoInclNS}
126
127        try:   
128            smClnt = SessionMgrClient(uri=g.securityCfg.smURI,
129                    sslCACertFilePathList=g.securityCfg.sslCACertFilePathList,
130                    sslPeerCertCN=g.securityCfg.sslPeerCertCN,
131                    signingCertFilePath=g.securityCfg.wssCertFilePath,
132                    signingPriKeyFilePath=g.securityCfg.wssPriKeyFilePath,
133                    signingPriKeyPwd=g.securityCfg.wssPriKeyPwd,
134                    caCertFilePathList=g.securityCfg.wssCACertFilePathList,
135                    refC14nKw=refC14nKw,
136                    signedInfoC14nKw=signedInfoC14nKw,
137                    tracefile=g.securityCfg.tracefile)
138                               
139            username = request.params['username']
140            passphrase = request.params['passphrase']                     
141                               
142        except Exception, e:
143            c.xml='Error establishing security context.  Please report ' + \
144                  'the error to your site administrator'
145            log.error("Login: initialising SessionMgrClient: %s" % e)
146            response.status_code = 400
147            return render('ndg.security.login')
148       
149        # Connect to Session Manager
150        log.debug('Calling Session Manager "%s" connect for user "%s" ...' % \
151                  (g.securityCfg.smURI, username))
152        try:
153            sessID = smClnt.connect(username, passphrase=passphrase)[-1]
154        except Exception, e:
155            c.xml = "Error logging in.  Please check your username/" + \
156                    "pass-phrase and try again."
157            log.error("Session Manager connect returned: %s" % e)
158            raise e
159            response.status_code = 401
160            return render('ndg.security.login')
161       
162        # Cache user attributes in Session Manager
163        log.debug("Calling Session Manager getAttCert for user ")
164        try:
165            # Make request for attribute certificate
166            attCert = smClnt.getAttCert(sessID=sessID, 
167                                        attAuthorityURI=g.securityCfg.aaURI)
168        except SessionExpired, e:
169            log.info("Session expired getting Attribute Certificate: %s" % e)
170            c.xml = "Session has expired, please re-login"
171            response.status_code = 401
172            return render('ndg.security.login')
173           
174        except AttributeRequestDenied, e:
175            log.error("Login: attribute Certificate request denied: %s" % e)
176            c.xml = "No authorisation roles are available for your " + \
177                    "account.  Please check with your site administrator."
178            response.status_code = 401
179            return render('ndg.security.login')
180           
181        except Exception, e:
182            log.error("Login: attribute Certificate request: %s" % e)
183            c.xml = "An internal error occured.  Please report this to " + \
184                    "your site administrator."
185            response.status_code = 400
186            return render('ndg.security.login')
187
188        log.debug('Completing login...')
189       
190        # Make security session details
191        setSecuritySession(h=g.securityCfg.smURI,
192                           u=username,
193                           org=attCert.issuerName,
194                           roles=attCert.roles,
195                           sid=sessID)
196        session.save()
197
198        log.info("user %s logged in with roles %s" % (session['ndgSec']['u'],
199                                                  session['ndgSec']['roles']))
200        return self._redirect()
201       
202       
203    def _redirect(self):
204        """Pass security creds back to requestor so that they can make
205        a cookie.  If the requestor is in the same domain as the login then
206        this is not necessary."""
207        log.debug("LoginController._redirect...")
208       
209        # This is set in index and getCredentials
210        if c.b64encReturnTo:
211       
212            # Only add token if return URI is in a different domain
213            thisHostname = request.host.split(':')[0]
214           
215            # Decode return to address
216            returnToURL = urlsafe_b64decode(c.b64encReturnTo)
217            log.debug('Login redirect to [%s]' % returnToURL)
218
219            returnToURLHostname = urlsplit(returnToURL)[1]
220#            returnToURLHostname = 'localhost'
221#            if thisHostname not in returnToURLHostname:
222            if True:
223                # Returning to a different domain - copy the security session
224                # details into the URL query string
225                if '?' in returnToURL:
226                    returnToURL += '&%s' % LoginServiceQuery()
227                else:
228                    returnToURL += '?%s' % LoginServiceQuery()
229           
230            # Check return-to address by examining peer cert
231            log.debug("Checking return-to URL for valid SSL peer cert. ...")
232
233            # Inclusive namespace prefixes for WS-Security digital signature
234            # (Exclusive C14N only)
235            refC14nKw={'unsuppressedPrefixes':g.securityCfg.wssRefInclNS}
236            signedInfoC14nKw = {'unsuppressedPrefixes':
237                                g.securityCfg.wssSignedInfoInclNS}
238           
239            # Look-up list of Cert DNs for trusted requestors
240            aaClnt = AttAuthorityClient(uri=g.securityCfg.aaURI,
241                        signingCertFilePath=g.securityCfg.wssCertFilePath,
242                        signingPriKeyFilePath=g.securityCfg.wssPriKeyFilePath,
243                        signingPriKeyPwd=g.securityCfg.wssPriKeyPwd,
244                        caCertFilePathList=g.securityCfg.wssCACertFilePathList,
245                        refC14nKw=refC14nKw,
246                        signedInfoC14nKw=signedInfoC14nKw,
247                        tracefile=g.securityCfg.tracefile)
248           
249            HostInfo = aaClnt.getAllHostsInfo()
250            requestServerDN = [val['loginRequestServerDN'] \
251                               for val in HostInfo.values()]
252            log.debug(\
253            "Attribute Authority [%s] expecting DN for SSL peer one of: %s" % \
254                      (g.securityCfg.aaURI, requestServerDN))
255            hostCheck=HostCheck(acceptedDNs=requestServerDN,
256                    caCertFilePathList=g.securityCfg.sslCACertFilePathList)           
257            testConnection = HTTPSConnection(returnToURLHostname, 
258                                             None, 
259                                             postConnectionCheck=hostCheck)
260
261            log.debug('Testing connection to "%s"' % returnToURLHostname)
262            try:
263                try:
264                    testConnection.connect()
265                except (InvalidCertSignature, InvalidCertDN), e:
266                    log.error("Login: requestor SSL certificate: %s" % e)
267                    c.xml = """Request to redirect back to %s with your
268credentials refused: there is a problem with the SSL certificate of this site.
269  Please report this to your site administrator.""" % returnToURLHostname
270                    response.status_code = 400
271                    return render('ndg.security.login')
272            finally:   
273                testConnection.close()
274
275            log.debug("SSL peer cert. is OK - redirecting to [%s] ..." % \
276                                                                returnToURL)
277            # redirect_to doesn't like unicode
278            h.redirect_to(str(returnToURL))
279        else:
280            log.debug(\
281        "LoginController._redirect: no redirect URL set - render login page")
282            c.xml='Logged in'
283            return render('ndg.security.login')
Note: See TracBrowser for help on using the repository browser.