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

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

python/ndg.security.server/ndg/security/server/AttAuthority/server-config.tac:

python/www/html/attAuthority.wsdl,
python/ndg.security.server/ndg/security/server/AttAuthority/AttAuthority_services_server.py,
python/ndg.security.common/ndg/security/common/AttAuthority/AttAuthority_services_types.py,
python/ndg.security.common/ndg/security/common/AttAuthority/AttAuthority_services.py:
Include request denied message in getAttCertResponse.

python/ndg.security.server/ndg/security/server/AttAuthority/init.py:
fix to AttAuthorityAccessDenied? doc message.

python/ndg.security.server/ndg/security/server/SessionMgr/server-config.tac:
Exlpicitly convert AttCert? in response to string type.

python/ndg.security.server/ndg/security/server/SessionMgr/init.py:

  • make explicit imports from ndg.security.common.CredWallet?
  • make X509CertParse import
  • updated exception handling for getAttCert call to CredWallet?.

python/www/html/sessionMgr.wsdl,
python/ndg.security.server/ndg/security/server/SessionMgr/SessionMgr_services_server.py,
python/ndg.security.common/ndg/security/common/SessionMgr/SessionMgr_services.py,
python/ndg.security.common/ndg/security/common/SessionMgr/SessionMgr_services_types.py:
Remove statusCode from getAttCertResponse - not needed.

python/ndg.security.test/ndg/security/test/AttAuthority/AttAuthorityClientTest.py:
minor updates to getAttCert tests.

python/ndg.security.test/ndg/security/test/MyProxy/myProxyClientTest.cfg:
fix to test1Store settings

python/ndg.security.test/ndg/security/test/MyProxy/Makefile:
makefile copies proxy obtained from MyProxy? ready for use in AttAuthority? client tests.

python/ndg.security.test/ndg/security/test/SessionMgr/SessionMgrClientTest.py:

  • add AttributeRequestDenied? import from SessionMgr?.
  • fix test4CookieDisconnect signing PKI settings
  • revised output tuple for getAttCert calls.
  • Added test6aCookieGetAttCertRefused to demonstrate attribute request denied exception
  • test3ProxyCertConnect signature verification failing at server!

python/ndg.security.test/ndg/security/test/SessionMgr/sessionMgrClientTest.cfg:
added more getAttCert test params.

python/ndg.security.common/ndg/security/common/AttAuthority/init.py:

python/ndg.security.common/ndg/security/common/wsSecurity.py:
comment out all print statements - only 'print decryptedData' affected in decrypt method
of EncryptionHandler?. This is not in use.

python/ndg.security.common/ndg/security/common/SessionMgr/init.py:

  • Added AttributeRequestDenied? exception for handling getAttCert calls.
  • msg now included in output tuple for getAttCert call.

python/ndg.security.common/ndg/security/common/AttCert.py:
Override XMLSecDoc parent class toString and str calls so that output is returned even
if the signature DOM object has not been initialised.

python/ndg.security.common/ndg/security/common/CredWallet.py:

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