source: TI12-security/trunk/python/ndg.security.server/ndg/security/server/SessionMgr/__init__.py @ 4129

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.server/ndg/security/server/SessionMgr/__init__.py@4129
Revision 4129, 49.0 KB checked in by cbyrom, 11 years ago (diff)

General refactoring and updating of code, including:

Removal of refC14nKw and singnedInfoC14nKw keywords in wsssecurity session manager config
(the refC14nInclNS and signedInfoC14nInclNS keywords are sufficient);
Creation of new DOM signature handler class, dom.py, based on the wsSecurity
class;
Abstraction of common code between dom.py and etree.py into new parent
class, BaseSignatureHandler?.py.
Fixing and extending use of properties in the SignatureHandler? code.
Fixing a few bugs with the original SignatureHandler? code.
Updating of test cases to new code/code structure.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1"""NDG Security server side session management and security includes
2UserSession and SessionMgr classes.
3
4NERC Data Grid Project
5"""
6__author__ = "P J Kershaw"
7__date__ = "02/06/05"
8__copyright__ = "(C) 2007 STFC & NERC"
9__license__ = \
10"""This software may be distributed under the terms of the Q Public
11License, version 1.0 or later."""
12__contact__ = "P.J.Kershaw@rl.ac.uk"
13__revision__ = '$Id$'
14
15# Modify sys.path when carrying out dynamic import for Credential Repository
16import sys
17
18# Time module for use with cookie expiry
19from time import strftime
20from datetime import datetime
21
22# For parsing of properties file
23try: # python 2.5
24    from xml.etree import cElementTree as ElementTree
25except ImportError:
26    # if you've installed it yourself it comes this way
27    import cElementTree as ElementTree
28
29# Base 64 encode session IDs if returned in strings - urandom's output may
30# not be suitable for printing!
31import base64
32
33# Session Manager URI in cookie
34from Crypto.Cipher import AES
35
36# Check Session Manager URI is encrypted
37from urllib import urlopen
38
39# Credential Wallet
40from ndg.security.common.CredWallet import CredWallet, CredRepos, \
41    CredWalletError, CredWalletAttributeRequestDenied
42
43from ndg.security.common.X509 import X500DN, X509Cert, X509CertParse, \
44                                X509CertExpired, X509CertInvalidNotBeforeTime
45
46# MyProxy server interface
47from ndg.security.server.MyProxy import *
48
49# Use client package to allow redirection of authorisation requests and
50# to retrieve Attribute Authority public key
51from ndg.security.common.SessionMgr import SessionMgrClient
52
53# Placing of session ID on client
54from ndg.security.common.sessionCookie import SessionCookie
55
56# Use in SessionMgr __redirectAttCertReq to retrieve and store Public
57# key
58import tempfile
59import urllib
60import logging
61log = logging.getLogger(__name__)
62
63#_____________________________________________________________________________
64class _SessionMgrException(Exception):
65    """Base class for all Exceptions in this module.  Overrides Exception to
66    enable writing to the log"""
67    def __init__(self, msg):
68        log.error(msg)
69        Exception.__init__(self, msg)
70
71#_____________________________________________________________________________
72class UserSessionError(_SessionMgrException):   
73    """Exception handling for NDG User Session class."""
74
75#_____________________________________________________________________________
76class InvalidUserSession(UserSessionError):   
77    """Problem with a session's validity"""
78
79#_____________________________________________________________________________
80class UserSessionExpired(UserSessionError):   
81    """Raise when session's X.509 cert. has expired"""
82
83#_____________________________________________________________________________
84class UserSessionX509CertNotBeforeTimeError(UserSessionError):   
85    """Raise when session's X.509 cert. not before time is before the current
86    system time"""
87   
88
89#_____________________________________________________________________________
90# Inheriting from 'object' allows Python 'new-style' class with Get/Set
91# access methods
92class UserSession(object):
93    """Session details - created when a user logs into NDG"""
94
95    #_________________________________________________________________________
96    def __init__(self, *credWalletArgs, **credWalletKeys):
97        """Initialise UserSession with args and keywords to CredWallet"""
98               
99        log.debug("UserSession.__init__ ...")
100       
101        # Each User Session has one or more browser sessions associated with
102        # it.  These are stored in a list
103        self.__sessIDlist = []
104        self.addNewSessID()
105        self.__credWallet = CredWallet(*credWalletArgs, **credWalletKeys)
106
107        log.info("Created a session with ID = %s" % self.__sessIDlist[-1])
108
109    #_________________________________________________________________________
110    # CredWallet access
111    def __getCredWallet(self):
112        """Get Credential Wallet instance"""
113        return self.__credWallet
114   
115    credWallet = property(fget=__getCredWallet,
116                          doc="Read-only access to CredWallet instance")
117
118
119    #_________________________________________________________________________
120    # CredWallet access
121    def __getSessIDlist(self):
122        """Get Session ID list - last item is latest allocated for this
123        session"""
124        return self.__sessIDlist
125   
126    sessIDlist = property(fget=__getSessIDlist,
127                          doc="Read-only access to Session ID list")
128
129
130    #_________________________________________________________________________       
131    def __latestSessID(self):
132        """Get the session ID most recently allocated"""
133        return self.__sessIDlist[-1]
134   
135    # Publish as an attribute
136    latestSessID = property(fget=__latestSessID,
137                            doc="Latest Session ID allocated")
138
139
140    #_________________________________________________________________________
141    def addNewSessID(self):
142        """Add a new session ID to be associated with this UserSession
143        instance"""
144
145        # base 64 encode output from urandom - raw output from urandom is
146        # causes problems when passed over SOAP.  A consequence of this is
147        # that the string length of the session ID will almost certainly be
148        # longer than SessionMgr.__sessIDlen
149        sessID = base64.urlsafe_b64encode(os.urandom(SessionCookie.sessIDlen))
150        self.__sessIDlist.append(sessID)
151
152
153    #_________________________________________________________________________
154    def __getExpiryStr(self):
155        """Return session expiry date/time as would be formatted for a cookie
156        """
157
158        try:
159            # Proxy certificate's not after time determines the expiry
160            dtNotAfter = self.credWallet.userCert.notAfter
161
162            return dtNotAfter.strftime(self.__sessCookieExpiryFmt)
163        except Exception, e:
164            UserSessionError("getExpiry: %s" % e)
165
166
167    #_________________________________________________________________________
168    @staticmethod
169    def encodeSessionMgrURI(txt, encrKey=None):
170        """Encode Session Manager URI to allow inclusion in a web browser
171        session cookie
172       
173        The address is optionally encrypted and then base 64 encoded use a
174        URL safe encoding
175       
176        @type encrKey: string
177        @param encrKey: 16 char encryption key used to encrypt the URI.  If
178        omitted or set None, the URI is not encrypted but merely base 64
179        encoded"""
180       
181        if encrKey is not None:
182            # Text length must be a multiple of 16 for AES encryption
183            try:
184                mod = len(txt) % 16
185                nPad = mod and 16 - mod or 0
186                   
187                # Add padding
188                paddedURI = txt + ''.join(' '*nPad)
189            except Exception, e:
190                raise UserSessionError("Padding text for encryption: %s" % e)
191       
192            # Encrypt
193            try:
194                txt = AES.new(encrKey, AES.MODE_ECB).encrypt(paddedURI)
195           
196            except Exception, e:
197                raise UserSessionError("Encrypting Session Manager URI: %s"%e)
198
199        try:
200            return base64.urlsafe_b64encode(txt)
201       
202        except Exception, e:
203            raise UserSessionError("Encoding Session Manager URI: %s"%e)
204       
205   
206    #_________________________________________________________________________
207    @staticmethod                                   
208    def decodeSessionMgrURI(txt, encrKey=None):
209        """Decode the URI from cookie set by another Session Manager.  This
210        is required when reading a session cookie to find out which
211        Session Manager holds the client's session
212       
213        @type txt: string
214        @param txt: base 64 encoded encrypted text
215       
216        @type encrKey: string
217        @param encrKey: 16 char encryption key used to encrypt the URI.  If
218        omitted or set None, the URI is assumed to be unencrypted"""
219
220        try:
221            # Convert if unicode type - unicode causes TypeError with
222            # base64.urlsafe_b64decode
223            if isinstance(txt, unicode):
224                txt = str(txt)
225               
226            # Decode from base 64
227            b64DecodedEncrTxt = base64.urlsafe_b64decode(txt)
228           
229        except Exception, e:
230            raise SessionMgrError("Decoding Session Manager URI: %s" % e)         
231
232
233        if encrKey is not None:
234            try:
235                aes = AES.new(encrKey, AES.MODE_ECB)
236               
237                # Decrypt and strip trailing spaces
238                return aes.decrypt(b64DecodedEncrTxt).strip()
239           
240            except Exception, e:
241                raise SessionMgrError("Decrypting Session Manager URI: %s"%e)           
242        else:
243            return b64DecodedEncrTxt
244       
245
246    #_________________________________________________________________________
247    def createCookie(self, 
248                     sessMgrURI,
249                     encrKey, 
250                     sessID=None,
251                     cookieDomain=None,
252                     asString=True):
253        """Create cookies for session ID Session Manager WSDL address
254
255        @type sessMgrURI: string
256        @param sessMgrURI: address for Session Mananger
257       
258        @type encrKey: string
259        @param encrKey: encryption key used to encrypted above URIs
260       
261        @type sessID: string
262        @param sessID: if no session ID is provided, use the latest one to
263        be allocated.
264       
265        @type cookieDomain: string
266        @param cookieDomain: domain set for cookie, if non set, web server
267        domain name is used.  Nb. Generalised domains which don't set a
268        specific host can be a security risk.
269       
270        @type asString: bool
271        @param asString: Set to True to return the cookie as string text. 
272        If False, it is returned as a SessionCookie instance.
273       
274        @rtype: SessionCookie / string depending on asString keyword
275        @return: session cookie"""
276       
277        log.debug("UserSession.createCookie ...")
278         
279        if sessID is None:
280            # Use latest session ID allocated if none was input
281            sessID = self.__sessIDlist[-1]
282           
283        elif not isinstance(sessID, basestring):
284            raise UserSessionError("Input session ID is not a valid string")
285                               
286            if sessID not in self.__sessIDlist:
287                raise UserSessionError("Input session ID not found in list")
288 
289 
290        encrSessMgrURI = self.encodeSessionMgrURI(sessMgrURI, encrKey)
291        dtExpiry = self.credWallet.userCert.notAfter
292       
293        # Call class method
294        cookieTags = SessionCookie.tags
295        cookieTagsKw = {}.fromkeys(cookieTags)
296        cookieTagsKw[cookieTags[0]] = sessID
297        cookieTagsKw[cookieTags[1]] = encrSessMgrURI
298       
299        sessCookie = SessionCookie(dtExpiry=dtExpiry,
300                                   cookieDomain=cookieDomain,
301                                   **cookieTagsKw)
302        if asString:
303            return str(sessCookie)
304        else:
305            return sessCookie
306   
307
308#_____________________________________________________________________________
309class SessionMgrError(_SessionMgrException):   
310    """Exception handling for NDG Session Manager class."""
311
312class SessionNotFound(SessionMgrError):
313    """Raise from SessionMgr.__connect2UserSession when session ID is not
314    found in the Session dictionary"""
315   
316# Find the missing elements in targ referenced in ref
317getMissingElem = lambda targ, ref: [e for e in targ if e not in ref]
318
319# Expand environment variables for ElementTree Element type.  Use in
320# readProperties.
321filtElemTxt = lambda elem: isinstance(elem.text, basestring) and \
322                os.path.expandvars(elem.text).strip() or elem.text
323
324
325#_____________________________________________________________________________
326class SessionMgr(dict):
327    """NDG authentication and session handling
328   
329    @type __validElem: dict
330    @cvar __validElem: list of the valid properties file element names and
331    sub-elements where appropriate
332   
333    @type __confDir: string
334    @cvar __confDir: configuration directory under $NDGSEC_DIR - default location
335    for properties file
336   
337    @type __propFileName: string
338    @cvar __propFileName: default file name for properties file under
339    __confDir
340    """
341
342    # valid configuration property keywords
343    __validElem = \
344    {
345        'portNum':                None,
346        'useSSL':                 None,
347        'sslCertFile':            None,
348        'sslKeyFile':             None,
349        'sslCACertDir':           None,
350        'useSignatureHandler':    None,
351        'caCertFileList':         [],
352        'certFile':               None,
353        'keyFile':                None,
354        'keyPwd':                 None,
355        'clntCertFile':           None,
356        'wssRefInclNS':           [],
357        'wssSignedInfoInclNS':    [],
358        'sessMgrEncrKey':         None, 
359        'sessMgrURI':             None,
360        'cookieDomain':           None, 
361        'myProxyProp':            None, 
362        'credReposProp':          ('modFilePath', 'modName', 'className', 
363                                   'propFile'),
364        'simpleCACltProp':        ('uri', 'xmlSigKeyFile', 'xmlSigCertFile', 
365                                   'xmlSigCertPwd')
366    }
367
368    __confDir = "conf"
369    __propFileName = "sessionMgrProperties.xml"
370     
371   
372    #_________________________________________________________________________
373    def __init__(self, propFilePath=None, credReposPPhrase=None, **prop):       
374        """Create a new session manager to manager NDG User Sessions
375       
376        propFilePath:        path to properties file
377        credReposPPhrase:    for credential repository if not set in
378                             properties file
379        **prop:              set any other properties corresponding to the
380                             tags in the properties file"""       
381
382        log.info("Initialising service ...")
383       
384        # Base class initialisation
385        dict.__init__(self)
386
387        # Key user sessions by session ID
388        self.__sessDict = {}
389
390        # Key user sessions by user DN
391        self.__dnDict = {}
392
393        # Credential Repository interface only set if properties file is set
394        # otherwise explict calls are necessary to set credReposProp via
395        # setProperties/readProperties and then loadCredReposInterface
396        self.__credRepos = None
397       
398        # MyProxy interface
399        try:
400            self.__myPx = MyProxyClient()
401           
402        except Exception, e:
403            raise SessionMgrError("Creating MyProxy interface: %s" % e)
404   
405        # Dictionary to hold properties     
406        self.__prop = {}
407       
408        # Set from input or use defaults based or environment variables
409        self.setPropFilePath(propFilePath)
410       
411        # Set properties from file
412        self.readProperties()
413
414        # Call here as we can safely expect that all Credential Repository
415        # parameters have been set above
416        self.loadCredReposInterface()
417
418        # Set any properties that were provided by keyword input
419        #
420        # Nb. If any are duplicated with tags in the properties file they
421        # will overwrite the latter
422        #
423        # loadCredReposInterface must be called explicitly if propFilePath
424        # wasn't set.  This is because if properties are passed by keyword
425        # alone there is no guarantee that those needed to load the interface
426        # will be present.  readProperties however, requires that all the
427        # required parameters are present in the properties file.
428        self.setProperties(**prop)
429       
430       
431    #_________________________________________________________________________
432    def loadCredReposInterface(self, credReposPPhrase=None, Force=False):
433        """
434        Pick up and instantiate Credential Repository interface class from
435        properties file settings/keywords set by setProperties/__init__
436       
437        @type credReposPPhrase: string
438        @param credReposPPhrase: password for CredentialRepository database
439        This is passed into the Credential Repository object but may not
440        be needed.  e.g. the custom class could pick up a password from
441        the properties file for it - ['credRepos']['propFilePath']
442       
443        @type Force: boolean
444        @param Force: flag to force reload of Credential Repository instance
445        """
446       
447        log.debug("Loading Credential Repository interface ...")
448       
449        # Don't bother if object has already been created.  Use Force=True
450        # to override and force reload
451        if Force is False and self.__credRepos is not None:
452            return
453       
454        # Credentials repository - permanent store of user credentials
455        try:
456            try:
457                # Module file path may be None if the new module to be loaded
458                # can be found in the existing system path           
459                if self.__prop['credReposProp']['modFilePath'] is not None:
460                    # Temporarily extend system path ready for import
461                    sysPathBak = sys.path[:]
462
463                    if not os.path.exists(\
464                              self.__prop['credReposProp']['modFilePath']):
465                        raise Exception, "File path '%s' doesn't exist" % \
466                              self.__prop['credReposProp']['modFilePath']
467                             
468                    sys.path.append(\
469                                self.__prop['credReposProp']['modFilePath'])
470               
471                # Import module name specified in properties file
472                credReposMod = \
473                    __import__(self.__prop['credReposProp']['modName'],
474                               globals(),
475                               locals(),
476                               [self.__prop['credReposProp']['className']])
477   
478                credReposClass = eval(\
479                'credReposMod.' + self.__prop['credReposProp']['className'])
480            finally:
481                try:
482                    sys.path[:] = sysPathBak
483                except NameError:
484                    # sysPathBak may not have been defined
485                    pass
486               
487        except KeyError, e:
488            raise SessionMgrError('Missing %s element for credential '
489                                  'repository module import' % str(e))
490                       
491        except Exception, e:
492            raise SessionMgrError('Importing credential repository module: %s'\
493                                  % str(e))
494
495        # Check class inherits from CredWallet.CredRepos abstract base class
496        if not issubclass(credReposClass, CredRepos):
497            raise SessionMgrError("Credential Repository class %s must be "
498                                  "inherited from %s" % \
499                                  (credReposClass, CredRepos))
500
501        # Instantiate custom class
502        try:
503            self.__credRepos = credReposClass(\
504                      propFilePath=self.__prop['credReposProp']['propFile'],
505                      dbPPhrase=credReposPPhrase)
506           
507        except Exception, e:
508            raise SessionMgrError(
509            "Error instantiating Credential Repository interface: " + str(e))
510     
511        log.info(\
512'Instantiated "%s" class from Credential Repository module: "%s" file path %s' % \
513         (self.__prop['credReposProp']['className'],
514          self.__prop['credReposProp']['modName'],
515          self.__prop['credReposProp']['modFilePath'] or "from PYTHONPATH"))
516
517       
518    #_________________________________________________________________________       
519    def __repr__(self):
520        """Return file properties dictionary as representation"""
521        return repr(self.__prop)
522
523    def __delitem__(self, key):
524        "Session Manager keys cannot be removed"       
525        raise KeyError('Keys cannot be deleted from '+self.__class__.__name__)
526
527
528    def __getitem__(self, key):
529        self.__class__.__name__ + """ behaves as data dictionary of Session
530        Manager properties
531        """
532        if key not in self.__prop:
533            raise KeyError("Invalid key '%s'" % key)
534       
535        return self.__prop[key]
536   
537   
538    def __setitem__(self, key, item):
539        self.__class__.__name__ + """ behaves as data dictionary of Session
540        Manager properties"""
541        self.setProperties(**{key: item})
542           
543    def get(self, kw):
544        return self.__prop.get(kw)
545
546    def clear(self):
547        raise KeyError("Data cannot be cleared from "+self.__class__.__name__)
548   
549    def keys(self):
550        return self.__prop.keys()
551
552    def items(self):
553        return self.__prop.items()
554
555    def values(self):
556        return self.__prop.values()
557
558    def has_key(self, key):
559        return self.__prop.has_key(key)
560
561    # 'in' operator
562    def __contains__(self, key):
563        return key in self.__prop
564
565
566    #_________________________________________________________________________
567    def setPropFilePath(self, val=None):
568        """Set properties file from input or based on environment variable
569        settings"""
570        if not val:
571            if 'NDGSEC_SM_PROPFILEPATH' in os.environ:
572                val = os.environ['NDGSEC_SM_PROPFILEPATH']
573               
574                log.debug(\
575                'Set properties file path "%s" from "NDGSEC_SM_PROPFILEPATH"'\
576                % val)
577
578            elif 'NDGSEC_DIR' in os.environ:
579                val = os.path.join(os.environ['NDGSEC_DIR'], 
580                                   self.__class__.__confDir,
581                                   self.__class__.__propFileName)
582
583                log.debug('Set properties file path %s from "NDGSEC_DIR"'%val)
584            else:
585                raise AttributeError('Unable to set default Session '
586                                     'Manager properties file path: neither ' 
587                                     '"NDGSEC_SM_PROPFILEPATH" or "NDGSEC_DIR"'
588                                     ' environment variables are set')
589        else:
590             log.debug('Set properties file path %s from user input' % val)       
591
592        if not isinstance(val, basestring):
593            raise AttributeError("Input Properties file path must be a valid "
594                                 "string.")
595     
596        self.__propFilePath = val
597       
598    # Also set up as a property
599    propFilePath = property(fset=setPropFilePath,
600                            doc="Set the path to the properties file")   
601           
602
603    #_________________________________________________________________________
604    def readProperties(self, propElem=None):
605        """Read Session Manager properties from an XML file or cElementTree
606        node
607       
608        @type propElem: Element
609        @param propElem: pass in existing ElementTree treeroot
610        """
611
612        log.debug("Reading properties file ...")
613       
614        if not propElem:
615            try:
616                tree = ElementTree.parse(self.__propFilePath)
617                propElem = tree.getroot()
618
619            except IOError, e:
620                raise SessionMgrError(
621                                "Error parsing properties file \"%s\": %s" % \
622                                (e.filename, e.strerror))             
623            except Exception, e:
624                raise SessionMgrError(
625                    "Error parsing properties file: \"%s\": %s" % \
626                    (self.__propFilePath, e))
627
628        if propElem is None:
629            raise SessionMgrError(
630                            "Parsing properties: root element is not defined")
631 
632        missingElem = []
633        invalidElem = []
634        try:
635            for elem in propElem:
636                if elem.tag == 'myProxyProp':
637                    self.__myPx.readProperties(propElem=elem)
638   
639                elif elem.tag == 'credReposProp':
640                    self.__prop['credReposProp'] = \
641                                dict([(e.tag, filtElemTxt(e)) for e in elem])
642                           
643                    # Check for missing elements
644                    missingElem.extend(getMissingElem(\
645                                           self.__validElem['credReposProp'],
646                                           self.__prop['credReposProp']))
647                   
648                elif elem.tag == 'simpleCACltProp':
649                    self.__prop['simpleCACltProp'] = \
650                                dict([(e.tag, filtElemTxt(e)) for e in elem])
651                           
652                    # Check for missing elements
653                    missingElem.extend(getMissingElem(\
654                                       self.__validElem['simpleCACltProp'],
655                                       self.__prop['simpleCACltProp']))
656                   
657                elif elem.tag in self.__validElem:
658                    # Strip white space but not in the case of password
659                    # field as password might contain leading or
660                    # trailing white space
661                    if isinstance(self.__validElem[elem.tag], list):
662                        if len(elem) == 0 and elem.text is not None:
663                            # Treat as a list of space separated elements
664                            self.__prop[elem.tag] = elem.text.split()
665                        else:
666                            # Parse from a list of sub-elements
667                            self.__prop[elem.tag] = [filtElemTxt(subElem) \
668                                                     for subElem in elem]
669                       
670                    elif elem.text is not None and elem.tag != 'keyPwd':                       
671                        if elem.text.isdigit():
672                            self.__prop[elem.tag] = int(elem.text)
673                        else:                           
674                            # Check for environment variables in file paths
675                            self.__prop[elem.tag] = filtElemTxt(elem)       
676                    else:
677                        self.__prop[elem.tag] = elem.text
678                else:
679                    invalidElem.append(elem.tag)
680               
681        except Exception, e:
682            raise SessionMgrError(
683                'Error parsing tag "%s" in properties file: %s' % (elem.tag,e))
684
685        missingElem.extend(getMissingElem(self.__prop, self.__validElem))
686        errMsg = ''
687       
688        if invalidElem != []:
689            errMsg = 'Invalid elements: "%s"\n' % '", "'.join(invalidElem)
690
691        if missingElem != []:
692            errMsg += 'Missing elements: "%s"\n' % '", "'.join(missingElem)
693
694        if errMsg:
695            raise SessionMgrError(errMsg +  " for properties file")
696
697        log.info('Loaded properties from "%s"' % self.__propFilePath)
698       
699
700    #_________________________________________________________________________
701    def setProperties(self, **prop):
702        """Update existing properties from an input dictionary
703        Check input keys are valid names"""
704       
705        log.debug("Calling SessionMgr.setProperties with kw = %s" % prop)
706       
707        for key in prop.keys():
708            if key not in self.__validElem:
709                raise SessionMgrError("Property name \"%s\" is invalid" % key)
710
711
712        for key, value in prop.items():
713                       
714            if key == 'myProxyProp':
715                self.__myPx.setProperties(prop[key])
716   
717            elif key == 'credReposProp':
718                self.__prop['credReposProp'] = prop[key].copy()
719
720            elif key in self.__validElem:
721                # Only update other keys if they are not None or ""
722                if value:
723                    if isinstance(value, basestring):
724                        self.__prop[key] = os.path.expandvars(value).strip()
725                    else:
726                        self.__prop[key] = value             
727            else:
728                raise SessionMgrError(\
729                    "Key \"%s\" is not a valid Session Manager property" % key)
730       
731       
732    #_________________________________________________________________________
733    def addUser(self, username, passphrase=None):       
734        """Register a new user with an NDG data centre
735       
736        addUser([caConfigFilePath, ]|[, caPassPhrase]
737                |[, userName=u, pPhrase=p])
738
739        returns XML formatted response message
740       
741        caConfigFilePath|caPassPhrase:  pass phrase for SimpleCA's
742                                        certificate.  Set via file or direct
743                                        string input respectively.  Set here
744                                        to override setting [if any] made at
745                                        object creation.
746       
747                                        Passphrase is only required if
748                                        SimpleCA is instantiated on the local
749                                        machine.  If SimpleCA WS is called no
750                                        passphrase is required.
751                                       
752        **kw:                      use as alternative to
753                                        reqXMLtxt keyword - pass in
754                                        username and pass-phrase for new user
755                                        unencrypted as keywords username
756                                        and pPhrase respectively."""
757
758        log.debug("Calling SessionMgr.addUser ...")
759       
760        # Ask CA to sign certificate
761       
762        # Add new user certificate to MyProxy Repository
763        self.__myPx.store(username, 
764                          certFile,
765                          keyFile,
766                          ownerCertFile=None,
767                          ownerKeyFile=None,
768                          ownerPassphrase=None,
769                          lifetime=None,
770                          force=True)
771
772        return userDN           
773   
774   
775    def getSessionStatus(self, sessID=None, userDN=None):
776        """Check the status of a given session identified by sessID or
777        user Distinguished Name
778       
779        @type sessID: string
780        @param sessID: session identifier as returned from a call to connect()
781        @type userDN: string
782        @param userDN: user Distinguished Name of session to check
783        @rtype: bool
784        @return: True if session is active, False if no session found"""
785
786        log.debug("Calling SessionMgr.getSessionStatus ...")
787       
788        # Look for a session corresponding to this ID
789        if sessID and userDN:
790            raise SessionMgrError('Only "SessID" or "userDN" keywords may be '
791                                  'set')
792        elif sessID:
793            if sessID in self.__sessDict:               
794                log.info("Session found with ID = %s" % sessID)
795                return True
796            else:
797                # User session not found with given ID
798                log.info("No user session found matching input ID = %s" % \
799                         sessID)
800                return False
801                         
802        elif userDN:
803            try:
804                # Enables re-ordering of DN fields for following dict search
805                userDN = str(X500DN(userDN))
806               
807            except Exception, e:
808                log.error("Parsing input user certificate DN for "
809                          "getSessionStatus: %s" % e)
810                raise
811           
812            if userDN in self.__dnDict:
813                log.info("Session found with DN = %s" % userDN)
814                return True                       
815            else:
816                # User session not found with given proxy cert
817                log.info("No user session found matching input userDN = %s" %\
818                         userDN)
819                return False
820
821   
822    #_________________________________________________________________________       
823    def connect(self, 
824                createServerSess=True,
825                username=None,
826                passphrase=None, 
827                userCert=None, 
828                sessID=None):       
829        """Create a new user session or connect to an existing one:
830
831        connect([createServerSess=True/False, ]|[, username=u, passphrase=p]|
832                [, userCert=px]|[, sessID=id])
833
834        @type createUserSess: bool
835        @param createServerSess: If set to True, the SessionMgr will create
836        and manage a session for the user.  For command line case, it's
837        possible to choose to have a client or server side session using this
838        keyword.
839       
840        @type username: string
841        @param username: username of account to connect to
842
843        @type passphrase: string
844        @param passphrase: pass-phrase - user with username arg
845       
846        @type userCert: string
847        @param userCert: connect to existing session with proxy certificate
848        corresponding to user.  username/pass-phrase not required
849       
850        @type sessID: string
851        @param sessID: connect to existing session corresponding to this ID.
852        username/pass-phrase not required.
853       
854        @rtype: tuple
855        @return user certificate, private key, issuing certificate and
856        session ID respectively.  Session ID will be none if createUserSess
857        keyword is set to False
858        """
859       
860        log.debug("Calling SessionMgr.connect ...")
861       
862        # Initialise proxy cert to be returned
863        userCert = None
864       
865        if sessID is not None:           
866            # Connect to an existing session identified by a session ID and
867            # return equivalent proxy cert
868            userSess = self.__connect2UserSession(sessID=sessID)
869            userCert = userSess.credWallet.userCert
870           
871        elif userCert is not None:
872            # Connect to an existing session identified by a proxy
873            # certificate
874            userSess = self.__connect2UserSession(userCert=userCert)
875            sessID = userSess.latestSessID
876           
877        else:
878            # Create a fresh session
879            try:           
880                # Get a proxy certificate to represent users ID for the new
881                # session
882                userCreds = self.__myPx.logon(username, passphrase)
883               
884                # unpack
885                userCert = userCreds[0]
886                userPriKey = userCreds[1]
887               
888                # Issuing cert is needed only if userCert is a proxy
889                issuingCert = len(userCreds) > 2 and userCreds[2] or None
890               
891            except Exception, e:
892                raise SessionMgrError("Delegating from MyProxy: %s" % e)
893
894            if createServerSess:
895                # Session Manager creates and manages user's session
896                userSess = self.__createUserSession(userCert, 
897                                                    userPriKey, 
898                                                    issuingCert)
899                sessID = userSess.latestSessID
900            else:
901                sessID = None
902                               
903        # Return proxy details and cookie
904        return userCert, userPriKey, issuingCert, sessID       
905       
906       
907    #_________________________________________________________________________       
908    def __createUserSession(self, *creds):
909        """Create a new user session from input user credentials       
910        and return
911       
912        @type creds: tuple
913        @param creds: tuple containing user certificate, private key
914        and optionally an issuing certificate.  An issuing certificate is
915        present if user certificate is a proxy and therefore it's issuer is
916        other than the CA."""
917       
918        log.debug("Calling SessionMgr.__createUserSession ...")
919       
920        # Check for an existing session for the same user
921        try:
922            userDN = str(X509CertParse(creds[0]).dn)
923           
924        except Exception, e:
925            raise SessionMgrError(\
926                "Parsing input certificate DN for session create: %s" % \
927                                                                    str(e))
928
929        if userDN in self.__dnDict:
930            # Update existing session with user cert and add a new
931            # session ID to access it - a single session can be accessed
932            # via multiple session IDs e.g. a user may wish to access the
933            # same session from the their desktop PC and their laptop.
934            # Different session IDs are allocated in each case.
935            userSess = self.__dnDict[userDN]
936            userSess.addNewSessID()           
937        else:
938            # Create a new user session using the new user certificate
939            # and session ID
940            #
941            wssSignatureHandlerKw = {
942            'refC14nInclNS': self.__prop.get('wssRefInclNS', []),
943            'signedInfoC14nInclNS': self.__prop.get('wssSignedInfoInclNS', [])}
944
945            try:   
946                userSess = UserSession(credRepos=self.__credRepos, 
947                             caCertFilePathList=self.__prop['caCertFileList'],
948                             wssSignatureHandlerKw=wssSignatureHandlerKw,
949                             *creds)     
950            except Exception, e:
951                raise SessionMgrError("Creating User Session: %s" % e)
952
953            # Also allow access by user DN
954            self.__dnDict[userDN] = userSess
955
956       
957        newSessID = userSess.latestSessID
958       
959        # Check for unique session ID
960        if newSessID in self.__sessDict:
961            raise SessionMgrError(
962                "New Session ID is already in use:\n\n %s" % newSessID)
963
964        # Add new session to list                 
965        self.__sessDict[newSessID] = userSess
966                       
967        # Return new session
968        return userSess
969
970
971    #_________________________________________________________________________       
972    def __connect2UserSession(self, userCert=None, sessID=None):
973        """Connect to an existing session by providing a valid session ID or
974        proxy certificate
975
976        __connect2UserSession([userCert]|[sessID])
977       
978        @type userCert: string
979        @param userCert: proxy certificate string corresponding to an
980        existing session to connect to.
981       
982        @type sessID: string
983        @param sessID: similiarly, a web browser session ID linking to an
984        an existing session."""
985       
986        log.debug("Calling SessionMgr.__connect2UserSession ...")
987           
988        # Look for a session corresponding to this ID
989        if sessID:
990            try:
991                # Check matched session has not expired
992                userSess = self.__sessDict[sessID]
993               
994            except KeyError:
995                # User session not found with given ID
996                raise SessionNotFound, \
997                        "No user session found matching input session ID"
998
999            log.info("Connecting to session user DN = %s using ID = %s" % \
1000                     (userSess.credWallet.userCert.dn, sessID))
1001                               
1002        elif isinstance(userCert, basestring):
1003            try:
1004                userDN = str(X509CertParse(userCert).dn)
1005               
1006            except Exception, e:
1007                raise SessionMgrError(
1008                "Parsing input user certificate DN for session connect: %s" %e)
1009
1010            try:
1011                userSess = self.__dnDict[userDN]
1012                       
1013            except KeyError:
1014                # User session not found with given proxy cert
1015                raise SessionNotFound, \
1016                    "No user session found matching input proxy certificate"
1017           
1018            log.info("Connecting to session ID = %s using cert, DN = %s" % \
1019                     (userSess.sessIDlist, userDN))
1020                   
1021        elif isinstance(userCert, X509Cert):
1022            try:
1023                userDN = str(userCert.dn)
1024               
1025            except Exception, e:
1026                raise SessionMgrError(\
1027                "Parsing input user certificate DN for session connect: %s" %e)
1028           
1029            try:
1030                userSess = self.__dnDict[userDN]
1031                       
1032            except KeyError:
1033                # User session not found with given proxy cert
1034                raise SessionNotFound, \
1035                    "No user session found matching input proxy certificate"           
1036
1037            log.info("Connecting to session ID = %s using cert, DN = %s" % \
1038                     (userSess.sessIDlist, userDN))
1039        else:
1040            raise SessionMgrError,\
1041                                '"sessID" or "userCert" keywords must be set'
1042                       
1043        try:
1044            userSess.credWallet.isValid(raiseExcep=True)
1045            return userSess
1046       
1047        except X509CertInvalidNotBeforeTime, e:
1048            # ! Delete user session since it's user certificate is invalid
1049            self.deleteUserSession(userSess=userSess)
1050            raise UserSessionX509CertNotBeforeTimeError(
1051                                    "User session is invalid: %s" % e)       
1052   
1053        except X509CertExpired, e:
1054            # ! Delete user session since it's user certificate is invalid
1055            self.deleteUserSession(userSess=userSess)
1056            raise UserSessionExpired, "User session is invalid: %s" % e         
1057       
1058        except Exception, e:
1059            raise InvalidUserSession, "User session is invalid: %s" % e
1060               
1061
1062
1063    #_________________________________________________________________________       
1064    def deleteUserSession(self, sessID=None, userCert=None, userSess=None):
1065        """Delete an existing session by providing a valid session ID or
1066        proxy certificate - use for user logout
1067
1068        deleteUserSession([userCert]|[sessID]|[userSess])
1069       
1070        @type userCert: ndg.security.common.X509.X509Cert
1071        @param userCert: proxy certificate corresponding to an existing
1072        session to connect to.
1073       
1074        @type sessID: string
1075        @param sessID: similiarly, a web browser session ID linking to an
1076        an existing session.
1077       
1078        @type userSess: UserSession
1079        @param userSess: user session object to be deleted
1080        """
1081       
1082        log.debug("Calling SessionMgr.deleteUserSession ...")
1083       
1084        # Look for a session corresponding to the session ID/proxy cert.
1085        if sessID:
1086            try:
1087                userSess = self.__sessDict[sessID]
1088               
1089            except KeyError:
1090                raise SessionMgrError(
1091                    "Deleting user session - no matching session ID exists")
1092
1093            # Get associated user Distinguished Name
1094            userDN = str(userSess.credWallet.userCert.dn)
1095           
1096        elif userCert:
1097            try:
1098                userDN = str(userCert.dn)
1099               
1100            except Exception, e:
1101                raise SessionMgrError(
1102                "Parsing input proxy certificate DN for session connect: %s"%\
1103                                                                        str(e))
1104            try:
1105                userSess = self.__dnDict[userDN]
1106                       
1107            except KeyError:
1108                # User session not found with given proxy cert
1109                raise SessionMgrError(
1110                    "No user session found matching input proxy certificate")
1111       
1112        if userSess:
1113            userDN = str(userSess.credWallet.userCert.dn)
1114        else:
1115            # User session not found with given ID
1116            raise SessionMgrError(
1117                    '"sessID", "userCert" or "userSess" keywords must be set')
1118 
1119        # Delete associated sessions
1120        try:
1121            # Each session may have a number of session IDs allocated to
1122            # it. 
1123            #
1124            # Use pop rather than del so that key errors are ignored
1125            for userSessID in userSess.sessIDlist:
1126                self.__sessDict.pop(userSessID, None)
1127
1128            self.__dnDict.pop(userDN, None)
1129       
1130        except Exception, e:
1131            raise SessionMgrError("Deleting user session: %s" % e) 
1132
1133        log.info("Deleted user session: user DN = %s, sessID = %s" % \
1134                 (userDN, userSess.sessIDlist))
1135
1136    #_________________________________________________________________________
1137    def getAttCert(self,
1138                   userCert=None,
1139                   sessID=None,
1140                   encrSessMgrURI=None,
1141                   **credWalletKw):
1142        """For a given user, request Attribute Certificate from an Attribute
1143        Authority given by service URI.  If sucessful, an attribute
1144        certificate is added to the user session credential wallet and also
1145        returned from this method
1146
1147        @type userCert: string
1148        @param userCert: user's certificate to key into their session
1149       
1150        @type sessID: string
1151        @param sessID: user's ID to key into their session
1152       
1153        @type encrSessMgrURI: string
1154        @param encrSessMgrURI: URI for remote session manager to forward a
1155        request to.  This effectively use THIS session manager as a proxy to
1156        another.  This URI is encrypted with a shared key.  The key is stored
1157        in the property file 'sessMgrEncrKey' element.  *** This functionality
1158        is redundant for NDG BETA delivery ***
1159       
1160        @type credWalletKw: dict
1161        @param **credWalletKw: keywords to CredWallet.getAttCert
1162        """
1163       
1164        log.debug("Calling SessionMgr.getAttCert ...")
1165       
1166        # Web browser client input will include the encrypted address of the
1167        # Session Manager where the user's session is held.
1168        if encrSessMgrURI:
1169           
1170            # Decrypt the URI for where the user's session resides
1171            userSessMgrURI = UserSession.decodeSessionMgrURI(encrSessMgrURI,
1172                                               self.__prop['sessMgrEncrKey'])
1173                                               
1174            # Check the address against the address of THIS Session Manager 
1175            if userSessMgrURI != self.__prop['sessMgrURI']:
1176               
1177                # Session is held on a remote Session  Manager
1178                userSessMgrResp = self.__redirectAttCertReq(userSessMgrURI,
1179                                                            sessID=sessID,
1180                                                            userCert=userCert,
1181                                                            **credWalletKw)
1182
1183                # Reset response by making a new AuthorisationResp object
1184                # The response from the remote Session Manager will still
1185                # contain the encrypted XML sent by it.  This should be
1186                # discarded
1187                return userSessMgrResp
1188
1189           
1190        # User's session resides with THIS Session Manager / no encrypted
1191        # URI address passed in (as in command line use case for security) ...
1192
1193           
1194        # Retrieve session corresponding to user's session ID using relevant
1195        # input credential
1196        userSess = self.__connect2UserSession(sessID=sessID,userCert=userCert)
1197
1198
1199        # User's Credential Wallet carries out attribute request to the
1200        # Attribute Authority
1201        try:
1202            attCert = userSess.credWallet.getAttCert(**credWalletKw)
1203            return attCert, None, []
1204           
1205        except CredWalletAttributeRequestDenied, e:
1206            # Exception object contains a list of attribute certificates
1207            # which could be used to re-try to get authorisation via a mapped
1208            # certificate
1209            return None, str(e), e.extAttCertList
1210
1211
1212    #_________________________________________________________________________
1213    def __redirectAttCertReq(self, userSessMgrURI, **kw):
1214        """Handle case where User session resides on another Session Manager -
1215        forward the request
1216       
1217        @type userSessMgrURI: string
1218        @param userSessMgrURI: address of remote session manager where user
1219        session is held
1220       
1221        @type **kw: dict
1222        @param **kw: same keywords which apply to getAttCert call"""
1223
1224       
1225        log.info('SessionMgr.__redirectAttCertReq - redirecting to "%s"' % \
1226                 userSessMgrURI)
1227               
1228       
1229        # Instantiate WS proxy for remote session manager
1230        try:
1231            sessMgrClnt = SessionMgrClient(uri=userSessMgrURI,
1232                                 signingCertFilePath=self.__prop['certFile'],
1233                                 signingPriKeyFilePath=self.__prop['keyFile'],
1234                                 signingPriKeyPwd=self.__prop['keyPwd'])           
1235        except Exception, e:
1236            raise SessionMgrError(
1237                "Re-directing attribute certificate request to \"%s\": %s" % \
1238                (userSessMgrURI, str(e)))
1239
1240           
1241        # Call remote session manager's authorisation request method
1242        # and return result to caller
1243        try:
1244            # Call remote SessionMgr where users session lies
1245            resp = sessMgrClnt.getAttCert(**kw)       
1246            return resp
1247       
1248        except Exception, e:
1249            raise SessionMgrError(
1250        "Forwarding Authorisation request for Session Manager \"%s\": %s" %\
1251                (userSessMgrURI, e))
1252
1253
1254    #_________________________________________________________________________
1255    def auditCredRepos(self):
1256        """Remove expired Attribute Certificates from the Credential
1257        Repository"""
1258        log.debug("Calling SessionMgr.auditCredRepos ...")
1259        self.__credRepos.auditCredentials()
Note: See TracBrowser for help on using the repository browser.