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

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

Fully implement usage of new properties file parser in the SessionMgr?
service.

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