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

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

Fix to getSessionStatus for userDN input - X500DN instead of X509DN

  • 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            'refC14nKw': {'unsuppressedPrefixes': 
943                          self.__prop.get('wssRefInclNS', [])},
944            'signedInfoC14nKw':{'unsuppressedPrefixes':
945                                self.__prop.get('wssSignedInfoInclNS', [])}}
946
947            try:   
948                userSess = UserSession(credRepos=self.__credRepos, 
949                             caCertFilePathList=self.__prop['caCertFileList'],
950                             wssSignatureHandlerKw=wssSignatureHandlerKw,
951                             *creds)     
952            except Exception, e:
953                raise SessionMgrError("Creating User Session: %s" % e)
954
955            # Also allow access by user DN
956            self.__dnDict[userDN] = userSess
957
958       
959        newSessID = userSess.latestSessID
960       
961        # Check for unique session ID
962        if newSessID in self.__sessDict:
963            raise SessionMgrError(
964                "New Session ID is already in use:\n\n %s" % newSessID)
965
966        # Add new session to list                 
967        self.__sessDict[newSessID] = userSess
968                       
969        # Return new session
970        return userSess
971
972
973    #_________________________________________________________________________       
974    def __connect2UserSession(self, userCert=None, sessID=None):
975        """Connect to an existing session by providing a valid session ID or
976        proxy certificate
977
978        __connect2UserSession([userCert]|[sessID])
979       
980        @type userCert: string
981        @param userCert: proxy certificate string corresponding to an
982        existing session to connect to.
983       
984        @type sessID: string
985        @param sessID: similiarly, a web browser session ID linking to an
986        an existing session."""
987       
988        log.debug("Calling SessionMgr.__connect2UserSession ...")
989           
990        # Look for a session corresponding to this ID
991        if sessID:
992            try:
993                # Check matched session has not expired
994                userSess = self.__sessDict[sessID]
995               
996            except KeyError:
997                # User session not found with given ID
998                raise SessionNotFound, \
999                        "No user session found matching input session ID"
1000
1001            log.info("Connecting to session user DN = %s using ID = %s" % \
1002                     (userSess.credWallet.userCert.dn, sessID))
1003                               
1004        elif isinstance(userCert, basestring):
1005            try:
1006                userDN = str(X509CertParse(userCert).dn)
1007               
1008            except Exception, e:
1009                raise SessionMgrError(
1010                "Parsing input user certificate DN for session connect: %s" %e)
1011
1012            try:
1013                userSess = self.__dnDict[userDN]
1014                       
1015            except KeyError:
1016                # User session not found with given proxy cert
1017                raise SessionNotFound, \
1018                    "No user session found matching input proxy certificate"
1019           
1020            log.info("Connecting to session ID = %s using cert, DN = %s" % \
1021                     (userSess.sessIDlist, userDN))
1022                   
1023        elif isinstance(userCert, X509Cert):
1024            try:
1025                userDN = str(userCert.dn)
1026               
1027            except Exception, e:
1028                raise SessionMgrError(\
1029                "Parsing input user certificate DN for session connect: %s" %e)
1030           
1031            try:
1032                userSess = self.__dnDict[userDN]
1033                       
1034            except KeyError:
1035                # User session not found with given proxy cert
1036                raise SessionNotFound, \
1037                    "No user session found matching input proxy certificate"           
1038
1039            log.info("Connecting to session ID = %s using cert, DN = %s" % \
1040                     (userSess.sessIDlist, userDN))
1041        else:
1042            raise SessionMgrError,\
1043                                '"sessID" or "userCert" keywords must be set'
1044                       
1045        try:
1046            userSess.credWallet.isValid(raiseExcep=True)
1047            return userSess
1048       
1049        except X509CertInvalidNotBeforeTime, e:
1050            # ! Delete user session since it's user certificate is invalid
1051            self.deleteUserSession(userSess=userSess)
1052            raise UserSessionX509CertNotBeforeTimeError(
1053                                    "User session is invalid: %s" % e)       
1054   
1055        except X509CertExpired, e:
1056            # ! Delete user session since it's user certificate is invalid
1057            self.deleteUserSession(userSess=userSess)
1058            raise UserSessionExpired, "User session is invalid: %s" % e         
1059       
1060        except Exception, e:
1061            raise InvalidUserSession, "User session is invalid: %s" % e
1062               
1063
1064
1065    #_________________________________________________________________________       
1066    def deleteUserSession(self, sessID=None, userCert=None, userSess=None):
1067        """Delete an existing session by providing a valid session ID or
1068        proxy certificate - use for user logout
1069
1070        deleteUserSession([userCert]|[sessID]|[userSess])
1071       
1072        @type userCert: ndg.security.common.X509.X509Cert
1073        @param userCert: proxy certificate corresponding to an existing
1074        session to connect to.
1075       
1076        @type sessID: string
1077        @param sessID: similiarly, a web browser session ID linking to an
1078        an existing session.
1079       
1080        @type userSess: UserSession
1081        @param userSess: user session object to be deleted
1082        """
1083       
1084        log.debug("Calling SessionMgr.deleteUserSession ...")
1085       
1086        # Look for a session corresponding to the session ID/proxy cert.
1087        if sessID:
1088            try:
1089                userSess = self.__sessDict[sessID]
1090               
1091            except KeyError:
1092                raise SessionMgrError(
1093                    "Deleting user session - no matching session ID exists")
1094
1095            # Get associated user Distinguished Name
1096            userDN = str(userSess.credWallet.userCert.dn)
1097           
1098        elif userCert:
1099            try:
1100                userDN = str(userCert.dn)
1101               
1102            except Exception, e:
1103                raise SessionMgrError(
1104                "Parsing input proxy certificate DN for session connect: %s"%\
1105                                                                        str(e))
1106            try:
1107                userSess = self.__dnDict[userDN]
1108                       
1109            except KeyError:
1110                # User session not found with given proxy cert
1111                raise SessionMgrError(
1112                    "No user session found matching input proxy certificate")
1113       
1114        if userSess:
1115            userDN = str(userSess.credWallet.userCert.dn)
1116        else:
1117            # User session not found with given ID
1118            raise SessionMgrError(
1119                    '"sessID", "userCert" or "userSess" keywords must be set')
1120 
1121        # Delete associated sessions
1122        try:
1123            # Each session may have a number of session IDs allocated to
1124            # it. 
1125            #
1126            # Use pop rather than del so that key errors are ignored
1127            for userSessID in userSess.sessIDlist:
1128                self.__sessDict.pop(userSessID, None)
1129
1130            self.__dnDict.pop(userDN, None)
1131       
1132        except Exception, e:
1133            raise SessionMgrError("Deleting user session: %s" % e) 
1134
1135        log.info("Deleted user session: user DN = %s, sessID = %s" % \
1136                 (userDN, userSess.sessIDlist))
1137
1138    #_________________________________________________________________________
1139    def getAttCert(self,
1140                   userCert=None,
1141                   sessID=None,
1142                   encrSessMgrURI=None,
1143                   **credWalletKw):
1144        """For a given user, request Attribute Certificate from an Attribute
1145        Authority given by service URI.  If sucessful, an attribute
1146        certificate is added to the user session credential wallet and also
1147        returned from this method
1148
1149        @type userCert: string
1150        @param userCert: user's certificate to key into their session
1151       
1152        @type sessID: string
1153        @param sessID: user's ID to key into their session
1154       
1155        @type encrSessMgrURI: string
1156        @param encrSessMgrURI: URI for remote session manager to forward a
1157        request to.  This effectively use THIS session manager as a proxy to
1158        another.  This URI is encrypted with a shared key.  The key is stored
1159        in the property file 'sessMgrEncrKey' element.  *** This functionality
1160        is redundant for NDG BETA delivery ***
1161       
1162        @type credWalletKw: dict
1163        @param **credWalletKw: keywords to CredWallet.getAttCert
1164        """
1165       
1166        log.debug("Calling SessionMgr.getAttCert ...")
1167       
1168        # Web browser client input will include the encrypted address of the
1169        # Session Manager where the user's session is held.
1170        if encrSessMgrURI:
1171           
1172            # Decrypt the URI for where the user's session resides
1173            userSessMgrURI = UserSession.decodeSessionMgrURI(encrSessMgrURI,
1174                                               self.__prop['sessMgrEncrKey'])
1175                                               
1176            # Check the address against the address of THIS Session Manager 
1177            if userSessMgrURI != self.__prop['sessMgrURI']:
1178               
1179                # Session is held on a remote Session  Manager
1180                userSessMgrResp = self.__redirectAttCertReq(userSessMgrURI,
1181                                                            sessID=sessID,
1182                                                            userCert=userCert,
1183                                                            **credWalletKw)
1184
1185                # Reset response by making a new AuthorisationResp object
1186                # The response from the remote Session Manager will still
1187                # contain the encrypted XML sent by it.  This should be
1188                # discarded
1189                return userSessMgrResp
1190
1191           
1192        # User's session resides with THIS Session Manager / no encrypted
1193        # URI address passed in (as in command line use case for security) ...
1194
1195           
1196        # Retrieve session corresponding to user's session ID using relevant
1197        # input credential
1198        userSess = self.__connect2UserSession(sessID=sessID,userCert=userCert)
1199
1200
1201        # User's Credential Wallet carries out attribute request to the
1202        # Attribute Authority
1203        try:
1204            attCert = userSess.credWallet.getAttCert(**credWalletKw)
1205            return attCert, None, []
1206           
1207        except CredWalletAttributeRequestDenied, e:
1208            # Exception object contains a list of attribute certificates
1209            # which could be used to re-try to get authorisation via a mapped
1210            # certificate
1211            return None, str(e), e.extAttCertList
1212
1213
1214    #_________________________________________________________________________
1215    def __redirectAttCertReq(self, userSessMgrURI, **kw):
1216        """Handle case where User session resides on another Session Manager -
1217        forward the request
1218       
1219        @type userSessMgrURI: string
1220        @param userSessMgrURI: address of remote session manager where user
1221        session is held
1222       
1223        @type **kw: dict
1224        @param **kw: same keywords which apply to getAttCert call"""
1225
1226       
1227        log.info('SessionMgr.__redirectAttCertReq - redirecting to "%s"' % \
1228                 userSessMgrURI)
1229               
1230       
1231        # Instantiate WS proxy for remote session manager
1232        try:
1233            sessMgrClnt = SessionMgrClient(uri=userSessMgrURI,
1234                                 signingCertFilePath=self.__prop['certFile'],
1235                                 signingPriKeyFilePath=self.__prop['keyFile'],
1236                                 signingPriKeyPwd=self.__prop['keyPwd'])           
1237        except Exception, e:
1238            raise SessionMgrError(
1239                "Re-directing attribute certificate request to \"%s\": %s" % \
1240                (userSessMgrURI, str(e)))
1241
1242           
1243        # Call remote session manager's authorisation request method
1244        # and return result to caller
1245        try:
1246            # Call remote SessionMgr where users session lies
1247            resp = sessMgrClnt.getAttCert(**kw)       
1248            return resp
1249       
1250        except Exception, e:
1251            raise SessionMgrError(
1252        "Forwarding Authorisation request for Session Manager \"%s\": %s" %\
1253                (userSessMgrURI, e))
1254
1255
1256    #_________________________________________________________________________
1257    def auditCredRepos(self):
1258        """Remove expired Attribute Certificates from the Credential
1259        Repository"""
1260        log.debug("Calling SessionMgr.auditCredRepos ...")
1261        self.__credRepos.auditCredentials()
Note: See TracBrowser for help on using the repository browser.