source: TI12-security/trunk/python/ndg/security/server/Session.py @ 1638

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg/security/server/Session.py@1638
Revision 1638, 47.0 KB checked in by pjkersha, 14 years ago (diff)

Moved client and server side specific modules to their respective client and server packages.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1"""NDG Session Management and security includes UserSession,
2SessionMgr, Credentials Repository classes.
3
4NERC Data Grid Project
5
6P J Kershaw 02/06/05
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
14reposID = '$Id$'
15
16# Placing of session ID on client
17from Cookie import SimpleCookie
18
19# Time module for use with cookie expiry
20from time import strftime
21from datetime import datetime
22
23# For parsing of properties files
24import cElementTree as ElementTree
25
26# Base 64 encode session IDs if returned in strings - urandom's output may
27# not be suitable for printing!
28import base64
29
30# Session Manager WSDL URI in cookie
31from Crypto.Cipher import AES
32
33# Check Session Mgr WSDL URI is encrypted
34from urllib import urlopen
35
36# Credential Wallet
37from NDG.CredWallet import *
38
39# MyProxy server interface
40from NDG.MyProxy import *
41
42# Tools for interfacing with SessionMgr WS
43from NDG.SessionMgrIO import *
44
45# SessionMgr.reqAuthorisation - getPubKey WS call.  Don't import
46# AttAuthorityIO's namespace as it would conflict with SessionMgrIO's
47from NDG import AttAuthorityIO
48
49# Use client package to allow redirection of authorisation requests and
50# to retrieve Attribute Authority public key
51from NDG.SecurityClient import *
52
53# Use in SessionMgr __redirectAuthorisationReq to retrieve and store Public
54# key
55import tempfile
56import urllib
57
58#_____________________________________________________________________________
59class UserSessionError(Exception):   
60    """Exception handling for NDG User Session class."""
61    pass
62   
63   
64#_____________________________________________________________________________
65class _MetaUserSession(type):
66    """Enable UserSession to have read only class variables e.g.
67   
68    print UserSession.cookieTags is allowed but,
69   
70    UserSession.cookieTags = None
71   
72    ... raises - AttributeError: can't set attribute"""
73    def __getCookieTags(cls):
74        return ("NDG-ID1", "NDG-ID2")
75
76    cookieTags = property(fget=__getCookieTags)
77
78
79#_____________________________________________________________________________
80# Inheriting from 'object' allows Python 'new-style' class with Get/Set
81# access methods
82class UserSession(object):
83    """Session details - created when a user logs into NDG"""
84
85    __metaclass__ = _MetaUserSession
86   
87    # Session ID
88    __sessIDlen = 64
89
90    # Follow standard format for cookie path and expiry attributes
91    __cookiePathTag = "path"
92    __cookiePath = "/"
93    __cookieDomainTag = 'domain'
94    __cookieExpiryTag = "expires"
95 
96    # Quotes are vital (and part of the official cookei format) - otherwise it
97    # will not be parsed correctly
98    __sessCookieExpiryFmt = "\"%a, %d-%b-%Y %H:%M:%S GMT\""
99
100
101    #_________________________________________________________________________
102    def __init__(self, *credWalletArgs, **credWalletKeys):
103        """Initialise UserSession with args and keywords to CredWallet"""
104
105        # Domain for cookie used by createCookie method - if not set, default
106        # is web server domain name
107        self.__cookieDomain = None
108               
109       
110        # Each User Session has one or more browser sessions associated with
111        # it.  These are stored in a list
112        self.__sessIDlist = []
113        self.addNewSessID()
114        self.__credWallet = CredWallet(*credWalletArgs, **credWalletKeys)
115
116
117    #_________________________________________________________________________
118    def __setCookieDomain(self, cookieDomain):
119        """Set domain for cookie - set to None to assume domain of web server
120        """
121
122        if not isinstance(cookieDomain, basestring) and \
123           cookieDomain is not None:
124            raise UserSessionError(\
125                "Expecting string or None type for \"cookieDomain\"")
126                       
127        self.__cookieDomain = cookieDomain
128
129    cookieDomain = property(fset=__setCookieDomain, doc="Set cookie domain")
130
131
132    #_________________________________________________________________________
133    # CredWallet access
134    def __getCredWallet(self):
135        """Get Credential Wallet instance"""
136        return self.__credWallet
137   
138    credWallet = property(fget=__getCredWallet,
139                          doc="Read-only access to CredWallet instance")
140
141
142    #_________________________________________________________________________
143    # CredWallet access
144    def __getSessIDlist(self):
145        """Get Session ID list - last item is latest allocated for this
146        session"""
147        return self.__sessIDlist
148   
149    sessIDlist = property(fget=__getSessIDlist,
150                          doc="Read-only access to Session ID list")
151
152
153    #_________________________________________________________________________       
154    def __latestSessID(self):
155        """Get the session ID most recently allocated"""
156        return self.__sessIDlist[-1]
157   
158    # Publish as an attribute
159    latestSessID = property(fget=__latestSessID,
160                            doc="Latest Session ID allocated")
161
162
163    #_________________________________________________________________________
164    def addNewSessID(self):
165        """Add a new session ID to be associated with this UserSession
166        instance"""
167
168        # base 64 encode output from urandom - raw output from urandom is
169        # causes problems when passed over SOAP.  A consequence of this is
170        # that the string length of the session ID will almost certainly be
171        # longer than SessionMgr.__sessIDlen
172        sessID = base64.urlsafe_b64encode(os.urandom(self.__sessIDlen))
173        self.__sessIDlist.append(sessID)
174
175
176    #_________________________________________________________________________
177    def __getExpiryStr(self):
178        """Return session expiry date/time as would formatted for a cookie"""
179
180        try:
181            # Proxy certificate's not after time determines the expiry
182            dtNotAfter = self.credWallet.proxyCert.notAfter
183
184            return dtNotAfter.strftime(self.__sessCookieExpiryFmt)
185        except Exception, e:
186            UserSessionError("getExpiry: %s" % e)
187
188
189    #_________________________________________________________________________
190    @staticmethod
191    def encrypt(txt, encrKey):
192        """Encrypt the test of this Session Manager's WS URI / URI for its
193        public key to allow inclusion in a web browser session cookie
194       
195        The address is encrypted and then base 64 encoded"""
196       
197        # Text length must be a multiple of 16 for AES encryption
198        try:
199            mod = len(txt) % 16
200            nPad = mod and 16 - mod or 0
201               
202            # Add padding
203            paddedURI = txt + ''.join(' '*nPad)
204        except Exception, e:
205            raise UserSessionError, "Error padding text for encryption: " + \
206                                    str(e)
207       
208        # encrypt
209        try:
210            aes = AES.new(encrKey, AES.MODE_ECB)
211            return base64.urlsafe_b64encode(aes.encrypt(paddedURI))
212       
213        except Exception, e:
214            raise UserSessionError("Error encrypting text: %s" % str(e))
215                                       
216   
217    #_________________________________________________________________________
218    @staticmethod                                   
219    def decrypt(encrTxt, encrKey):
220        """Decrypt text from cookie set by another Session Manager.  This
221        is required when reading a session cookie to find out which
222        Session Manager holds the client's session
223       
224        encrTxt:    base 64 encoded encrypted text"""
225
226        try:
227            aes = AES.new(encrKey, AES.MODE_ECB)
228           
229            # Decode from base 64
230            b64DecodedEncrTxt = base64.urlsafe_b64decode(encrTxt)
231           
232            # Decrypt and strip trailing spaces
233            return aes.decrypt(b64DecodedEncrTxt).strip()
234       
235        except Exception, e:
236            raise SessionMgrError("Decrypting: %s" % str(e))           
237
238
239    #_________________________________________________________________________
240    def createCookie(self, 
241                     sessMgrWSDLuri,
242                     encrKey, 
243                     sessID=None,
244                     cookieDomain=None,
245                     asString=True):
246        """Create cookies for session ID Session Manager WSDL address
247
248        sessMgrWSDLuri:     WSDL address for Session Mananger
249        sessMgrPubKeyURI:   URI for public key of Session Manager
250        encrKey:               encryption key used to encrypted above URIs
251        sessID:                if no session ID is provided, use the latest
252                               one to be allocated.
253        cookieDomain:          domain set for cookie, if non set, web server
254                               domain name is used
255        asString:              Set to True to return the cookie as string
256                               text.  If False, it is returned as a
257                               SimpleCookie instance."""
258
259
260        # Nb. Implicit call to __setCookieDomain method
261        if cookieDomain:
262            self.cookieDomain = cookieDomain
263
264         
265        if sessID is None:
266            # Use latest session ID allocated if none was input
267            sessID = self.__sessIDlist[-1]
268           
269        elif not isinstance(sessID, basestring):
270            raise UserSessionError, "Input session ID is not a valid string"
271                               
272            if sessID not in self.__sessIDlist:
273                raise UserSessionError, "Input session ID not found in list"
274 
275 
276        encrSessMgrWSDLuri = self.encrypt(sessMgrWSDLuri, encrKey)
277        dtExpiry = self.credWallet.proxyCert.notAfter
278       
279        # Call class method
280        cookieTags = UserSession.cookieTags
281        cookieTagsKwArgs = {}.fromkeys(cookieTags)
282        cookieTagsKwArgs[cookieTags[0]] = sessID
283        cookieTagsKwArgs[cookieTags[1]] = encrSessMgrWSDLuri
284       
285        return self.__class__.createSecurityCookie(dtExpiry=dtExpiry,
286                                            cookieDomain=self.__cookieDomain,
287                                            asString=asString,
288                                            **cookieTagsKwArgs)
289
290   
291    #_________________________________________________________________________   
292    @classmethod
293    def createSecurityCookie(cls, 
294                             expiryStr=None,
295                             dtExpiry=None,
296                             cookieDomain=None,
297                             asString=False,
298                             **cookieTagKwArgs):
299        """Class method for generic cookie creation independent of individual
300        UserSession details.  This is useful for making a fresh cookie from
301        an existing one.
302       
303        sessID:                session ID for cookie
304        encrSessMgrWSDLuri:    encrypted Session Manager WSDL address.
305                               Use UserSession.encrypt().  The site
306                               SessionManager holds the encryption key.
307        expiryStr|dtExpiry:    expiry date time for the cookie.  Input as a
308                               string formatted in the standard way for
309                               cookies or input a datetime type for
310                               conversion.
311        cookieDomain:          The domain for the cookie.  Default is the
312                               web server address.  Override to set to a
313                               site wide cookie e.g. .rl.ac.uk
314        asString:              a SimpleCookie instance is returned be default.
315                               Set this flag to True to override and return
316                               the string text instead.
317        """
318       
319        try:
320            sessID = cookieTagKwArgs['NDG-ID1']
321            encrSessMgrWSDLuri = cookieTagKwArgs['NDG-ID2']
322           
323        except KeyError:
324            c = cls.cookieTags
325            msg = len(c) > 1 and 's %s and %s' % (', '.join(c[:-1]), c[-1]) \
326                or ' ' + c[-1]
327            raise UserSessionError, "The Cookie tag keyword%s" % c
328
329           
330        if len(sessID) < cls.__sessIDlen:
331            UserSessionError, "Session ID has an invalid length"
332           
333        if encrSessMgrWSDLuri[:7] == 'http://' or \
334           encrSessMgrWSDLuri[:8] == 'https://':
335            UserSessionError, "Input Session Manager WSDL URI does not " + \
336                              "appear to have been encrypted"
337                             
338        if dtExpiry:
339            if not isinstance(dtExpiry, datetime):
340                UserSessionError, \
341                    "Expecting valid datetime object with dtExpiry keyword"
342               
343            expiryStr = dtExpiry.strftime(cls.__sessCookieExpiryFmt)
344           
345        elif not expiryStr or not isinstance(expiryStr, basestring):
346            raise UserSessionError, "No cookie expiry was set"
347           
348           
349        try:   
350            sessCookie = SimpleCookie()
351             
352            tagValues = (sessID, encrSessMgrWSDLuri)
353            i=0
354            for tag in cls.cookieTags:
355               
356                sessCookie[tag] = tagValues[i]
357                i += 1
358               
359                # Use standard format for cookie path and expiry
360                sessCookie[tag][cls.__cookiePathTag] = cls.__cookiePath               
361                sessCookie[tag][cls.__cookieExpiryTag]= expiryStr
362                                           
363                # Make cookie as generic as possible for domains - Nb. '.uk'
364                # alone won't work but .rl.ac.uk would
365                if cookieDomain:
366                    sessCookie[tag][cls.__cookieDomainTag] = cookieDomain
367           
368           
369            # Caller should set the cookie e.g. in a CGI script
370            # print "Content-type: text/html"
371            # print cookie.output() + os.linesep
372            if asString:
373                return sessCookie.output()
374            else:
375                return sessCookie
376           
377        except Exception, e:
378            raise UserSessionError("Creating Session Cookie: %s" % e)
379       
380   
381    #_________________________________________________________________________   
382    @classmethod
383    def isValidSecurityCookie(cls, cookie, raiseExcep=False):
384        """Check cookie has the expected session keys.  Cookie may be a
385        string or SimpleCookie type"""
386       
387        if isinstance(cookie, basestring):
388            cookie = SimpleCookie(cookie)
389           
390        elif not isinstance(cookie, SimpleCookie):
391            if raiseExcep:
392                raise UserSessionError, "Input cookie must be a string or " +\
393                                        "SimpleCookie type"
394            else:
395                return False
396       
397        missingTags = [tag for tag in cls.cookieTags if tag not in cookie]
398        if missingTags:
399            if raiseExcep:
400                raise UserSessionError, \
401                    "Input cookie missing security tag(s): " + \
402                    ", ".join(missingTags)
403            else:
404                return False
405
406        if len(cookie[cls.cookieTags[0]].value) < cls.__sessIDlen:
407            if raiseExcep:
408                raise UserSessionError, "Session ID has an invalid length"
409            else:
410                return False
411       
412        return True
413   
414
415#_____________________________________________________________________________
416class SessionMgrError(Exception):   
417    """Exception handling for NDG Session Manager class."""
418    pass
419
420#_____________________________________________________________________________
421class SessionMgr(dict):
422    """NDG authentication and session handling"""
423
424    # valid configuration property keywords
425    __validElem = \
426    {
427        'caCertFile':     None,
428        'certFile':       None,
429        'keyFile':        None,
430        'keyPPhrase':     None, 
431        'sessMgrEncrKey': None, 
432        'sessMgrWSDLuri': None,
433        'cookieDomain':   None, 
434        'myProxyProp':    None, 
435        'credReposProp':  ('modFilePath', 'modName', 'className', 'propFile')
436    }
437
438   
439    #_________________________________________________________________________
440    def __init__(self, 
441                 propFilePath=None, 
442                 credReposPPhrase=None, 
443                 **prop):       
444        """Create a new session manager to manager NDG User Sessions
445       
446        propFilePath:        path to properties file
447        credReposPPhrase:    for credential repository if not set in
448                             properties file
449        **prop:              set any other properties corresponding to the
450                             tags in the properties file"""       
451
452        # Base class initialisation
453        dict.__init__(self)
454
455        # Key user sessions by session ID
456        self.__sessDict = {}
457
458        # Key user sessions by user DN
459        self.__dnDict = {}
460
461        # Credential Repository interface only set if properties file is set
462        # otherwise explict calls are necessary to set credReposProp via
463        # setProperties/readProperties and then loadCredReposInterface
464        self.__credRepos = None
465       
466
467        # MyProxy interface
468        try:
469            self.__myPx = MyProxy()
470           
471        except Exception, e:
472            raise SessionMgrError, "Creating MyProxy interface: %s" % e
473
474               
475        # Dictionary to hold properties     
476        self.__prop = {}
477       
478       
479        # Set properties from file
480        if propFilePath is not None:
481            self.readProperties(propFilePath)
482
483            # Call here as we can safely expect that all Credential Repository
484            # parameters have been set above
485            self.loadCredReposInterface()
486
487       
488        # Set any properties that were provided by keyword input
489        #
490        # Nb. If any are duplicated with tags in the properties file they
491        # will overwrite the latter
492        #
493        # loadCredReposInterface must be called explicitly if propFilePath
494        # wasn't set.  This is because if properties are passed by keyword
495        # alone there is no guarantee that those needed to load the interface
496        # will be present.  readProperties however, requires that all the
497        # required parameters are present in the properties file.
498        self.setProperties(**prop)
499       
500       
501    #_________________________________________________________________________
502    def loadCredReposInterface(self, credReposPPhrase=None, Force=False):
503        """
504        Pick up and instantiate Credential Repository interface class from
505        properties file settings/keywords set by setProperties/__init__
506       
507        @param credReposPPhrase: password for CredentialRepository database
508            This is passed into the Credential Repository object but may not
509            be needed.  e.g. the custom class could pick up a password from
510            the properties file for it - ['credRepos']['propFilePath']
511        @type credReposPPhrase: string
512        @param Force: flag to force reload of Credential Repository instance
513        @type Force: boolean
514        @return: None
515        """
516       
517        # Don't bother if object has already been created.  Use Force=True
518        # to override and force reload
519        if Force is False and self.__credRepos is not None:
520            return
521       
522        # Credentials repository - permanent store of user credentials
523        try:
524            try:
525                # Temporarily extend system path ready for import
526                sysPathBak = sys.path[:]
527                sys.path.append(self.__prop['credReposProp']['modFilePath'])
528               
529                # Import module name specified in properties file
530                credReposMod = \
531                    __import__(self.__prop['credReposProp']['modName'],
532                               globals(),
533                               locals(),
534                               [self.__prop['credReposProp']['className']])
535   
536                credReposClass = eval(\
537                'credReposMod.' + self.__prop['credReposProp']['className'])
538            finally:
539                sys.path[:] = sysPathBak
540
541        except KeyError, e:
542            raise SessionMgrError, \
543        'Missing %s element for credential repository module import' % str(e)
544                       
545        except Exception, e:
546            raise SessionMgrError, \
547                        'Importing credential repository module: %s' % str(e)
548
549        # Check class inherits from CredWallet.CredRepos abstract base class
550        if not issubclass(credReposClass, CredRepos):
551            raise SessionMgrError, \
552                "Credential Repository class %s must be inherited from %s" % \
553                (credReposClass, CredRepos)
554
555
556        # Instantiate custom class
557        try:
558            self.__credRepos = credReposClass(\
559                      propFilePath=self.__prop['credReposProp']['propFile'],
560                      dbPPhrase=credReposPPhrase)
561           
562        except Exception, e:
563            raise SessionMgrError, \
564            "Error instantiating Credential Repository interface: " + str(e)
565     
566       
567    #_________________________________________________________________________       
568    def __delitem__(self, key):
569        "Session Manager keys cannot be removed"       
570        raise KeyError('Keys cannot be deleted from '+self.__class__.__name__)
571
572
573    def __getitem__(self, key):
574        self.__class__.__name__ + """ behaves as data dictionary of Session
575        Manager properties
576        """
577        if key not in self.__prop:
578            raise KeyError("Invalid key " + key)
579       
580        return self.__prop[key]
581   
582   
583    def __setitem__(self, key, item):
584        self.__class__.__name__ + """ behaves as data dictionary of Session
585        Manager properties"""
586        self.setProperties(**{key: item})
587       
588
589    def clear(self):
590        raise KeyError("Data cannot be cleared from "+self.__class__.__name__)
591   
592    def keys(self):
593        return self.__prop.keys()
594
595    def items(self):
596        return self.__prop.items()
597
598    def values(self):
599        return self.__prop.values()
600
601    def has_key(self, key):
602        return self.__prop.has_key(key)
603
604    # 'in' operator
605    def __contains__(self, key):
606        return key in self.__prop
607           
608
609    #_________________________________________________________________________
610    def readProperties(self, propFilePath=None, propElem=None):
611        """Read Session Manager properties from an XML file or cElementTree
612        node
613       
614        @param propFilePath: file path for XML properties file
615        @type propFilePath: string
616        @param propElem: pass in existing ElementTree treeroot
617        @type propElem: Element
618        @ruturn: None"""
619
620        if propFilePath is not None:
621            try:
622                tree = ElementTree.parse(propFilePath)
623                propElem = tree.getroot()
624
625            except IOError, e:
626                raise SessionMgrError, \
627                                "Error parsing properties file \"%s\": %s" % \
628                                (e.filename, e.strerror)
629               
630            except Exception, e:
631                raise SessionMgrError, \
632                    "Error parsing properties file: \"%s\": %s" % \
633                    (propFilePath, e)
634
635        if propElem is None:
636            raise SessionMgrError, \
637            "Parsing properties file \"%s\": root element is not defined" % \
638            propFilePath
639
640
641        filtElemTxt = lambda elem: os.path.expandvars(elem.text).strip()
642       
643        missingElem = []
644        getMissingElem = lambda targ, ref: [e for e in targ if e not in ref]
645        invalidElem = []
646        try:
647            for elem in propElem:
648                if elem.tag == 'myProxyProp':
649                    self.__myPx.readProperties(propElem=elem)
650   
651                elif elem.tag == 'credReposProp':
652                    self.__prop['credReposProp'] = \
653                                dict([(e.tag, filtElemTxt(e)) for e in elem])
654                           
655                    # Check for missing elements
656                    missingElem.extend(getMissingElem(\
657                                           self.__validElem['credReposProp'],
658                                           self.__prop['credReposProp']))
659                       
660                elif elem.tag in self.__validElem:
661                    # Strip white space but not in the case of pass-phrase
662                    # field as pass-phrase might contain leading or
663                    # trailing white space
664                    if elem.text and elem.tag != 'keyPPhrase':
665                       
666                        # Check for environment variables in file paths
667                        self.__prop[elem.tag] = filtElemTxt(elem)
668                    else:
669                        self.__prop[elem.tag] = elem.text                                         
670                else:
671                    invalidElem.append(elem.tag)
672               
673        except Exception, e:
674            raise SessionMgrError, \
675                "Error parsing tag \"%s\" in properties file \"%s\": %s" % \
676                (elem.tag, propFilePath, e)
677
678        missingElem.extend(getMissingElem(self.__prop, self.__validElem))
679        errMsg = ''
680       
681        if invalidElem != []:
682            errMsg = 'Invalid elements: "%s"\n' % '", "'.join(invalidElem)
683
684        if missingElem != []:
685            errMsg += 'Missing elements: "%s"\n' % '", "'.join(missingElem)
686
687        if errMsg:
688            raise SessionMgrError, errMsg +  "for properties file \"%s\"" % \
689                                                                propFilePath
690       
691
692    #_________________________________________________________________________
693    def setProperties(self, **prop):
694        """Update existing properties from an input dictionary
695        Check input keys are valid names"""
696       
697        for key in prop.keys():
698            if key not in self.__validElem:
699                raise SessionMgrError, "Property name \"%s\" is invalid" % key
700
701
702        for key, value in prop.items():
703                       
704            if key == 'myProxyProp':
705                self.__myPx.setProperties(prop[key])
706   
707            elif key == 'credReposProp':
708                self.__prop['credReposProp'] = prop[key].copy()
709
710            elif key in self.__validElem:
711                # Only update other keys if they are not None or ""
712                if value:
713                    self.__prop[key] = value               
714            else:
715                raise SessionMgrError, \
716                "Key \"%s\" is not a valid Session Manager property" % key
717
718
719    #_________________________________________________________________________
720    def addUser(self, caConfigFilePath=None, caPassPhrase=None, **reqKeys):       
721        """Register a new user with an NDG data centre
722       
723        addUser([caConfigFilePath, ]|[, caPassPhrase]
724                |[, userName=u, pPhrase=p])
725
726        returns XML formatted response message
727       
728        caConfigFilePath|caPassPhrase:  pass phrase for SimpleCA's
729                                        certificate.  Set via file or direct
730                                        string input respectively.  Set here
731                                        to override setting [if any] made at
732                                        object creation.
733       
734                                        Passphrase is only required if
735                                        SimpleCA is instantiated on the local
736                                        machine.  If SimpleCA WS is called no
737                                        passphrase is required.
738                                       
739        **reqKeys:                      use as alternative to
740                                        reqXMLtxt keyword - pass in
741                                        username and pass-phrase for new user
742                                        unencrypted as keywords username
743                                        and pPhrase respectively.  See
744                                        SessionMgrIO.AddUserRequest class for
745                                        reference."""
746             
747        try:
748            # Add new user certificate to MyProxy Repository
749            #
750            # Changed so that record is NOT added to UserID table of
751            # CredentialRepository.  Instead, a check can be made when a new
752            # Attribute Certificate credential is added: if no entry is
753            # present for the user, add them into the UserID table at this
754            # point.
755            #
756            # By removing the add record call, MyProxy and Credential
757            # Repository can be independent of one another
758            user = self.__myPx.addUser(reqKeys['userName'],
759                                       reqKeys['pPhrase'],
760                                       caConfigFilePath=caConfigFilePath,
761                                       caPassPhrase=caPassPhrase,
762                                       retDN=True)           
763        except Exception, e:
764            return AddUserResp(errMsg=str(e))
765
766        return AddUserResp(errMsg='')
767   
768   
769    #_________________________________________________________________________       
770    def connect(self, **reqKeys):       
771        """Create a new user session or connect to an existing one:
772
773        connect([getCookie=True/False][createServerSess=Tue/False, ]
774                [, userName=u, pPhrase=p]|[, proxyCert=px]|[, sessID=id])
775
776        getCookie:              If True, allocate a user session with a
777                                wallet in the session manager and return a
778                                cookie containing the new session ID
779                                allocated.  If set False, return a proxy
780                                certificate only.  The client is then
781                                responsible for Credential Wallet management.
782        createServerSess:       If set to True, the SessionMgr will create
783                                and manage a session for the user.  Nb.
784                                this flag is ignored and set to True if
785                                getCookie is set.  For command line case,
786                                where getCookie is False, it's possible
787                                to choose to have a client or server side
788                                session using this keyword.
789        reqXMLtxt:              encrypted XML containing user credentials -
790                                user name, pass-phrase or proxy cert etc
791        reqKeys:                username and pass-phrase or the proxy"""
792       
793
794        if 'sessID' in reqKeys:
795           
796            # Connect to an existing session identified by a session ID and
797            # return equivalent proxy cert
798            userSess = self.__connect2UserSession(sessID=sessID)
799            return ConnectResp(proxyCert=userSess.credWallet.proxyCertTxt)
800       
801        elif 'proxyCert' in reqKeys:
802            # Connect to an existing session identified by a proxy
803            # certificate and return an equivalent session cookie
804            userSess = self.__connect2UserSession(proxyCert=proxyCert)
805            sessCookie = userSess.createCookie(self.__prop['sessMgrWSDLuri'],
806                                               self.__prop['sessMgrEncrKey'])
807            return ConnectResp(sessCookie=sessCookie)
808       
809        else:
810            # Create a fresh session
811            try:           
812                # Get a proxy certificate to represent users ID for the new
813                # session
814                proxyCert = self.__myPx.getDelegation(reqKeys['userName'], 
815                                                      reqKeys['pPhrase'])   
816            except Exception, e:
817                raise SessionMgrError, "Delegating from MyProxy: %s" % e
818
819            bGetCookie = 'getCookie' in reqKeys and reqKeys['getCookie']
820                                               
821            bCreateServerSess = 'createServerSess' in reqKeys and \
822                                            reqKeys['createServerSess']
823                                           
824            if bGetCookie or bCreateServerSess:
825                # Session Manager creates and manages user's session
826                userSess = self.__createUserSession(proxyCert)
827 
828                               
829            if bGetCookie:
830               
831                # Web browser client - Return session cookie
832                userSess.cookieDomain = self.__prop['cookieDomain']
833
834                sessCookie = userSess.createCookie(\
835                                            self.__prop['sessMgrWSDLuri'],
836                                            self.__prop['sessMgrEncrKey'])
837               
838                try:
839                    # Encrypt response if a client public key is available
840                    return ConnectResp(sessCookie=sessCookie)
841               
842                except Exception, e:
843                    raise SessionMgrError, \
844                                "Error formatting connect response: %s" % e             
845            else:
846                # NDG Command line client - Return proxy certificate
847                return ConnectResp(proxyCert=proxyCert)       
848       
849       
850    #_________________________________________________________________________       
851    def __createUserSession(self, proxyCert):
852        """Create a new user session from input user credentials       
853        and return
854       
855        """
856       
857        try:   
858            # Check for an existing session for the same user
859            try:
860                userDN = str(X509CertParse(proxyCert).dn)
861               
862            except Exception, e:
863                raise SessionMgrError, \
864                "Parsing input proxy certificate DN for session create: %s"%\
865                                                                        str(e)
866
867            if userDN in self.__dnDict:
868                # Update existing session with proxy cert and add a new
869                # session ID to access it - a single session can be accessed
870                # via multiple session IDs e.g. a user may wish to access the
871                # same session from the their desktop PC and their laptop.
872                # Different session IDs are allocated in each case.
873                userSess = self.__dnDict[userDN]
874                userSess.addNewSessID()
875               
876            else:
877                # Create a new user session using the new proxy certificate
878                # and session ID
879                #
880                # Nb. Client pub/pri key info to allow message level
881                # encryption for responses from Attribute Authority WS
882                userSess = UserSession(proxyCert, 
883                                   caPubKeyFilePath=self.__prop['caCertFile'],
884                                   clntPubKeyFilePath=self.__prop['certFile'],
885                                   clntPriKeyFilePath=self.__prop['keyFile'],
886                                   clntPriKeyPwd=self.__prop['keyPPhrase'],
887                                   credRepos=self.__credRepos)
888
889                # Also allow access by user DN
890                self.__dnDict[userDN] = userSess
891
892                               
893            newSessID = userSess.latestSessID
894           
895            # Check for unique session ID
896            if newSessID in self.__sessDict:
897                raise SessionMgrError, \
898                    "New Session ID is already in use:\n\n %s" % newSessID
899
900            # Add new session to list                 
901            self.__sessDict[newSessID] = userSess
902                   
903            # Return new session
904            return userSess
905       
906        except Exception, e:
907            raise SessionMgrError, "Creating User Session: %s" % str(e)
908
909
910    #_________________________________________________________________________       
911    def __connect2UserSession(self, **idKeys):
912        """Connect to an existing session by providing a valid session ID or
913        proxy certificate
914
915        __connect2UserSession([proxyCert]|[sessID])
916       
917        proxyCert:    proxy certificate string corresponding to an existing
918                      session to connect to.
919        sessID:       similiarly, a web browser session ID linking to an
920                      an existing session."""
921       
922           
923        # Look for a session corresponding to this ID
924        if 'sessID' in idKeys:
925            try:
926                # Check matched session has not expired
927                userSess = self.__sessDict[idKeys['sessID']]
928               
929            except KeyError:
930                # User session not found with given ID
931                raise SessionMgrError, \
932                        "No user session found matching input session ID"
933                       
934            try:
935                userSess.credWallet.isValid(raiseExcep=True)
936                return userSess
937                       
938            except Exception, e:
939                raise SessionMgrError, \
940                        "Matching session ID to existing user session: %s" % e
941               
942       
943        elif 'proxyCert' in idKeys:
944            try:
945                userDN = str(X509CertParse(idKeys['proxyCert']).dn)
946               
947            except Exception, e:
948                raise SessionMgrError, \
949                "Parsing input proxy certificate DN for session connect: %s"%\
950                                                                        str(e)
951            try:
952                userSess = self.__dnDict[userDN]
953                       
954            except KeyError:
955                # User session not found with given proxy cert
956                raise SessionMgrError, \
957                    "No user session found matching input proxy certificate"
958                   
959            try:
960                # Check matched session has not expired
961                userSess.credWallet.isValid(raiseExcep=True)
962                return userSess
963                                       
964            except Exception, e:
965                raise SessionMgrError(\
966                "Matching proxy certificate to existing user session: %s" % e)
967        else:
968            raise SessionMgrError,\
969                                '"sessID" or "proxyCert" keywords must be set'
970
971
972    #_________________________________________________________________________       
973    def deleteUserSession(self, sessID=None, proxyCert=None):
974        """Delete an existing session by providing a valid session ID or
975        proxy certificate - use for user logout
976
977        __deleteUserSession([proxyCert]|[sessID])
978       
979        proxyCert:    proxy certificate corresponding to an existing
980                      session to connect to.
981        sessID:       similiarly, a web browser session ID linking to an
982                      an existing session."""
983       
984           
985        # Look for a session corresponding to the session ID/proxy cert.
986        if sessID:
987            try:
988                userSess = self.__sessDict[sessID]
989               
990            except KeyError:
991                raise SessionMgrError, \
992                    "Deleting user session - no matching session ID exists"
993
994            # Get associated user Distinguished Name
995            userDN = userSess.credWallet.proxyCert.dn
996           
997        elif proxyCert:
998            try:
999                userDN = str(X509CertParse(idKeys['proxyCert']).dn)
1000               
1001            except Exception, e:
1002                raise SessionMgrError, \
1003                "Parsing input proxy certificate DN for session connect: %s"%\
1004                                                                        str(e)
1005            try:
1006                userSess = self.__dnDict[userDN]
1007                       
1008            except KeyError:
1009                # User session not found with given proxy cert
1010                raise SessionMgrError, \
1011                    "No user session found matching input proxy certificate"
1012        else:
1013            # User session not found with given ID
1014            raise SessionMgrError, \
1015                                '"sessID" or "proxyCert" keywords must be set'
1016 
1017        # Delete associated sessions
1018        try:
1019            # Each session may have a number of session IDs allocated to
1020            # it
1021            for userSessID in userSess.sessIDlist:
1022                del self.__sessDict[userSessID]
1023
1024            del self.__dnDict[userDN]
1025           
1026        except Exception, e:
1027            raise SessionMgrError, "Deleting user session: %s" % e       
1028
1029
1030    #_________________________________________________________________________
1031    def reqAuthorisation(self, **reqKeys):
1032        """For given sessID, request authorisation from an Attribute Authority
1033        given by aaWSDL.  If sucessful, an attribute certificate is
1034        returned.
1035
1036        **reqKeys:            pass equivalent to XML as keywords instead.
1037                              See SessionMgrIO.AuthorisationReq class
1038        """
1039       
1040        # Web browser client input will include the encrypted address of the
1041        # Session Manager where the user's session is held.
1042        if 'encrSessMgrWSDLuri' in reqKeys:
1043           
1044            # Decrypt the URI for where the user's session resides
1045            userSessMgrWSDLuri = UserSession.decrypt(\
1046                                                reqKeys['encrSessMgrWSDLuri'],
1047                                                self.__prop['sessMgrEncrKey'])
1048                                               
1049            # Check the address against the address of THIS Session Manager 
1050            if userSessMgrWSDLuri != self.__prop['sessMgrWSDLuri']:
1051               
1052                # Session is held on a remote Session  Manager
1053                userSessMgrResp = self.__redirectAuthorisationReq(\
1054                                                        userSessMgrWSDLuri,
1055                                                        **reqKeys)
1056
1057                # Reset response by making a new AuthorisationResp object
1058                # The response from the remote Session Manager will still
1059                # contain the encrypted XML sent by it.  This should be
1060                # discarded
1061                return userSessMgrResp
1062
1063           
1064        # User's session resides with THIS Session Manager / no encrypted
1065        # WSDL address passed in (as in command line context for security) ...
1066
1067           
1068        # Retrieve session corresponding to user's session ID using relevant
1069        # input credential
1070        idKeys = {}
1071        if 'sessID' in reqKeys:
1072            idKeys['sessID'] = reqKeys['sessID']
1073           
1074        elif 'proxyCert' in reqKeys:
1075            idKeys['proxyCert'] = reqKeys['proxyCert']           
1076        else:
1077            raise SessionMgrError,'Expecting "sessID" or "proxyCert" keywords'
1078                               
1079        userSess = self.__connect2UserSession(**idKeys)
1080
1081
1082        # Copy keywords to be passed onto the request to the attribute
1083        # authority
1084        #
1085        # Nb. the following keys aren't required
1086        delKeys = ('proxyCert',
1087                   'sessID',
1088                   'encrCert',
1089                   'encrSessMgrWSDLuri', 
1090                   'aaPubKey')
1091                   
1092        aaKeys = dict([i for i in reqKeys.items() if i[0] not in delKeys])
1093
1094
1095        if 'aaPubKey' not in reqKeys:
1096            # Get public key using WS
1097            try:
1098                aaClnt = AttAuthorityClient(aaWSDL=reqKeys['aaWSDL'])               
1099                reqKeys['aaPubKey'] = aaClnt.getPubKey()
1100
1101            except Exception, e:
1102                raise SessionMgrError, \
1103                    "Retrieving Attribute Authority public key: "+ str(e)
1104                               
1105                                                       
1106        # Make a temporary file to hold Attribute Authority Public Key. 
1107        # The Credential Wallet needs this to encrypt requests to the
1108        # Attribute Authority
1109        try:
1110            aaPubKeyTmpFile = tempfile.NamedTemporaryFile()
1111            open(aaPubKeyTmpFile.name, "w").write(reqKeys['aaPubKey'])
1112            aaKeys['aaPubKeyFilePath'] = aaPubKeyTmpFile.name
1113           
1114        except IOError, (errNo, errMsg):
1115            raise SessionMgrError, "Making temporary file for Attribute " + \
1116                                  "Authority public key: %s" % errMsg
1117               
1118        except Exception, e:
1119            raise SessionMgrError, "Making temporary file for Attribute " + \
1120                                  "Authority public key: %s" % str(e)
1121
1122                                             
1123        # User's Credential Wallet carries out authorisation request to the
1124        # Attribute Authority
1125        try:
1126            attCert = userSess.credWallet.reqAuthorisation(**aaKeys)
1127           
1128            # AuthorisationResp class formats a response message in XML and
1129            # allow dictionary-like access to XML tags
1130            resp = AuthorisationResp(attCert=attCert, 
1131                                     statCode=AuthorisationResp.accessGranted)
1132           
1133        except CredWalletAuthorisationDenied, e:
1134            # Exception object containa a list of attribute certificates
1135            # which could be used to re-try to get authorisation via a mapped
1136            # certificate
1137            resp = AuthorisationResp(extAttCertList=e.extAttCertList,
1138                                     statCode=AuthorisationResp.accessDenied,
1139                                     errMsg=str(e))
1140       
1141        except Exception, e:
1142            # Some other error occured - create an error Authorisation
1143            # response
1144            resp = AuthorisationResp(statCode=AuthorisationResp.accessError,
1145                                     errMsg=str(e))
1146   
1147        return resp
1148
1149
1150    #_________________________________________________________________________
1151    def __redirectAuthorisationReq(self, userSessMgrWSDLuri, **reqKeys):
1152        """Handle case where User session resides on another Session Manager -
1153        forward the request"""
1154       
1155        # Instantiate WS proxy for remote session manager
1156        try:
1157            sessClnt = SessionClient(smWSDL=userSessMgrWSDLuri,
1158                                 clntPubKeyFilePath=self.__prop['certFile'],
1159                                 clntPriKeyFilePath=self.__prop['keyFile'])           
1160        except Exception, e:
1161            raise SessionMgrError(\
1162                        "Re-directing authorisation request to \"%s\": %s" % \
1163                        (userSessMgrWSDLuri, str(e)))
1164
1165           
1166        # Call remote session manager's authorisation request method
1167        # and return result to caller
1168        try:
1169            # encrCert key not needed - it gets set above via
1170            # 'clntPubKeyFilePath'
1171            if 'encrCert' in reqKeys:
1172                del reqKeys['encrCert']
1173               
1174            # Call remote SessionMgr where users session lies
1175            redirectAuthResp = sessClnt.reqAuthorisation(\
1176                                    clntPriKeyPwd=self.__prop['keyPPhrase'],
1177                                    **reqKeys)
1178         
1179            return redirectAuthResp
1180       
1181        except Exception, e:
1182            raise SessionMgrError(\
1183        "Forwarding Authorisation request for Session Manager \"%s\": %s" %\
1184                (userSessMgrWSDLuri, e))
1185
1186
1187    #_________________________________________________________________________
1188    def auditCredRepos(self):
1189        """Remove expired Attribute Certificates from the Credential
1190        Repository"""
1191        self.__credRepos.auditCredentials()
Note: See TracBrowser for help on using the repository browser.