source: security/trunk/python/Tests/security.py @ 489

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/security/trunk/python/Tests/security.py@489
Revision 489, 20.9 KB checked in by pjkersha, 14 years ago (diff)

Working version for authorisation via mapped certificates.

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
Line 
1#!/usr/local/NDG/ActivePython-2.4/bin/python
2"""NDG CGI security script
3 
4NERC Data Grid Project
5 
6P J Kershaw 14/09/05
7 
8Copyright (C) 2005 CCLRC & NERC
9 
10This software may be distributed under the terms of the Q Public License,
11version 1.0 or later.
12"""
13import os
14import sys
15import cgi
16import re
17
18# Catch socket errors
19import socket
20
21from Cookie import SimpleCookie
22from ZSI import ServiceProxy
23
24try:
25    from NDG.AttCert import *
26   
27except ImportError, e:
28    # Temporary Hack - try getting from development area instead
29    sys.path.append('/home/users/pjkersha/Development/security/python')
30    from NDG.AttCert import *
31
32
33#_____________________________________________________________________________
34class SecurityCGIError(Exception):
35
36    def __init__(self, msg):
37
38        self.__msg = msg
39
40    def __str__(self):
41        return self.__msg
42
43
44#_____________________________________________________________________________
45class SecurityCGI:
46    """CGI for NDG authentication and authorisation"""
47   
48    def __init__(self,
49                 smWSDL,
50                 aaWSDL,
51                 userName=None,
52                 passPhrase=None,
53                 org=None):
54        """Omit username, passphrase and org if running from CGI"""
55       
56        self.__aaWSDL = aaWSDL   
57        self.__smWSDL = smWSDL   
58        self.__userName = userName
59        self.__passPhrase = passPhrase
60
61        # Authenticating organisation
62        self.__org = org
63
64        # Flag taken from form radio button decides whether mapping is
65        # allowed or not and if so, should the user be prompted for which
66        # attribute certificate to submit
67        self.__authorisationMethod = None
68
69        self.__attCert = None
70       
71       
72    #_________________________________________________________________________
73    def cgi(self):       
74        """Two stage process - login followed by authentication.  If
75        authentication fails re-call login."""
76
77        # Use userName field to flag authentication call       
78        form = cgi.FieldStorage()
79        bAuthorise = form.has_key("authorise")
80
81        #sys.stderr.write("Form keys: %s\n" % ', '.join(form.keys()))
82
83        if form.has_key("extTrustedHost"):
84            extTrustedHost = form["extTrustedHost"].value
85        else:
86            extTrustedHost = ''
87               
88        if form.has_key("authorisationMethod"):
89            self.__authorisationMethod = form["authorisationMethod"].value
90
91
92        if form.has_key("addUser"):
93
94            # Register new account
95            if not form.has_key("userName") or not form["userName"].value:
96                raise SecurityCGIError("No username set")
97           
98            if not form.has_key("passPhrase") or not form["passPhrase"].value:
99                raise SecurityCGIError("No pass-phrase set")
100           
101            if not form.has_key("confirmPassPhrase") or \
102               not form["confirmPassPhrase"].value:
103                raise SecurityCGIError("No confirmation pass-phrase set")
104
105            if form["passPhrase"].value != form["confirmPassPhrase"].value:
106                raise SecurityCGIError(\
107                    "Pass-phrase and confirmation pass-phrase don't agree")
108               
109            self.__userName = form["userName"].value           
110            self.__passPhrase = form["passPhrase"].value
111
112            self.addUser()
113           
114        elif form.has_key("login") or not form.keys():
115           
116            # Login
117            print "Content-type: text/html" + os.linesep
118            self.showLogin(bAuthorise=bAuthorise, bodyTag=True)
119           
120        elif form.has_key("authenticate"):
121
122            # Authentication
123            if form.has_key("userName") and form["userName"].value:
124                self.__userName = form["userName"].value
125           
126            if form.has_key("passPhrase") and form["passPhrase"].value:
127                self.__passPhrase = form["passPhrase"].value
128               
129            if form.has_key("org") and form["org"].value:
130                self.__org = form["org"].value
131
132            if bAuthorise:
133                                   
134                # Authorisation and authentication arguments were set
135                # - Call authentication first
136                sessID = self.authenticate(setCookie=False)
137
138                # Call authorisation passing the session ID for authorise to
139                # set the cookie
140                self.authorise(sessID, extTrustedHost=extTrustedHost)
141
142            else:
143                # Only the authentication flag was set - Call authentication
144                # and set cookie
145                self.authenticate()
146               
147        elif bAuthorise:
148            self.authorise(extTrustedHost=extTrustedHost)
149
150        elif form.has_key("showAC"):
151            self.showAttCert()
152           
153        else:
154            raise SecurityCGIError(\
155                            "None of the Form keys were recognised: %s" % \
156                            ', '.join(form.keys()))
157           
158
159    #_________________________________________________________________________
160    def showLogin(self,
161                  bAuthorise=False,
162                  htmlTag=False,
163                  heading=None,
164                  bodyTag=False):
165        """Display initial NDG login form"""
166        if htmlTag: print "<html>"
167       
168        if isinstance(heading, basestring):
169            print "<head>"
170            print "    <title>%s</title>" % heading
171            print "</head>"
172           
173        if bodyTag: print "<body>"
174
175        if bAuthorise:
176            authoriseArg = "<input type=hidden name=authorise value=\"1\">"                                       
177        else:
178            authoriseArg = ""
179
180
181        # Set authorisation method default
182        authorisationMethodChk = { "allowMapping":              '',
183                                   "allowMappingWithPrompt" :   '',
184                                   "noMapping":                 ''}
185       
186        if self.__authorisationMethod is None:
187            # Default to safest option for user
188            authorisationMethodChk["allowMappingWithPrompt"] = ' checked'
189        else:
190            authorisationMethodChk[self.__authorisationMethod] = ' checked'
191       
192        print \
193"""<script language="javascript">
194<!--
195    function toggleLayer(layerId)
196    {
197        if (document.getElementById)
198        {
199            // Standard
200            var style = document.getElementById(layerId).style;
201        }
202        else if (document.all)
203        {
204            // Old msie versions
205            var style = document.all[whichLayer].style;
206        }
207        else if (document.layers)
208        {
209            // nn4
210            var style = document.layers[whichLayer].style;
211        }
212        style.visibility = style.visibility == "visible" ? "hidden":"visible";
213    }
214//-->
215</script>
216<h2>Login to the NERC Data Grid (Test)<BR clear=all></h2>
217<hr>
218
219<form action="./security.cgi" method="POST">
220
221<table bgcolor=#ADD8E6 cellspacing=0 border=0 cellpadding=5>
222
223<tr><td>User Name:</td> <td><input type=text name=userName value="">@
224<select name="org">
225    <option>BADC</option>
226    <option>BODC</option>
227    <option>PML</option>
228    <option>SOC</option>
229</td></tr>
230<tr>
231    <td>Password:</td>
232    <td><input type=password name=passPhrase></td>
233</tr>
234<tr>
235    <td colspan="2" align="right">
236        <a href="javascript:toggleLayer('advSettings');">Advanced Settings</a>
237        <input type=submit value="Login">
238    </td>
239</tr>
240<input type=hidden name=authenticate value="1">
241%s"""  % authoriseArg
242       
243        print \
244"""</table>
245<div id="advSettings" style="position: relative; visibility: hidden;">
246    <p>If you are not registered with this data centre it is possible to
247    access the data via any accounts you hold at other data centres:
248    </p>
249    <input type="radio" name="authorisationMethod"
250     value="allowMapping"%(allowMapping)s>
251        Use accounts I hold at other data centres to help gain access to the
252        data.<br>
253    <input type="radio" name="authorisationMethod"
254        value="allowMappingWithPrompt"%(allowMappingWithPrompt)s>
255        Use other accounts I hold, but allow me to choose which account to
256        use.<br>
257    <input type="radio" name="authorisationMethod"
258     value="noMapping"%(noMapping)s>
259        Try access without using any other accounts I hold.<br>
260</div>
261</form>
262""" % authorisationMethodChk
263       
264        if bodyTag: print "</body>"
265        if htmlTag: print "</html>"
266
267    # end of showLogin()
268   
269   
270    #_________________________________________________________________________
271    def addUser(self, bDebug=False):
272        """Add a new NDG User account"""
273
274        if self.__userName is None:
275            raise SecurityCGIError("No username set")
276       
277        if self.__passPhrase is None:
278            raise SecurityCGIError("No passphrase set")
279
280        if bDebug:
281            traceFile = sys.stderr
282        else:
283            traceFile = None
284
285           
286        try:
287            # Instantiate WS proxy and request connection
288            try:
289                smSrv = ServiceProxy(self.__smWSDL,
290                                     use_wsdl=True,
291                                     tracefile=traceFile)               
292
293                resp = smSrv.addUser(userName=self.__userName,
294                                     passPhrase=self.__passPhrase)
295            except socket.error, e:
296                # Socket error returns tuple - reformat to just give msg
297                raise SecurityCGIError(str(e[1]))
298           
299            if resp['errMsg']:
300                raise SecurityCGIError(str(resp['errMsg']))
301
302            print \
303"""Content-type: text/html
304
305<head>
306    <title>NDG User Registration (Test)</title>
307</head>
308
309<body>
310    <p>New user %s registered</p>
311</body>""" % self.__userName
312       
313        except Exception, e:
314            # Re-display login screen
315            print \
316"""Content-type: text/html
317
318<head>
319    <title>NDG User Registration (Test)</title>
320</head>
321
322<body>
323    <p>Registration failed for new user account %s: %s</p>""" % \
324            (self.__userName, e)
325           
326            raise SecurityCGIError("User registration failed: %s" % e)
327   
328   
329    #_________________________________________________________________________
330    def authenticate(self, setCookie=True, bAuthorise=False, bDebug=False):
331        """Authenticate username and passphrase input from preceeding login
332        form
333
334        bAuthorise: set to True so that if an error occurs, login will be
335                    recalled followed by authorisation"""
336
337        if bDebug:
338            traceFile = sys.stderr
339        else:
340            traceFile = None
341
342           
343        try:
344            if self.__userName is None:
345                raise SecurityCGIError("no username set")
346           
347            if self.__passPhrase is None:
348                raise SecurityCGIError("no pass-phrase input")
349           
350            # Instantiate WS proxy and request connection
351            try:
352                smSrv = ServiceProxy(self.__smWSDL,
353                                     use_wsdl=True,
354                                     tracefile=traceFile)
355           
356                resp = smSrv.connect(userName=self.__userName,
357                                     passPhrase=self.__passPhrase,
358                                     tracefile=sys.stderr)               
359            except socket.error, e:
360                # Socket error returns tuple - reformat to just give msg
361                raise SecurityCGIError(str(e[1]))
362
363            if resp['errMsg']:
364                raise SecurityCGIError(str(resp['errMsg']))
365
366            sessID = str(resp['sessID'])
367
368            if setCookie:
369                # Make a cookie       
370                cookie = SimpleCookie()
371                cookie['Hash'] = sessID
372               
373                print \
374"""Content-type: text/html
375%s
376
377<head>
378    <title>NDG User Authentication (Test)</title>
379</head>
380
381<body>
382    <p>User %s authenticated</p>
383    <p>Cookie is: %s</p>
384</body>""" % (cookie.output(), self.__userName, sessID)
385
386            return sessID
387       
388        except Exception, e:
389            # Re-display login screen
390            if self.__userName is None:
391                msgFmt = ''
392            else:
393                msgFmt = " for user '%s'" % self.__userName
394               
395            print \
396"""Content-type: text/html
397
398<head>
399    <title>NDG User Authentication (Test)</title>
400</head>
401
402<body>"""           
403            self.showLogin(bAuthorise=bAuthorise)
404            print \
405"""<script>alert("Login error%s: %s")</script>
406</body>""" % (msgFmt, e)
407           
408            raise SecurityCGIError("Login failed: %s" % e)
409
410
411    #_________________________________________________________________________
412    def authorise(self,
413                  sessID=None,
414                  reqRole='nercFunded',
415                  extTrustedHost='',
416                  bDebug=True):
417        """Contact Attribute Authority to get Attribute Certificate for data
418        access
419
420        sessID:     session ID of user session, if omitted, use cookie
421        reqRole:    required role to get authorisation - default to NERC for
422                    testing"""
423
424        if bDebug:
425            traceFile = sys.stderr
426        else:
427            traceFile = None
428
429
430        if extTrustedHost:
431            extTrustedHostList = [extTrustedHost]
432        else:
433            extTrustedHostList = ''
434
435
436        extAttCertList = None   
437        bSetCookie = False
438       
439        try:
440            # Check for session ID input
441            if isinstance(sessID, basestring):
442
443                # Set cookie
444                cookie = SimpleCookie()
445                cookie['Hash'] = sessID
446                bSetCookie = True
447               
448            else:
449                # Check for session ID set in existing cookie
450                if 'HTTP_COOKIE' not in os.environ:
451                   
452                    # Re-display login screen
453                    print "Content-type: text/html" + os.linesep                   
454                    self.showLogin(bAuthorise=True,
455                                   bodyTag=True,
456                                   heading="NDG User Authorisation (Test)")
457
458                    return
459
460                # Get session ID from existing cookie
461                sessID = SimpleCookie(os.environ['HTTP_COOKIE'])['Hash'].value
462
463
464            if self.__authorisationMethod == 'allowMapping':
465                bMapFromTrustedHosts = True               
466            else:
467                bMapFromTrustedHosts = False
468
469
470            # Instantiate WS proxy and request authorisation
471            try:
472                smSrv = ServiceProxy(self.__smWSDL,
473                                     use_wsdl=True,
474                                     tracefile=traceFile)
475
476                resp = smSrv.reqAuthorisation(aaWSDL=self.__aaWSDL,
477                                    sessID=sessID,
478                                    reqRole=reqRole,
479                                    mapFromTrustedHosts=bMapFromTrustedHosts,
480                                    extAttCertList='',
481                                    extTrustedHostList=extTrustedHostList)
482            except socket.error, e:
483                # Socket error returns tuple - reformat to just give msg
484                raise SecurityCGIError(str(e[1]))
485
486            if resp['statCode'] == 'AccessGranted':
487                # Convert from unicode
488                self.__attCert = str(resp['attCert'])
489           
490            elif resp['statCode'] == 'AccessDenied':
491               
492                if not resp['extAttCertList']:
493                    raise SecurityCGIError(str(resp['errMsg']))
494                                           
495                # Convert from unicode
496                extAttCertList = [str(attCert) \
497                                  for attCert in resp['extAttCertList']]
498
499            elif resp['statCode'] == 'AccessError':
500                raise SecurityCGIError(str(resp['errMsg']))
501
502
503            # Handle access denied/granted
504            if bSetCookie:
505                cookieTxt = cookie.output() + os.linesep
506            else:
507                cookieTxt = ''
508               
509            print \
510"""Content-type: text/html
511%s
512
513<head>
514    <Title>NDG User Authorisation (Test)</Title>
515</head>
516
517<body>""" % cookieTxt
518
519            if self.__attCert:
520               
521                # Get data using certificate obtained
522                print "<p>User authorised</p>"
523                print "<p>Attribute Certificate: <br>%s</p>" % \
524                      re.sub("<", "&lt;", re.sub(">", "&gt;", self.__attCert))
525               
526            elif extAttCertList:
527                # Display available certificates from other AAs in a table
528                self.showExtAttCertSelect(extAttCertList)
529               
530            print "</body>" 
531
532        except Exception, e:
533                       
534            # Re-display login screen
535            print \
536"""Content-type: text/html
537
538<head>
539    <title>NDG User Authorisation (Test)</title>
540</head>
541
542<body>"""
543           
544            self.showLogin(bAuthorise=True)
545            print \
546"""<script>alert("Authorisation failed: %s")</script>
547</body>""" % e
548           
549            raise SecurityCGIError("Authorisation failed: %s" % e)
550   
551
552    def showAttCert(self, attCert=None):
553        """Make a page to display Attribute Certificate"""
554        if attCert is not None:
555            self.__attCert = attCert
556
557
558       
559        if self.__attCert is None:
560            print \
561"""Content-type: text/html
562
563<head>
564    <title>NDG User Authorisation (Test)</title>
565</head>
566
567<body>
568    <p>No Attribute Certificate set</p>
569</body>"""
570            return
571       
572        print \
573"""Content-type: text/xml
574
575%s""" % self.__attCert
576           
577
578    #_________________________________________________________________________
579    def showExtAttCertSelect(self,
580                             extAttCertList,
581                             htmlTag=False,
582                             heading=None,
583                             bodyTag=False):
584        """Display table for selection of external attribute certificates for
585        mapping"""
586        if htmlTag: print "<html>"
587       
588        if isinstance(heading, basestring):
589            print "<head>"
590            print "    <title>%s</title>" % heading
591            print "</head>"
592
593        sys.stderr.write("extAttCertList: \n\n%s\n" % extAttCertList)
594       
595        # Display title and table first row
596        #
597        # Form contains hidden fields so that on submit, authorisation is
598        # called to get authorisation via a mapped certificate
599        print \
600"""<h2>NDG Data Access</h2>
601<hr style="width: 100%; height: 2px;"><br>
602<p>Select a certificate to allow access to data:</p>
603<form action="./security.cgi" method="post">
604    <input type=hidden name=authorise value="1">
605    <input type=hidden name=authorisationMethod value="allowMapping">
606    <table style="width: 100%;" border="0" cellpadding="10"
607    cellspacing="1">
608        <tbody>
609        <tr bgcolor="#d5d5de">
610            <td style="text-align: left; vertical-align: top;">
611                <br>
612            </td>
613            <td style="width: 10px; text-align: left; vertical-align: top;">
614                <span style="font-weight: bold;">Issuer</span>
615            </td>
616            <td style="text-align: left; vertical-align: top;">
617                <span style="font-weight: bold;">Available Roles</span>
618            </td>
619        </tr>"""
620
621        # Display available certificates - one in each row
622        chkTxt = ['' for i in range(len(extAttCertList))]
623        chkTxt[0] = ' checked'
624       
625        for sCert in extAttCertList:
626
627            cert = AttCertParse(sCert)
628
629            # Nb. hidden field authorisationMethod set to allowMapping so that
630            # authorisation request can be made again but this time with the
631            # s
632            print """
633        <tr bgcolor="#e2e2e2">
634            <td style="vertical-align: top;">
635                <input type="radio" name="extTrustedHost"
636                value="%s"%s><br>
637            </td>
638            <td style="width: 20px;" valign="top">
639                %s<br>
640            </td>
641            <td valign="top" width="80%%">
642                %s<br>
643            </td>
644        </tr>
645""" % (cert['issuerName'],
646       chkTxt.pop(),
647       cert['issuerName'],
648       ', '.join(cert.getRoles()))
649
650        print \
651"""        <tr bgcolor="#d5d5de">
652            <td colspan="3" align="right">
653                <input type=submit value="   OK   ">
654                <input type=button value="Cancel"
655                 onClick="javascript:window.close();">
656            </td>
657        </tr>
658        </tbody>
659    </table>
660</form>
661"""
662       
663        if bodyTag: print "</body>"
664        if htmlTag: print "</html>"
665
666    # end of showExtAttCertSelect()
667
668 
669#_____________________________________________________________________________
670if __name__ == "__main__":
671   
672    # Instantiate and call CGI
673    security = SecurityCGI("http://glue.badc.rl.ac.uk/sessionMgr.wsdl",
674                           #"../html/sessionMgr.wsdl",
675                           "http://glue.badc.rl.ac.uk/attAuthority.wsdl")
676                           #"../html/attAuthority.wsdl")
677    security.cgi()
Note: See TracBrowser for help on using the repository browser.