source: TI12-security/trunk/python/ndg.security.common/ndg/security/common/SessionCookie.py @ 2573

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.common/ndg/security/common/SessionCookie.py@2573
Revision 2573, 11.2 KB checked in by pjkersha, 12 years ago (diff)

ndg.security.server/ndg/security/server/MyProxy.py: added details for
Access Grid Toolkit Public License in respective of myproxy_logon used as a
basis for the code.

ndg.security.test/ndg/security/test/SessionCookie/SessionCookieTest.py:

  • fixed so that private key password is prompted for once only by keeping

it as a class var.

  • corrected call to create method.
Line 
1"""NDG Session Cookie used by Session Manager UserSession and Login
2Service CGI code.
3
4NERC Data Grid Project
5
6P J Kershaw 27/10/06
7
8Copyright (C) 2006 CCLRC & NERC
9
10This software may be distributed under the terms of the Q Public License,
11version 1.0 or later.
12"""
13
14__revision__ = '$Id$'
15
16import base64
17from datetime import datetime
18from Cookie import SimpleCookie
19from M2Crypto import X509, BIO, RSA
20
21from ndg.security.common.X509 import X509Cert, X509CertRead
22
23#_____________________________________________________________________________
24class SessionCookieError(Exception):
25    "Handle exception from SessionCookie class"
26   
27       
28#_____________________________________________________________________________
29class _MetaSessionCookie(type):
30    """Enable SessionCookie to have read only class variables e.g.
31   
32    print sessionCookie.cookieTags is allowed but,
33   
34    sessionCookie.cookieTags = None
35   
36    ... raises - AttributeError: can't set attribute"""
37    def __getTag(cls):
38        '''tag refs the morsel containing the encrypted user DN, session ID
39        and the encrypted session manager address.'''
40        return "ndgSID1"
41
42    tag = property(fget=__getTag)
43
44    def __getSessIDlen(cls):
45        '''This sets the session ID length (!)'''
46        return 64
47   
48    sessIDlen = property(fget=__getSessIDlen)
49   
50   
51#_____________________________________________________________________________
52class SessionCookie(object):
53    """Handler for creation and parsing of NDG Security Session Cookies"""
54   
55    __metaclass__ = _MetaSessionCookie
56
57    # Follow standard format for cookie path and expiry attributes
58    __cookiePathTag = "path"
59    __cookiePath = "/"
60    __cookieDomainTag = 'domain'
61    __cookieExpiryTag = "expires"
62 
63    morselArgSep = ','
64    nMorselArgs = 3
65   
66    # Quotes are vital (and part of the official cookei format) - otherwise it
67    # will not be parsed correctly
68    __sessCookieExpiryFmt = "\"%a, %d-%b-%Y %H:%M:%S GMT\""
69
70       
71    #_________________________________________________________________________   
72    def __init__(self):
73        """NDG Security Session Cookie"""
74
75        self.__x509Cert = None
76        self.__priKey = None
77       
78        self.__simpleCookie = None
79        self.__userDN = None
80        self.__sessID = None
81        self.__sessMgrURI = None
82
83   
84    #_________________________________________________________________________
85    def __setCert(self, cert):
86        """filter and convert input cert for encryption of cookie morsel
87       
88        @type: ndg.security.common.X509.X509Cert / M2Crypto.X509.X509 /
89        string
90        @param cert: X.509 certificate. 
91       
92        @rtype ndg.security.common.X509.X509Cert
93        @return X.509 certificate object"""
94       
95        if isinstance(cert, X509Cert):
96            # ndg.security.common.X509.X509Cert type / None
97            return cert
98           
99        elif isinstance(cert, X509.X509):
100            # M2Crypto.X509.X509 type
101            return X509Cert(m2CryptoX509=cert)
102           
103        elif isinstance(cert, basestring):
104            return X509CertParse(cert)
105       
106        else:
107            raise AttributeError, "X.509 Cert. must be type: " + \
108                "ndg.security.common.X509.X509Cert, M2Crypto.X509.X509 or " +\
109                "a base64 encoded string"
110
111
112    #_________________________________________________________________________
113    def __setX509Cert(self, x509Cert):
114        "Set property method for X.509 cert. to encrypt cookie morsel"
115        self.__x509Cert = self.__setCert(x509Cert)
116       
117    x509Cert = property(fset=__setX509Cert,
118                        doc="Set X.509 Cert. to encrypt cookie morsel")
119
120 
121    #_________________________________________________________________________
122    def __setX509CertFromFile(self, x509CertFilePath):
123        "Set X.509 cert property method"
124       
125        if isinstance(x509CertFilePath, basestring):
126            self.__x509Cert = X509CertRead(x509CertFilePath)
127           
128        elif x509CertFilePath is not None:
129            raise AttributeError, \
130                "Signature X.509 cert. file path must be a valid string"
131       
132       
133    x509CertFilePath = property(fset=__setX509CertFromFile,
134                   doc="File path X.509 cert. for encryption of morsel")
135
136 
137    #_________________________________________________________________________
138    def __setPriKeyPwd(self, priKeyPwd):
139        """Set method for private key file password used to decrypt cookie
140        morsel"""
141        if priKeyPwd is not None and not isinstance(priKeyPwd, basestring):
142            raise AttributeError, \
143                "Signing private key password must be None or a valid string"
144       
145        self.__priKeyPwd = priKeyPwd
146       
147    priKeyPwd = property(fset=__setPriKeyPwd,
148             doc="Password protecting private key file used to sign message")
149
150 
151    #_________________________________________________________________________
152    def __setSigningPriKey(self, signingPriKey):
153        """Set method for client private key
154       
155        Nb. if input is a string, priKeyPwd will need to be set if
156        the key is password protected.
157       
158        @type signingPriKey: M2Crypto.RSA.RSA / string
159        @param signingPriKey: private key used to sign message"""
160       
161        if isinstance(signingPriKey, basestring):
162            pwdCallback = lambda *ar, **kw: self.__priKeyPwd
163            self.__signingPriKey = RSA.load_key_string(signingPriKey,
164                                                       callback=pwdCallback)
165
166        elif isinstance(signingPriKey, RSA.RSA):
167            self.__signingPriKey = signingPriKey
168                   
169        else:
170            raise AttributeError, "Signing private key must be a valid " + \
171                                  "M2Crypto.RSA.RSA type or a string"
172               
173    signingPriKey = property(fset=__setSigningPriKey,
174                             doc="Private key used to sign outbound message")
175
176 
177    #_________________________________________________________________________
178    def __setPriKeyFromFile(self, priKeyFilePath):
179        """Set method for client private key file path
180       
181        priKeyPwd MUST be set prior to a call to this method"""
182        if isinstance(priKeyFilePath, basestring):                           
183            try:
184                # Read Private key to sign with   
185                priKeyFile = BIO.File(open(priKeyFilePath)) 
186                pwdCallback = lambda *ar, **kw: self.__priKeyPwd                                           
187                self.__signingPriKey = RSA.load_key_bio(priKeyFile, 
188                                                        callback=pwdCallback)           
189            except Exception, e:
190                raise AttributeError, \
191                                "Setting private key for signature: %s" % e
192       
193        else:
194            raise AttributeError, \
195                        "Private key file path must be a valid string"
196       
197    priKeyFilePath = property(fset=__setPriKeyFromFile,
198                      doc="File path for private key used to sign message")
199
200
201    def __str__(self):
202        return str(self.__simpleCookie)
203   
204   
205    def __getUserDN(self):
206        return self.__userDN
207       
208    userDN = property(fget=__getUserDN, doc="user Distinguished Name")
209   
210   
211    def __getSessID(self):
212        return self.__sessID
213       
214    sessID = property(fget=__getSessID, doc="user Session ID")
215   
216   
217    def __getSessMgrURI(self):
218        return self.__sessMgrURI
219       
220    sessMgrURI = property(fget=__getSessMgrURI, doc="Session Manager URI")
221
222
223    def parse(self, cookieStr):
224        '''Parse from string text
225       
226        @rtype tuple
227        @return (userDN, sessID, sessMgrURI)'''
228       
229        # Nb. SimpleCookie doesn't like unicode
230        self.__simpleCookie = SimpleCookie(str(cookieStr))
231           
232        try:
233            # Check for expected cookie morsel
234            morsel = simpleCookie[self.__class__.tag].value
235            decrMorsel = self.__priKey.decrypt(morsel)
236            morselArgs = self.morselArgSep.split(decrMorsel)
237           
238        except KeyError:
239            raise SessionCookieError, 'Missing cookie morsel "%s"' % \
240                                      SessionCookie.tag
241       
242        if len(morselArgs) != self.__class__.nMorselArgs:
243            raise SessionCookieError, \
244                        "Expecting three input parameters for cookie morsel"
245
246        self.__userDN, self.__sessID, self.__sessMgrURI = morselArgs
247
248        if len(self.__sessID) < SessionCookie.sessIDlen:
249            raise SessionCookieError, "Session ID has an invalid length"
250     
251     
252    def create(self,
253               userDN,
254               sessID,
255               sessMgrURI,
256               dtExpiry=None,
257               strExpiry=None,
258               lifetime=28800,):
259        '''Create an NDG Session cookie'''
260        morselArgs = userDN, sessID, sessMgrURI
261        self.__userDN, self.__sessID, self.__sessMgrURI = morselArgs
262                       
263        morsel = self.__class__.morselArgSep.join(morselArgs)
264        encrMorsel = self.__priKey.encrypt(morsel)
265        b64EncMorsel = base64.encodestring(encrMorsel)
266
267
268        # Set the cookie expiry
269        if dtExpiry is not None:
270            if not isinstance(dtExpiry, datetime):
271                SessionCookieError, \
272                    "Expecting valid datetime object with dtExpiry keyword"
273               
274            strExpiry=dtExpiry.strftime(self.__class__.__sessCookieExpiryFmt)
275           
276        elif strExpiry is not None:
277            if not isinstance(strExpiry, basestring):
278                raise SessionCookieError, "strExpiry is not a valid string"
279           
280            # SimpleCookie doesn't like unicode
281            strExpiry = str(strExpiry)
282           
283        elif lifetime is not None:
284            dtExpiry = datetime.utcnow() + timedelta(seconds=lifetime)
285            strExpiry=dtExpiry.strftime(self.__class__.__sessCookieExpiryFmt)
286         
287        self.__simpleCookie = SimpleCookie()             
288        tag = self.__class__.tag
289       
290        self.__simpleCookie[tag] = b64EncMorsel
291       
292        # Use standard format for cookie path and expiry
293        self.__simpleCookie[tag][self.__cookiePathTag] = self.__cookiePath
294        self.__simpleCookie[tag][self.__cookieExpiryTag] = strExpiry
295
296        return self.__simpleCookie
297   
298   
299    #_________________________________________________________________________
300    @classmethod   
301    def isValid(cls, cookie, raiseExcep=False):
302        """Check cookie has the expected session keys.  Cookie may be a
303        string or SimpleCookie type"""
304       
305        if isinstance(cookie, basestring):
306            cookie = SimpleCookie(cookie)
307           
308        elif not isinstance(cookie, SimpleCookie):
309            if raiseExcep:
310                raise SessionCookieError,"Input cookie must be a string or "+\
311                                        "SimpleCookie type"
312            else:
313                return False
314       
315        if self.tag not in cookie:
316            if raiseExcep:
317                raise SessionCookieError, \
318                    'Input cookie missing security tag: "%s"' % self.tag
319            else:
320                return False
321       
322        return True
Note: See TracBrowser for help on using the repository browser.