source: TI12-security/trunk/python/NDG/SecurityCGI.py @ 1007

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/NDG/SecurityCGI.py@1007
Revision 1007, 18.7 KB checked in by pjkersha, 13 years ago (diff)

Tests/security.py: derived class from SecurityCGI for testing x domain cookie functionality.

  • Property svn:executable set to *
Line 
1#!/usr/bin/env python
2
3"""NDG Security CGI services
4
5NERC Data Grid Project
6
7P J Kershaw 23/05/06
8
9Copyright (C) 2006 CCLRC & NERC
10
11This software may be distributed under the terms of the Q Public License,
12version 1.0 or later.
13"""
14from Cookie import SimpleCookie
15
16import sys
17import cgi
18import os
19
20from NDG.SecurityClient import *
21from NDG.Session import UserSession
22from NDG.Session import UserSessionError
23
24
25class SecurityCGIError(Exception):
26    """Exception handling for NDG Security CGI class."""
27   
28    def __init__(self, msg):
29        self.__msg = msg
30         
31    def __str__(self):
32        return self.__msg
33
34   
35class SecurityCGI(cgi.FieldStorage):
36    """CGI interface class for NDG Security
37   
38    Terms used throughout:       
39        remote site     - where user is accessing resources
40        home site       - where user's credentials are held or they login"""
41
42    #_________________________________________________________________________
43    def __init__(self,
44                 smWSDL,
45                 userName=None,
46                 passPhrase=None,
47                 smPubKeyFilePath=None,
48                 clntPubKeyFilePath=None,
49                 clntPriKeyFilePath=None,
50                 clntPriKeyPwd=None,
51                 aaPubKey=None,
52                 scriptName=None,
53                 returnURI=None, 
54                 trustedHosts=None,
55                 wsDebug=False,
56                 **cgiFieldStorageKwArgs):
57        """scriptName:        name of script to call in forms - defaults to
58                              this file.  Modify if you inherit from this
59                              class.
60        smWSDL:    URI For Session Manager WSDL used for user
61                              authentication
62        returnURI:            the address to redirect back to following a
63                              redirect 
64                              to the user's home site to obtain their
65                              credentials
66        trustedHosts:         dictionary of URIs for trusted hosts indexed by
67                              hostname
68        wsDebug:              print output from WS transactions to stderr"""
69       
70        self.smWSDL = smWSDL
71        self.userName = userName
72        self.passPhrase = passPhrase
73        self.smPubKeyFilePath = smPubKeyFilePath
74        self.clntPubKeyFilePath = clntPubKeyFilePath
75        self.clntPriKeyFilePath = clntPriKeyFilePath
76        self.clntPriKeyPwd = clntPriKeyPwd
77        self.__aaPubKey = aaPubKey
78       
79        if scriptName:
80            self.scriptName = scriptName
81        else:
82            self.scriptName = __file__
83           
84        self.returnURI = returnURI
85        self.trustedHosts = trustedHosts
86        self.__wsDebug = False
87        self.__authorisationMethod = None
88       
89        cgi.FieldStorage.__init__(self, **cgiFieldStorageKwArgs)
90
91 
92    #_________________________________________________________________________
93    def processFields(self, **kwargs):
94        """Call appropriate actions according to the fields set"""
95       
96        if 'requestURI' in self:
97            # Request credentials from user's home site
98            self.requestCreds(**kwargs)
99   
100        elif 'NDG-ID1' in self and 'NDG-ID2' in self and 'expires' in self:
101            # Receive credentials back from home site and set a new cookie at
102            # remote site
103            self.receiveCredsResponse(self['NDG-ID1'].value, 
104                                      self['NDG-ID2'].value,
105                                      self['NDG-ID2']['expires'], 
106                                      **kwargs)
107   
108        elif 'authenticate' in self:
109            # User has logged on at home site and a cookie is now set -
110            # next step is processCredsRequest() below
111            sessCookie = self.authenticate()
112           
113            if 'returnURI' in self:
114                # Authentication is required following a redirect from
115                # another site - redirect back to the remote site returning
116                # the cookie information
117                self.processCredsRequest(sessCookie=sessCookie,
118                                         setCookie=True, 
119                                         **kwargs)
120               
121        elif 'returnURI' in self:
122            # Home site receives request from remote site for credentials and
123            # returns them
124            self.processCredsRequest(**kwargs)
125        else:
126            # Remote site presents possible sites for user to get their
127            # credentials from
128            self.showHomeSiteSelect(**kwargs)
129   
130   
131    #_________________________________________________________________________
132    # Use instance name as an alias to processFields method
133    __call__ = processFields
134   
135
136    #_________________________________________________________________________
137    def requestCreds(self,
138                     requestURI=None,
139                     pageTitle='',
140                     headTags='',
141                     delayTime=0,
142                     redirectMsg=''):
143        """Request credentials from a user's home site
144       
145        requestURI:   site to request credentials from - default is
146                      'requestURI' CGI form value
147        pageTitle:    Give the redirect page a title
148        headTags:     Optionally add additional tags in <head/> section
149        delayTime:    time in seconds before carrying out redirect - redirect
150                      page will be displayed in this interval
151        redirectMsg:  Message to put on redirect page.  Can be plain text or
152                      formatted HTML"""
153       
154        if requestURI is None:
155            requestURI = self['requestURI'].value
156           
157        print """Content-type: text/html
158   
159<html>
160<head>
161<title>%s</title>
162<meta http-equiv="REFRESH" content="%d; url=%s?returnURI=%s">
163%s
164</head>
165<body>
166%s
167</body>
168</html>""" % \
169    (pageTitle, delayTime, requestURI, self.returnURI, headTags, redirectMsg)
170   
171   
172    #_________________________________________________________________________
173    def receiveCredsResponse(self, 
174                             sessID,
175                             sessMgrURI, 
176                             expiry,
177                             pageTitle='',
178                             hdrTxt='',
179                             bodyTxt=''):
180        """Remote site receives returned credentials and creates a new cookie
181        for its domain"""
182   
183        sessCookie = UserSession.createSecurityCookie(sessID,
184                                                      sessMgrURI,
185                                                      expiryStr=expiry)
186   
187        print """Content-type: text/html"
188%s
189
190<html>
191<head>
192<title>%s</title>
193%s
194</head>
195<body>
196    %s
197</body>
198</html>""" % (sessCookie.output(), pageTitle, hdrTxt, bodyTxt)
199   
200   
201    #_________________________________________________________________________
202    def processCredsRequest(self, 
203                            returnURI=None, 
204                            sessCookie=None, 
205                            **returnCredsKwArgs):
206        """Receive request from remote site for credentials.  Process and
207        return via a redirect"""
208
209        if returnURI is None:
210            returnURI = self['returnURI'].value
211                                                         
212        # Check for cookie in environment
213        if 'HTTP_COOKIE' in os.environ:
214   
215            # Get session ID from existing cookie
216            sessCookie = SimpleCookie(os.environ['HTTP_COOKIE'])
217
218       
219        if sessCookie:
220            # Cookie is set - check for NDG cookie
221            try:
222                UserSession.isValidSecurityCookie(sessCookie, raiseExcep=True)
223               
224            except UserSessionError, e:
225                raise SecurityCGIError, \
226                                    'Checking existing session cookie: %s' % e
227
228            # Return cookie to requestor
229            self.returnCreds(sessCookie, returnURI, **returnCredsKwArgs)
230           
231        else:
232            # No cookie present - display login.  Submit must redirect back to
233            # this script with '?authenticate=1&returnURI=<...>'
234            self.showLogin(returnURI=returnURI,
235                           setCookie=True,
236                           contentTypeHdr=True,
237                           pageTitle="NDG Login",
238                           htmlTag=True,
239                           bodyTag=True)
240
241   
242    #_________________________________________________________________________
243    def returnCreds(self,
244                    sessCookie, 
245                    returnURI=None,
246                    pageTitle='',
247                    hdrTxt='',
248                    delayTime=0,
249                    redirectMsg='',
250                    setCookie=False):
251        """User's home site returns credentials to requestor via a HTTP
252        redirect
253       
254        sessCookie:   NDG Session cookie
255        pageTitle:    Give the redirect page a title
256        headTags:     Optionally add additional tags in <head/> section
257        delayTime:    time in seconds before carrying out redirect - redirect
258                      page will be displayed in this interval
259        redirectMsg:  Message to put on redirect page.  Can be plain text or
260                      formatted HTML"""
261
262        if returnURI is None:
263            returnURI = self['returnURI'].value
264                                         
265        if setCookie:
266            cookieTxt = sessCookie.output() + os.line_sep()
267        else:
268            cookieTxt = ''
269           
270        print """Content-type: text/html
271%s   
272<html>
273<head>
274<title>%s</title>
275<meta http-equiv="REFRESH"
276content="%d; url=%s?NDG-ID1=%s&NDG-ID2=%s&expires=%s">
277%s
278</head>
279<body>
280%s
281</body>
282</html>""" % ( cookieTxt,
283               pageTitle, 
284               delayTime, 
285               returnURI, 
286               sessCookie['NDG-ID1'].value,
287               sessCookie['NDG-ID2'].value,
288               base64.b64encode(sessCookie['NDG-ID1']['expires']),
289               redirectMsg)   
290   
291   
292    #_________________________________________________________________________
293    def authenticate(self, bAuthorise=False):
294        """Authenticate username and passphrase input from preceeding login
295        form
296
297        bAuthorise: set to True so that if an error occurs, login will be
298                    recalled followed by authorisation"""
299
300        if self.__wsDebug:
301            traceFile = sys.stderr
302        else:
303            traceFile = None
304
305
306        if 'userName' in self:
307            self.userName = self['userName'].value
308           
309        if 'passPhrase' in self:
310            self.passPhrase = self['passPhrase'].value
311           
312           
313        if self.userName is None:
314            raise SecurityCGIError("no username set for authentication")
315
316        if self.passPhrase is None:
317            raise SecurityCGIError("no pass-phrase set for authentication")
318
319
320        # Instantiate WS proxy and request connection
321        try:
322            smClient = SessionClient(
323                                smWSDL=self.smWSDL,
324                                smPubKeyFilePath=self.smPubKeyFilePath,
325                                clntPubKeyFilePath=self.clntPubKeyFilePath,
326                                clntPriKeyFilePath=self.clntPriKeyFilePath,
327                                traceFile=traceFile)
328
329            return smClient.connect(userName=self.userName,
330                                    pPhrase=self.passPhrase,
331                                    clntPriKeyPwd=self.clntPriKeyPwd)
332        except Exception, e:
333            raise SecurityCGIError("Session client: " + str(e))
334           
335   
336    #_________________________________________________________________________
337    def showLogin(self,
338                  returnURI=None,
339                  contentTypeHdr=False,
340                  htmlTag=False,
341                  pageTitle='',
342                  hdrTxt='',
343                  headTag=False,
344                  bodyTag=False,
345                  bAuthorise=False):
346        """Display initial NDG login form"""
347   
348        if contentTypeHdr: print "Content-type: text/html\n\n"
349       
350        if htmlTag: print "<html>"
351   
352        if headTag:
353            if not hdrTxt:
354                hdrTxt = """    <style type=\"text/css\">
355<!--
356.al {
357text-align: justify
358}
359a{
360text-decoration:none;
361}
362a:hover{
363color:#0000FF;
364}
365    body { font-family: Verdana, sans-serif; font-size: 11}
366    table { font-family: Verdana, sans-serif; font-size: 11}
367-->
368</style>"""
369                   
370            print """<head>
371        <title>%s</title>
372        %s
373    </head>""" % (pageTitle, hdrTxt)
374   
375   
376        if bodyTag: print "<body>"
377   
378        if returnURI:
379            returnURIfield = \
380                "<input type=hidden name=returnURI value=\"%s\">" % returnURI
381        else:
382            returnURIfield = ''
383       
384   
385        if bAuthorise:
386            authoriseArg = "<input type=hidden name=authorise value=\"1\">"
387        else:
388            authoriseArg = ""
389   
390   
391        # Set authorisation method default
392        authorisationMethodChk = {  "allowMapping":              '',
393                                    "allowMappingWithPrompt" :   '',
394                                    "noMapping":                 ''}
395   
396        if self.__authorisationMethod is None:
397            # Default to safest option for user
398            authorisationMethodChk["allowMappingWithPrompt"] = ' checked'
399        else:
400            authorisationMethodChk[self.__authorisationMethod] = ' checked'
401   
402        print \
403    """<script language="javascript">
404    <!--
405        function toggleLayer(layerId)
406        {
407            if (document.getElementById)
408            {
409                // Standard
410                var style = document.getElementById(layerId).style;
411            }
412            else if (document.all)
413            {
414                // Old msie versions
415                var style = document.all[whichLayer].style;
416            }
417            else if (document.layers)
418            {
419                // nn4
420                var style = document.layers[whichLayer].style;
421            }
422            style.visibility = style.visibility == "visible" ? "hidden":"visible";
423        }
424    //-->
425    </script>
426    <h3>NERC Data Grid Site Login (Test)<BR clear=all></h3>
427    <hr>
428   
429    <form action="%s" method="POST">
430   
431    <table bgcolor=#ADD8E6 cellspacing=0 border=0 cellpadding=5>
432    <tbody>
433    <tr><td>User Name:</td> <td><input type=text name=userName value="">
434    </td></tr>
435    <tr>
436        <td>Password:</td>
437        <td><input type=password name=passPhrase></td>
438    </tr>
439    <tr>
440        <td colspan="2" align="right">
441            <a href="javascript:toggleLayer('advSettings');">Advanced Settings</a>
442            <input type=submit value="Login">
443        </td>
444    </tr>
445    <input type=hidden name=authenticate value="1">
446    %s"""  % (self.scriptName, returnURIfield)
447   
448        print \
449    """</tbody></table>
450    <br>
451    <div id="advSettings" style="position: relative; visibility: hidden;">
452        <h4>Role Mapping for access to other trusted sites</h4>
453        <p>Your account has roles or <i>privileges</i> which determine what data you have access to.  If you access data at another NDG trusted site, these roles can be mapped to local roles at that site to help you gain access:
454        </p>
455        <table bgcolor=#ADD8E6 cellspacing=0 border=0 cellpadding=5>
456        <tbody>
457        <tr>
458        <td>
459            <input type="radio" name="authorisationMethod" value="allowMapping"%s>
460        </td>
461            <td>
462                Allow my roles to be mapped to local roles at other NDG trusted sites.
463            </td>
464        </tr>
465        <tr>
466            <td>
467                <input type="radio" name="authorisationMethod" value="allowMappingWithPrompt"%s>
468            </td>
469        <td>
470            Allow my roles to be mapped, but prompt me so that I may choose which roles to map before gaining access.
471        </td>
472        <tr>
473        <td>
474            <input type="radio" name="authorisationMethod" value="noMapping"%s>
475        </td>
476        <td>
477            Don't allow mapping of my roles.
478        </td>
479        </tr>
480        </tbody>
481        </table>
482    </div>
483    </form>
484    """ % (authorisationMethodChk['allowMapping'], \
485           authorisationMethodChk['allowMappingWithPrompt'], \
486           authorisationMethodChk['noMapping'])
487   
488        if bodyTag: print "</body>"
489        if htmlTag: print "</html>"
490   
491        # end of showLogin()
492   
493   
494    def showHomeSiteSelect(self,
495                           trustedHosts=None,
496                           scriptName=None,
497                           contentTypeHdr=False,
498                           htmlTag=False,
499                           hdrTag=False,
500                           hdrTxt='',
501                           bodyTag=False,
502                           pageTitle=""):
503
504        if trustedHosts:
505            self.trustedHosts = trustedHosts
506       
507        if scriptName:
508            self.scriptName = scriptName
509           
510               
511        if contentTypeHdr:
512            print "Content-type: text/html\n\n"
513               
514        if htmlTag:
515            print "<html>\n"
516
517        if hdrTag:           
518            if not hdrTxt:
519                hdrTxt = """    <style type=\"text/css\">
520<!--
521.al {
522text-align: justify
523}
524a{
525text-decoration:none;
526}
527a:hover{
528color:#0000FF;
529}
530    body { font-family: Verdana, sans-serif; font-size: 11}
531    table { font-family: Verdana, sans-serif; font-size: 11}
532-->
533</style>"""
534
535            print """<head>
536           
537    <title>%s</title>
538    %s
539</head>""" % (pageTitle, hdrTxt)
540   
541   
542        if bodyTag:
543            print "<body>\n"
544
545            print """
546    <form action="%s" method="POST">
547    <table bgcolor=#ADD8E6 cellspacing=0 border=0 cellpadding=5>
548    <tbody>
549    <tr>
550      <td>
551        <select name="requestURI">       
552          <option value="">Select your home site...""" % self.scriptName
553         
554            for hostname, uri in trustedHosts.items():
555                print "<option value=\"%s\">%s" % (uri, hostname)
556               
557            print \
558"""        </select>
559      </td>
560      <td align="right">
561        <input type=submit value="Go">
562      </td>
563    </tr>
564    </tbody>
565    </table>
566    </form>"""
567
568        if bodyTag:
569            print "</body>\n"
570
571        if htmlTag:
572            print "</html>\n"
573   
574        # end of showHomeSiteSelect()
575       
576       
577if __name__ == "__main__":
578    clntPubKeyFilePath = "../certs/GabrielCGI-cert.pem"
579    clntPriKeyFilePath = "../certs/GabrielCGI-key.pem"
580
581    securityCGI = SecurityCGI("http://gabriel.bnsc.rl.ac.uk/sessionMgr.wsdl",
582                 smPubKeyFilePath="/usr/local/NDG/conf/certs/gabriel-sm-cert.pem,
583                 clntPubKeyFilePath=clntPubKeyFilePath,
584                 clntPriKeyFilePath=clntPriKeyFilePath,
585                 returnURI="https://gabriel.bnsc.rl.ac.uk/cgi-bin/security.py", 
586                 trustedHosts=None)
587    securityCGI()
Note: See TracBrowser for help on using the repository browser.