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

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@3699
Revision 3699, 13.2 KB checked in by pjkersha, 12 years ago (diff)

Separated LoginService? into server and client components sso and ssoClient respectively. Packaging needs re-organising.

  • Made important fix to redirect_to - this doesn't work when called from a BaseController?.call in Pylons 0.9.6 - moved to before method instead.
Line 
1import logging
2
3from sso.lib.base import *
4from ndg.security.common.pylons.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            hostnameWithPortNum = urlsplit(returnToURL)[1]
220           
221            # Require hostname with port number striped to test SSL connection
222            # (will default to 443)
223            returnToURLHostname = hostnameWithPortNum.split(':')[0]
224           
225#            if thisHostname not in returnToURLHostname:
226            if True: # Ensure return args in URL regardless
227                # Returning to a different domain - copy the security session
228                # details into the URL query string
229                if '?' in returnToURL:
230                    returnToURL += '&%s' % LoginServiceQuery()
231                else:
232                    returnToURL += '?%s' % LoginServiceQuery()
233           
234            # Check return-to address by examining peer cert
235            log.debug("Checking return-to URL for valid SSL peer cert. ...")
236
237            # Inclusive namespace prefixes for WS-Security digital signature
238            # (Exclusive C14N only)
239            refC14nKw = {'unsuppressedPrefixes':g.securityCfg.wssRefInclNS}
240            signedInfoC14nKw = {'unsuppressedPrefixes':
241                                g.securityCfg.wssSignedInfoInclNS}
242           
243            # Look-up list of Cert DNs for trusted requestors
244            aaClnt = AttAuthorityClient(uri=g.securityCfg.aaURI,
245                        signingCertFilePath=g.securityCfg.wssCertFilePath,
246                        signingPriKeyFilePath=g.securityCfg.wssPriKeyFilePath,
247                        signingPriKeyPwd=g.securityCfg.wssPriKeyPwd,
248                        caCertFilePathList=g.securityCfg.wssCACertFilePathList,
249                        refC14nKw=refC14nKw,
250                        signedInfoC14nKw=signedInfoC14nKw,
251                        tracefile=g.securityCfg.tracefile)
252           
253            HostInfo = aaClnt.getAllHostsInfo()
254            requestServerDN = [val['loginRequestServerDN'] \
255                               for val in HostInfo.values()]
256            log.debug(\
257            "Attribute Authority [%s] expecting DN for SSL peer one of: %s" % \
258                      (g.securityCfg.aaURI, requestServerDN))
259            hostCheck=HostCheck(acceptedDNs=requestServerDN,
260                    caCertFilePathList=g.securityCfg.sslCACertFilePathList)           
261            testConnection = HTTPSConnection(returnToURLHostname, 
262                                             None, 
263                                             postConnectionCheck=hostCheck)
264
265            log.debug('Testing connection to "%s"' % returnToURLHostname)
266            try:
267                try:
268                    testConnection.connect()
269                except (InvalidCertSignature, InvalidCertDN), e:
270                    log.error("Login: requestor SSL certificate: %s" % e)
271                    c.xml = """Request to redirect back to %s with your
272credentials refused: there is a problem with the SSL certificate of this site.
273  Please report this to your site administrator.""" % returnToURLHostname
274                    response.status_code = 400
275                    return render('ndg.security.login')
276            finally:   
277                testConnection.close()
278
279            log.debug("SSL peer cert. is OK - redirecting to [%s] ..." % \
280                                                                returnToURL)
281            # redirect_to doesn't like unicode
282            h.redirect_to(str(returnToURL))
283        else:
284            log.debug(\
285        "LoginController._redirect: no redirect URL set - render login page")
286            c.xml='Logged in'
287            return render('ndg.security.login')
Note: See TracBrowser for help on using the repository browser.