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

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

Create new utility module, ClassFactory? - to allow generic instantiation
of classes dynamically.

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