source: TI05-delivery/ows_framework/trunk/ows_server/ows_server/controllers/login.py @ 3056

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI05-delivery/ows_framework/trunk/ows_server/ows_server/controllers/login.py@3056
Revision 3056, 11.6 KB checked in by pjkersha, 13 years ago (diff)

Important fix for ticket #883 - ensures cookie at users login site is kept in sync with their Session Manager.

ows_server/ndgDiscovery.config: default SM URI goes through Apache now

ows_server/ows_server/controllers/login.py: added call to SessionMgr?.getSessionStatus in LoginController?.index. This checks the users session and if not found on the Session Manager, offers re-login. This is a likely scenario where the user logs off at a remote site removing their session from the Session Manager but leave stale security session cookie details on their home site.

ows_server/ows_server/lib/security_util.py: fix to LoginServiceQuery? - raise new LoginServiceQueryError? type exception

Line 
1import sys,cgi
2from urlparse import urlsplit, urlunsplit
3import base64
4
5from ows_server.lib.base import *
6from ows_server.lib.security_util import setSecuritySession, LoginServiceQuery
7from ows_common.exception_report import OwsError
8from paste.request import parse_querystring
9import logging
10log = logging.getLogger(__name__)
11
12from ndg.security.common.AttAuthority import AttAuthorityClient
13from ndg.security.common.SessionMgr import SessionMgrClient, SessionExpired, \
14    AttributeRequestDenied
15from ndg.security.common.m2CryptoSSLUtility import HTTPSConnection, \
16    HostCheck, InvalidCertSignature, InvalidCertDN
17
18
19class LoginController(BaseController):
20    ''' Provides the pylons controller for local login '''
21   
22    def __before__(self, action): 
23        """For each action, get 'r' return to URL argument from current URL
24        query string.  c.returnTo is used in some of the .kid files"""
25        c.returnTo = request.params.get('r', '')
26       
27        # Check return to address - getCredentials should NOT be returned to
28        # with its query args intact
29        b64decReturnTo = base64.urlsafe_b64decode(c.returnTo)
30        scheme, netloc, pathInfo, query, frag = urlsplit(b64decReturnTo)
31        if 'getCredentials' in pathInfo:
32            # Swap to discovery and remove sensitive creds query args
33            #
34            # TODO: re-write to be more robust and modular.  Nb.
35            # BaseController.__call__ should filter out 'getCredentials'
36            # calls from c.requestURL so this code should never need to be
37            # executed.
38            filteredReturnTo = urlunsplit((scheme,netloc,'/discovery','',''))
39            c.returnTo = base64.urlsafe_b64encode(filteredReturnTo)
40       
41        # Check return to address - getCredentials should NOT be returned to
42        # with its query args intact
43        log.debug("LoginController.__before__: Decoded c.returnTo = %s" % \
44                                      base64.urlsafe_b64decode(c.returnTo))
45   
46   
47    def index(self):
48        ''' Ok, you really want to login here '''
49        log.debug("LoginController.index ...")   
50
51        if 'ndgSec' not in session: 
52            log.debug('No security session details found - offering login...')
53            return render_response('login')
54       
55        # Session is set in this domain - check it
56        try:   
57            smClnt = SessionMgrClient(uri=session['ndgSec']['h'],
58                    sslCACertFilePathList=g.securityCfg.sslCACertFilePathList,
59                    sslPeerCertCN=g.securityCfg.sslPeerCertCN,
60                    signingCertFilePath=g.securityCfg.wssCertFilePath,
61                    signingPriKeyFilePath=g.securityCfg.wssPriKeyFilePath,
62                    signingPriKeyPwd=g.securityCfg.wssPriKeyPwd,
63                    caCertFilePathList=g.securityCfg.wssCACertFilePathList,
64                    tracefile=g.securityCfg.tracefile)
65                               
66        except Exception, e:
67            c.xml='Error establishing security context [%s]'%cgi.escape(str(e))
68            return Response(render('content'), code=400)
69       
70        # Check session status
71        log.debug('Calling Session Manager "%s" getSessionStatus ' % \
72                  session['ndgSec']['h'] + 'for user "%s" with sid="%s" ...'%\
73                  (session['ndgSec']['u'], session['ndgSec']['sid']))
74        try:
75            bSessOK = smClnt.getSessionStatus(sessID=session['ndgSec']['sid'])
76        except Exception, e:
77            c.xml = "Error checking your session details.  Please re-login"
78            log.error("Session Manager getSessionStatus returned: %s" % e)
79        return Response(render('login'), code=401)
80   
81        if bSessOK:
82            log.debug(\
83        "Session found - redirect back to site requesting credentials ...")
84            # ... Return across http GET passing security parameters...
85            return self.__doRedirect()
86        else:
87            log.debug("Session wasn't found - re-displaying login...")
88            render_response('login')
89
90
91    def getCredentials(self):
92        """Authenticate user and cache user credentials in
93        Session Manager following user login"""
94        log.debug("LoginController.getCredentials ...")   
95
96        try:   
97            smClnt = SessionMgrClient(uri=g.securityCfg.smURI,
98                    sslCACertFilePathList=g.securityCfg.sslCACertFilePathList,
99                    sslPeerCertCN=g.securityCfg.sslPeerCertCN,
100                    signingCertFilePath=g.securityCfg.wssCertFilePath,
101                    signingPriKeyFilePath=g.securityCfg.wssPriKeyFilePath,
102                    signingPriKeyPwd=g.securityCfg.wssPriKeyPwd,
103                    caCertFilePathList=g.securityCfg.wssCACertFilePathList,
104                    tracefile=g.securityCfg.tracefile)
105                               
106            username = request.params['username']
107            passphrase = request.params['passphrase']                     
108                               
109        except Exception, e:
110            c.xml='Error establishing security context [%s]'%cgi.escape(str(e))
111            return Response(render('content'), code=400)
112       
113        # Connect to Session Manager
114        log.debug('Calling Session Manager "%s" connect for user "%s" ...' % \
115                  (g.securityCfg.smURI, username))
116        try:
117            sessID = smClnt.connect(username, passphrase=passphrase)[-1]
118        except Exception, e:
119            c.xml = \
120    "Error logging in.  Please check your username/pass-phrase and try again."
121            log.error("Session Manager connect returned: %s" % e)
122            return Response(render('login'), code=401)
123       
124        # Cache user attributes in Session Manager
125        log.debug("Calling Session Manager getAttCert for user ")
126        try:
127            # Make request for attribute certificate
128            attCert = smClnt.getAttCert(sessID=sessID, 
129                                        attAuthorityURI=g.securityCfg.aaURI)
130        except SessionExpired, e:
131            log.info("Session expired getting Attribute Certificate: %s" % e)
132            c.xml = "Session has expired, please re-login"
133            return Response(render('login'), code=401)
134           
135        except AttributeRequestDenied, e:
136            log.error("Login: attribute Certificate request denied: %s" % e)
137            c.xml = "No authorisation roles are available for your " + \
138                    "account.  Please check with your site administrator."
139            return Response(render('login'), code=401)
140           
141        except Exception, e:
142            log.error("Login: attribute Certificate request: %s" % e)
143            c.xml = "An internal error occured.  Please report this to " + \
144                    "your site administrator."
145            return Response(render('login'), code=400)
146
147        log.debug('Completing login...')
148       
149        # Make security session details
150        setSecuritySession(h=g.securityCfg.smURI,
151                           u=username,
152                           org=attCert.issuerName,
153                           roles=attCert.roles,
154                           sid=sessID)
155        session['panelView']='History'
156        session.save()
157
158        log.info("user %s logged in with roles %s" % (session['ndgSec']['u'],
159                                                  session['ndgSec']['roles']))
160        return self.__doRedirect()
161           
162           
163    def wayf(self):
164        ''' NDG equivalent to Shibboleth WAYF '''
165        log.debug("LoginController.wayf ...")   
166
167        # May be better as a 'g' global set-up at start-up?
168        #
169        # tracefile could be removed for production use
170        aaClnt = AttAuthorityClient(uri=g.securityCfg.aaURI,
171                    signingCertFilePath=g.securityCfg.wssCertFilePath,
172                    signingPriKeyFilePath=g.securityCfg.wssPriKeyFilePath,
173                    signingPriKeyPwd=g.securityCfg.wssPriKeyPwd,
174                    caCertFilePathList=g.securityCfg.wssCACertFilePathList,
175                    tracefile=g.securityCfg.tracefile)
176
177        # Get list of login uris for trusted sites including THIS one
178        log.debug("Calling Attribute Authority getTrustedHostInfo and " + \
179                  "getHostInfo for wayf")
180
181        hosts = aaClnt.getAllHostsInfo()   
182        c.providers=dict([(k, v['loginURI']) for k, v in hosts.items()])
183       
184        if 'panelView' in session: del session['panelView']
185        session.save()
186       
187        return render_response('wayf')
188       
189       
190    def __doRedirect(self):
191        """Pass security creds back to requestor so that they can make
192        a cookie.  If the requestor is in the same domain as the login then
193        this is not necessary."""
194       
195        # and now go back to whence we had come
196        if c.returnTo!='':
197            # is there a keyword on redirect_to that can make this https? See:
198            # http://pylonshq.com/project/pylonshq/browser/Pylons/trunk/pylons/decorators/secure.py#L69
199
200            # Only add token if return URI is in a different domain
201            thisHostname = request.host.split(':')[0]
202           
203            # Decode return to address
204            cc = base64.urlsafe_b64decode(c.returnTo)
205            log.debug('Login redirect to [%s]' % cc)
206
207            returnToHostname = urlsplit(cc)[1]
208#            returnToHostname = 'localhost'
209#            if thisHostname not in returnToHostname:
210            if True:
211                # Returning to a different domain - copy the security session
212                # details into the URL query string
213                if '?' in cc:
214                    cc+='&%s' % LoginServiceQuery()
215                else:
216                    cc+='?%s' % LoginServiceQuery()
217           
218            # Check return-to address by examining peer cert
219            log.debug("Checking return-to URL for valid SSL peer cert. ...")
220           
221            # Look-up list of Cert DNs for trusted requestors
222            aaClnt = AttAuthorityClient(uri=g.securityCfg.aaURI,
223                    signingCertFilePath=g.securityCfg.wssCertFilePath,
224                    signingPriKeyFilePath=g.securityCfg.wssPriKeyFilePath,
225                    signingPriKeyPwd=g.securityCfg.wssPriKeyPwd,
226                    caCertFilePathList=g.securityCfg.wssCACertFilePathList,
227                    tracefile=g.securityCfg.tracefile)
228           
229            HostInfo = aaClnt.getAllHostsInfo()
230            requestServerDN = [val['loginRequestServerDN'] \
231                               for val in HostInfo.values()]
232            log.debug("Expecting DN for SSL peer one of: %s"%requestServerDN)
233            hostCheck=HostCheck(acceptedDNs=requestServerDN,
234                    caCertFilePathList=g.securityCfg.sslCACertFilePathList)           
235            testConnection = HTTPSConnection(returnToHostname, 
236                                             None, 
237                                             postConnectionCheck=hostCheck)
238
239            log.debug('Testing connection to "%s"' % returnToHostname)
240            try:
241                try:
242                    testConnection.connect()
243                except (InvalidCertSignature, InvalidCertDN), e:
244                    log.error("Login: requestor SSL certificate: %s" % e)
245                    c.xml = """Request to redirect back to %s with your
246credentials refused: there is a problem with the SSL certificate of this site.
247  Please report this to your site administrator.""" % returnToHostname
248                    return Response(render('login'), code=400)
249            finally:   
250                testConnection.close()
251
252            log.debug("SSL peer cert. is OK - redirecting to [%s] ..." % cc)
253            h.redirect_to(cc)
254        else:
255            c.xml='<p> Logged in </p>'
256            return render_response('content')
Note: See TracBrowser for help on using the repository browser.