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

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

ndg.security.server/ndg/security/server/AttAuthority/init.py,
ndg.security.server/ndg/security/server/ca/init.py,
ndg.security.server/ndg/security/server/SessionMgr/init.py,
ndg.security.common/ndg/security/common/Log/log_services_server.py:

  • replaced all refs to NDG_DIR environment variable with NDGSEC_DIR so that

it's specific to NDG security.

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