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

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

ndg.security.test/ndg/security/test/SessionCookie/init.py: created
separate SessionCookie? unit test package.

ndg.security.test/ndg/security/test/SessionCookie/sessionCookieTest.cfg:
configures PKI for encryption of cookie info

ndg.security.common/ndg/security/common/SessionCookie.py: major refactoring -

  • use a single morsel rather two. This contains: userDN, sessID, and

sessMgrURI

  • morsel is encrypted using PKI
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,dtExpiry=None,strExpiry=None,lifetime=28800,*morselArgs):
253        '''Create an NDG Session cookie'''
254       
255        if len(morselArgs) != self.__class__.nMorselArgs:
256            raise SessionCookieError, \
257                        "Expecting three input parameters for cookie morsel"
258       
259        self.__userDN, self.__sessID, self.__sessMgrURI = morselArgs
260                       
261        morsel = morselArgs.join(self.__class__.morselArgSep)
262        encrMorsel = self.__priKey.encrypt(morsel)
263        b64EncMorsel = base64.encodestring(encrMorsel)
264
265
266        # Set the cookie expiry
267        if dtExpiry is not None:
268            if not isinstance(dtExpiry, datetime):
269                SessionCookieError, \
270                    "Expecting valid datetime object with dtExpiry keyword"
271               
272            strExpiry=dtExpiry.strftime(self.__class__.__sessCookieExpiryFmt)
273           
274        elif strExpiry is not None:
275            if not isinstance(strExpiry, basestring):
276                raise SessionCookieError, "strExpiry is not a valid string"
277           
278            # SimpleCookie doesn't like unicode
279            strExpiry = str(strExpiry)
280           
281        elif lifetime is not None:
282            dtExpiry = datetime.utcnow() + timedelta(seconds=lifetime)
283            strExpiry=dtExpiry.strftime(self.__class__.__sessCookieExpiryFmt)
284         
285        self.__simpleCookie = SimpleCookie()             
286        tag = self.__class__.tag
287       
288        self.__simpleCookie[tag] = b64EncMorsel
289       
290        # Use standard format for cookie path and expiry
291        self.__simpleCookie[tag][self.__cookiePathTag] = self.__cookiePath
292        self.__simpleCookie[tag][self.__cookieExpiryTag] = strExpiry
293
294        return self.__simpleCookie
295   
296   
297    #_________________________________________________________________________
298    @classmethod   
299    def isValid(cls, cookie, raiseExcep=False):
300        """Check cookie has the expected session keys.  Cookie may be a
301        string or SimpleCookie type"""
302       
303        if isinstance(cookie, basestring):
304            cookie = SimpleCookie(cookie)
305           
306        elif not isinstance(cookie, SimpleCookie):
307            if raiseExcep:
308                raise SessionCookieError,"Input cookie must be a string or "+\
309                                        "SimpleCookie type"
310            else:
311                return False
312       
313        if self.tag not in cookie:
314            if raiseExcep:
315                raise SessionCookieError, \
316                    'Input cookie missing security tag: "%s"' % self.tag
317            else:
318                return False
319       
320        return True
Note: See TracBrowser for help on using the repository browser.