Changeset 1005


Ignore:
Timestamp:
24/05/06 20:03:22 (13 years ago)
Author:
pjkersha
Message:

NDG/SecurityCGI.py: rationalised from original code taken from xDomainCredTransfer.py. Needs
testing.

NDG/Session.py: added class methods to UserSession? for NDG security sessino cookie handling.

createSecurityCookie - makes a new cookie
isValidSecuirtyCookie - checks an existing cookie to see if it has the write tags for an NDG
security cookie.

These have been made class methods so that they can by called without making a UserSession?
instance. This is useful for creating a new cookie from an existing one as is needed when
cookie information is communicated across domains.

Location:
TI12-security/trunk/python/NDG
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • TI12-security/trunk/python/NDG/SecurityCGI.py

    r986 r1005  
    1212version 1.0 or later. 
    1313""" 
    14  
    15 from NDG.SecurityClient import * 
    1614from Cookie import SimpleCookie 
    1715 
     
    2018import os 
    2119 
    22  
     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    
    2335class SecurityCGI(cgi.FieldStorage): 
    24     """CGI interface class for NDG Security""" 
     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""" 
    2541 
    2642    #_________________________________________________________________________ 
    2743    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, 
    2852                 scriptName=None, 
    29                  sessMgrWSDLuri=None, 
    3053                 returnURI=None,  
    31                  trustedHosts=None,  
    32                  **kwargs): 
     54                 trustedHosts=None, 
     55                 wsDebug=False, 
     56                 **cgiFieldStorageKwArgs): 
    3357        """scriptName:        name of script to call in forms - defaults to 
    3458                              this file.  Modify if you inherit from this 
    3559                              class. 
    36         sessMgrWSDLuri:    URI For Session Manager WSDL used for user  
     60        smWSDL:    URI For Session Manager WSDL used for user  
    3761                              authentication 
    3862        returnURI:            the address to redirect back to following a  
     
    4165                              credentials 
    4266        trustedHosts:         dictionary of URIs for trusted hosts indexed by 
    43                               hostname""" 
     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 
    4478         
    4579        if scriptName: 
     
    4882            self.scriptName = __file__ 
    4983             
    50         self.sessMgrWSDLuri = sessMgrWSDLuri 
    5184        self.returnURI = returnURI 
    5285        self.trustedHosts = trustedHosts 
     86        self.__wsDebug = False 
    5387        self.__authorisationMethod = None 
    5488         
    55         cgi.FieldStorage.__init__(self, **kwargs) 
     89        cgi.FieldStorage.__init__(self, **cgiFieldStorageKwArgs) 
    5690 
    5791  
     
    6498            self.requestCreds(**kwargs) 
    6599     
    66         elif 'NDG-ID1' in self and 'NDG-ID2' in self: 
    67             # Receive credentials back from home site 
     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 
    68103            self.receiveCredsResponse(self['NDG-ID1'].value,  
    69                                       self['NDG-ID2'].value) 
    70      
    71         elif 'setCookie' in self and 'returnURI' in self: 
    72             # User has logged on at home site and a cookie is now to be set -  
     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 -  
    73110            # next step is processCredsRequest() below 
    74             self.setCookie(returnURI=self['returnURI'].value) 
    75      
     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                 
    76121        elif 'returnURI' in self: 
    77122            # Home site receives request from remote site for credentials and 
     
    79124            self.processCredsRequest(**kwargs) 
    80125        else: 
     126            # Remote site presents possible sites for user to get their  
     127            # credentials from 
    81128            self.showHomeSiteSelect(**kwargs) 
    82129     
     
    124171     
    125172    #_________________________________________________________________________ 
    126     def receiveCredsResponse(self, sessID, sessMgrURI): 
     173    def receiveCredsResponse(self,  
     174                             sessID, 
     175                             sessMgrURI,  
     176                             expiry, 
     177                             pageTitle='', 
     178                             hdrTxt='', 
     179                             bodyTxt=''): 
    127180        """Remote site receives returned credentials and creates a new cookie  
    128181        for its domain""" 
    129         self.setCookie(sessID, sessMgrURI) 
    130      
    131      
    132     #_________________________________________________________________________ 
    133     def processCredsRequest(self, **returnCredsKwArgs): 
     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): 
    134206        """Receive request from remote site for credentials.  Process and  
    135207        return via a redirect""" 
    136                  
     208 
     209        if returnURI is None: 
     210            returnURI = self['returnURI'].value 
     211                                                          
    137212        # Check for cookie in environment 
    138213        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: 
    139220            # Cookie is set - check for NDG cookie 
    140      
    141             # Get session ID from existing cookie 
    142             cookie = SimpleCookie(os.environ['HTTP_COOKIE']) 
    143             if "NDG-ID1" not in cookie: 
    144                 raise Exception, 'Expecting "NDG-ID1" ID for session cookie' 
    145      
    146             if "NDG-ID2" not in cookie: 
    147                 raise Exception, 'Expecting "NDG-ID2" ID for session cookie' 
    148      
    149             self.returnCreds(cookie["NDG-ID1"].value, 
    150                              cookie["NDG-ID2"].value, 
    151                              **returnCredsKwArgs) 
     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             
    152231        else: 
    153232            # No cookie present - display login.  Submit must redirect back to 
    154             # sender 
    155             print """Content-type: text/html 
    156      
    157     """ 
    158             showLogin(self.returnURI, 
    159                       setCookie=True, 
    160                       pageTitle="Login", 
    161                       htmlTag=True, 
    162                       bodyTag=True) 
    163  
    164      
    165     #_________________________________________________________________________ 
    166     def returnCreds(self,  
    167                     sessID, 
    168                     sessMgrURI, 
     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, 
    169246                    pageTitle='', 
     247                    hdrTxt='', 
    170248                    delayTime=0, 
    171                     redirectMsg=''): 
     249                    redirectMsg='', 
     250                    setCookie=False): 
    172251        """User's home site returns credentials to requestor via a HTTP  
    173252        redirect 
    174253         
    175         sessID:       NDG Session ID from cookie 
    176         sessMgrURI:   NDG Session Manager WSDL URI derived from cookie 
     254        sessCookie:   NDG Session cookie 
    177255        pageTitle:    Give the redirect page a title 
    178256        headTags:     Optionally add additional tags in <head/> section 
     
    181259        redirectMsg:  Message to put on redirect page.  Can be plain text or 
    182260                      formatted HTML""" 
    183      
     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             
    184270        print """Content-type: text/html 
    185      
     271%s     
    186272<html> 
    187273<head> 
    188274<title>%s</title> 
    189 <meta http-equiv="REFRESH" content="%d; url=%s?NDG-ID1=%s&NDG-ID2=%s"> 
     275<meta http-equiv="REFRESH"  
     276content="%d; url=%s?NDG-ID1=%s&NDG-ID2=%s&expires=%s"> 
    190277%s 
    191278</head> 
     
    193280%s 
    194281</body> 
    195 </html>""" % \ 
    196 (pageTitle, delayTime, self.returnURI, sessID,sessMgrURI,headTags,redirectMsg) 
    197      
    198      
    199     #_________________________________________________________________________ 
    200     def setCookie(self, sessID=None, sessMgrURI=None, returnURI=None): 
    201         """Make NDG cookie""" 
    202      
    203         cookie = SimpleCookie() 
    204          
    205         # TODO: Replace with call to SessionMgr WS 
    206         if not sessID: sessID = base64.b64encode(os.urandom(128)) 
    207         if not sessMgrURI: sessMgrURI = base64.b64encode(os.urandom(32)) 
    208      
    209         cookie['NDG-ID1'] = sessID 
    210         cookie['NDG-ID1']['expires'] = "Tue, 13-12-2006 12:00:00 GMT" 
    211         cookie['NDG-ID2'] = sessMgrURI 
    212         cookie['NDG-ID2']['expires'] = "Tue, 13-12-2006 12:00:00 GMT" 
    213      
    214         if returnURI: 
    215             returnURIfield = """<meta http-equiv=\"REFRESH\" 
    216             content=\"0;url=./xDomainCredsTransfer.py?returnURI=%s\">""" % \ 
    217                                                                     returnURI 
    218         else: 
    219             returnURIfield = '' 
    220      
    221         print "Content-type: text/html" 
    222         print cookie.output() + os.linesep + os.linesep 
    223         print """<html> 
    224     <head> 
    225     <title>Set Cookie</title> 
    226     %s 
    227     </head> 
    228      
    229     <body> 
    230         <h1>Cookie set!</h1> 
    231     </body> 
    232     </html>""" % returnURIfield 
    233      
     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             
    234335     
    235336    #_________________________________________________________________________ 
    236337    def showLogin(self, 
    237338                  returnURI=None, 
    238                   setCookie=False, 
     339                  contentTypeHdr=False, 
    239340                  htmlTag=False, 
    240                   pageTitle=None, 
     341                  pageTitle='', 
    241342                  hdrTxt='', 
     343                  headTag=False, 
    242344                  bodyTag=False, 
    243345                  bAuthorise=False): 
    244346        """Display initial NDG login form""" 
    245347     
     348        if contentTypeHdr: print "Content-type: text/html\n\n" 
     349         
    246350        if htmlTag: print "<html>" 
    247351     
    248         if pageTitle: 
     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                     
    249370            print """<head> 
    250371        <title>%s</title> 
     
    260381        else: 
    261382            returnURIfield = '' 
    262      
    263      
    264         if setCookie: 
    265             setCookieField = "<input type=hidden name=setCookie value=\"1\">" 
    266         else: 
    267             setCookieField = '' 
    268      
     383         
    269384     
    270385        if bAuthorise: 
     
    328443        </td> 
    329444    </tr> 
    330     %s 
    331     %s"""  % (self.scriptName, returnURIfield, setCookieField) 
     445    <input type=hidden name=authenticate value="1"> 
     446    %s"""  % (self.scriptName, returnURIfield) 
    332447     
    333448        print \ 
     
    396511        if contentTypeHdr: 
    397512            print "Content-type: text/html\n\n" 
    398      
     513                 
    399514        if htmlTag: 
    400515            print "<html>\n" 
    401516 
    402         if hdrTag: 
     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 
    403535            print """<head> 
    404536             
  • TI12-security/trunk/python/NDG/Session.py

    r941 r1005  
    230230             
    231231            # Decode from base 64 
    232             b64DecodedEncrTxt=base64.b64decode(encrTxt) 
     232            b64DecodedEncrTxt = base64.b64decode(encrTxt) 
    233233             
    234234            # Decrypt and strip trailing spaces 
     
    265265 
    266266           
    267         try: 
    268             if sessID is None: 
    269                 # Use latest session ID allocated if none was input 
    270                 sessID = self.__sessIDlist[-1] 
    271                  
    272             elif not isinstance(sessID, basestring): 
    273                 raise UserSessionError(\ 
    274                                     "Input session ID is not a valid string") 
    275                                      
    276                 if sessID not in self.__sessIDlist: 
    277                     raise UserSessionError(\ 
    278                                         "Input session ID not found in list") 
     267        if sessID is None: 
     268            # Use latest session ID allocated if none was input 
     269            sessID = self.__sessIDlist[-1] 
     270             
     271        elif not isinstance(sessID, basestring): 
     272            raise UserSessionError, "Input session ID is not a valid string" 
     273                                 
     274            if sessID not in self.__sessIDlist: 
     275                raise UserSessionError, "Input session ID not found in list" 
    279276  
    280              
     277  
     278        encrSessMgrWSDLuri = self.encrypt(sessMgrWSDLuri, encrKey) 
     279        dtExpiry = self.credWallet.proxyCert.notAfter 
     280         
     281        # Call class method  
     282        self.__class__.createSecurityCookie(sessID,  
     283                                            encrSessMgrWSDLuri, 
     284                                            dtExpiry=dtExpiry, 
     285                                            cookieDomain=self.__cookieDomain) 
     286         
     287     
     288    #_________________________________________________________________________     
     289    @classmethod 
     290    def createSecurityCookie(cls,  
     291                             sessID,  
     292                             encrSessMgrWSDLuri, 
     293                             expiryStr=None, 
     294                             dtExpiry=None, 
     295                             cookieDomain=None, 
     296                             asString=False): 
     297        """Class method for generic cookie creation independent of individual 
     298        UserSession details.  This is useful for making a fresh cookie from 
     299        an existing one. 
     300         
     301        sessID:                session ID for cookie 
     302        encrSessMgrWSDLuri:    encrypted Session Manager WSDL address. 
     303                               Use UserSession.encrypt().  The site  
     304                               SessionManager holds the encryption key. 
     305        expiryStr|dtExpiry:    expiry date time for the cookie.  Input as a  
     306                               string formatted in the standard way for  
     307                               cookies or input a datetime type for  
     308                               conversion. 
     309        cookieDomain:          The domain for the cookie.  Default is the 
     310                               web server address.  Override to set to a  
     311                               site wide cookie e.g. .rl.ac.uk 
     312        asString:              a SimpleCookie instance is returned be default. 
     313                               Set this flag to True to override and return 
     314                               the string text instead. 
     315        """ 
     316         
     317        if len(sessID) < cls.__sessIDlen: 
     318            UserSessionError, "Session ID has an invalid length" 
     319             
     320        if encrSessMgrWSDLuri[:7] == 'http://' or \ 
     321           encrSessMgrWSDLuri[:8] == 'https://': 
     322            UserSessionError, "Input Session Manager WSDL URI does not " + \ 
     323                              "appear to have been encrypted" 
     324                               
     325        if dtExpiry: 
     326            if not isinstance(dtExpiry, datetime): 
     327                UserSessionError, \ 
     328                    "Expecting vaild datetime object with dtExpiry keyword" 
     329                 
     330            expiryStr = dtExpiry.strftime(cls.__sessCookieExpiryFmt) 
     331             
     332        elif not expiryStr or not isinstance(expiryStr, basestring): 
     333            UserSessionError, "No cookie expiry was set" 
     334             
     335             
     336        try:    
    281337            sessCookie = SimpleCookie() 
    282              
    283             tagValues = (sessID, self.encrypt(sessMgrWSDLuri, encrKey)) 
    284                           
    285             expiryStr = self.__getExpiryStr() 
    286              
     338              
     339            tagValues = (sessID, encrSessMgrWSDLuri) 
    287340            i=0 
    288             for tag in self.__cookieTags: 
     341            for tag in cls.__cookieTags: 
    289342                 
    290343                sessCookie[tag] = tagValues[i] 
     
    292345                 
    293346                # Use standard format for cookie path and expiry 
    294                 sessCookie[tag][self.__cookiePathTag] = self.__cookiePath                 
    295                 sessCookie[tag][self.__cookieExpiryTag]= expiryStr 
     347                sessCookie[tag][cls.__cookiePathTag] = cls.__cookiePath                 
     348                sessCookie[tag][cls.__cookieExpiryTag]= expiryStr 
    296349                                             
    297350                # Make cookie as generic as possible for domains - Nb. '.uk' 
    298351                # alone won't work 
    299                 if self.__cookieDomain: 
    300                     sessCookie[tag][self.__cookieDomainTag] = \ 
    301                                                         self.__cookieDomain 
     352                if cookieDomain: 
     353                    sessCookie[tag][cls.__cookieDomainTag] = cookieDomain 
    302354             
    303355             
     
    312364        except Exception, e: 
    313365            raise UserSessionError("Creating Session Cookie: %s" % e) 
    314  
     366         
     367     
     368    #_________________________________________________________________________     
     369    @classmethod 
     370    def isValidSecurityCookie(cls, cookie, raiseExcep=False): 
     371        """Check cookie has the expected session keys.  Cookie may be a  
     372        string or SimpleCookie type""" 
     373         
     374        if isinstance(cookie, basestring): 
     375            cookie = SimpleCookie(cookie) 
     376             
     377        elif not isinstance(cookie, SimpleCookie): 
     378            if raiseExcep: 
     379                raise UserSessionError, "Input cookie must be a string or " +\ 
     380                                        "SimpleCookie type" 
     381            else: 
     382                return False 
     383         
     384        missingTags = [tag for tag in cls.__cookieTags if tag not in cookie] 
     385        if missingTags: 
     386            if raiseExcep: 
     387                raise UserSessionError, \ 
     388                    "Input cookie missing security tag(s): " + \ 
     389                    ", ".join(missingTags) 
     390            else: 
     391                return False 
     392 
     393        if len(cookie[cls.__cookieTags[0]].value) < cls.__sessIDlen: 
     394            if raiseExcep: 
     395                raise UserSessionError, "Session ID has an invalid length" 
     396            else: 
     397                return False 
     398         
     399        return True 
     400     
    315401 
    316402#_____________________________________________________________________________ 
Note: See TracChangeset for help on using the changeset viewer.