source: TI12-security/trunk/python/ndg/security/server/Session.py @ 1639

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg/security/server/Session.py@1639
Revision 1639, 47.5 KB checked in by pjkersha, 14 years ago (diff)

python/setup.py: adapted to make Python Egg.
python/*.py: changed import statements to reflect new package structure of client and server.

  • 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
6P J Kershaw 02/06/05
7
8Copyright (C) 2006 CCLRC & NERC
9
10This software may be distributed under the terms of the Q Public License,
11version 1.0 or later.
12"""
13
14reposID = '$Id$'
15
16# Placing of session ID on client
17from Cookie import SimpleCookie
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 WSDL URI in cookie
31from Crypto.Cipher import AES
32
33# Check Session Mgr WSDL URI is encrypted
34from urllib import urlopen
35
36# Credential Wallet
37from NDG.CredWallet import *
38
39# MyProxy server interface
40from NDG.MyProxy import *
41
42# Tools for interfacing with SessionMgr WS
43from NDG.SessionMgrIO import *
44
45# SessionMgr.reqAuthorisation - getPubKey WS call.  Don't import
46# AttAuthorityIO's namespace as it would conflict with SessionMgrIO's
47from NDG import AttAuthorityIO
48
49# Use client package to allow redirection of authorisation requests and
50# to retrieve Attribute Authority public key
51from NDG.SecurityClient import *
52
53# Use in SessionMgr __redirectAuthorisationReq to retrieve and store Public
54# key
55import tempfile
56import urllib
57
58#_____________________________________________________________________________
59class UserSessionError(Exception):   
60    """Exception handling for NDG User Session class."""
61   
62
63#_____________________________________________________________________________
64# Inheriting from 'object' allows Python 'new-style' class with Get/Set
65# access methods
66class UserSession(object):
67    """Session details - created when a user logs into NDG"""
68
69    #_________________________________________________________________________
70    def __init__(self, *credWalletArgs, **credWalletKeys):
71        """Initialise UserSession with args and keywords to CredWallet"""
72               
73       
74        # Each User Session has one or more browser sessions associated with
75        # it.  These are stored in a list
76        self.__sessIDlist = []
77        self.addNewSessID()
78        self.__credWallet = CredWallet(*credWalletArgs, **credWalletKeys)
79
80
81    #_________________________________________________________________________
82    # CredWallet access
83    def __getCredWallet(self):
84        """Get Credential Wallet instance"""
85        return self.__credWallet
86   
87    credWallet = property(fget=__getCredWallet,
88                          doc="Read-only access to CredWallet instance")
89
90
91    #_________________________________________________________________________
92    # CredWallet access
93    def __getSessIDlist(self):
94        """Get Session ID list - last item is latest allocated for this
95        session"""
96        return self.__sessIDlist
97   
98    sessIDlist = property(fget=__getSessIDlist,
99                          doc="Read-only access to Session ID list")
100
101
102    #_________________________________________________________________________       
103    def __latestSessID(self):
104        """Get the session ID most recently allocated"""
105        return self.__sessIDlist[-1]
106   
107    # Publish as an attribute
108    latestSessID = property(fget=__latestSessID,
109                            doc="Latest Session ID allocated")
110
111
112    #_________________________________________________________________________
113    def addNewSessID(self):
114        """Add a new session ID to be associated with this UserSession
115        instance"""
116
117        # base 64 encode output from urandom - raw output from urandom is
118        # causes problems when passed over SOAP.  A consequence of this is
119        # that the string length of the session ID will almost certainly be
120        # longer than SessionMgr.__sessIDlen
121        sessID = base64.urlsafe_b64encode(os.urandom(SessionCookie.sessIDlen))
122        self.__sessIDlist.append(sessID)
123
124
125    #_________________________________________________________________________
126    def __getExpiryStr(self):
127        """Return session expiry date/time as would be formatted for a cookie
128        """
129
130        try:
131            # Proxy certificate's not after time determines the expiry
132            dtNotAfter = self.credWallet.proxyCert.notAfter
133
134            return dtNotAfter.strftime(self.__sessCookieExpiryFmt)
135        except Exception, e:
136            UserSessionError, "getExpiry: %s" % e
137
138
139    #_________________________________________________________________________
140    @staticmethod
141    def encrypt(txt, encrKey):
142        """Encrypt the test of this Session Manager's WS URI / URI for its
143        public key to allow inclusion in a web browser session cookie
144       
145        The address is encrypted and then base 64 encoded"""
146       
147        # Text length must be a multiple of 16 for AES encryption
148        try:
149            mod = len(txt) % 16
150            nPad = mod and 16 - mod or 0
151               
152            # Add padding
153            paddedURI = txt + ''.join(' '*nPad)
154        except Exception, e:
155            raise UserSessionError, "Error padding text for encryption: " + \
156                                    str(e)
157       
158        # encrypt
159        try:
160            aes = AES.new(encrKey, AES.MODE_ECB)
161            return base64.urlsafe_b64encode(aes.encrypt(paddedURI))
162       
163        except Exception, e:
164            raise UserSessionError, "Error encrypting text: %s" % str(e)
165                                       
166   
167    #_________________________________________________________________________
168    @staticmethod                                   
169    def decrypt(encrTxt, encrKey):
170        """Decrypt text from cookie set by another Session Manager.  This
171        is required when reading a session cookie to find out which
172        Session Manager holds the client's session
173       
174        encrTxt:    base 64 encoded encrypted text"""
175
176        try:
177            aes = AES.new(encrKey, AES.MODE_ECB)
178           
179            # Decode from base 64
180            b64DecodedEncrTxt = base64.urlsafe_b64decode(encrTxt)
181           
182            # Decrypt and strip trailing spaces
183            return aes.decrypt(b64DecodedEncrTxt).strip()
184       
185        except Exception, e:
186            raise SessionMgrError, "Decrypting: %s" % str(e)           
187
188
189    #_________________________________________________________________________
190    def createCookie(self, 
191                     sessMgrWSDLuri,
192                     encrKey, 
193                     sessID=None,
194                     cookieDomain=None,
195                     asString=True):
196        """Create cookies for session ID Session Manager WSDL address
197
198        sessMgrWSDLuri:     WSDL address for Session Mananger
199        sessMgrPubKeyURI:   URI for public key of Session Manager
200        encrKey:               encryption key used to encrypted above URIs
201        sessID:                if no session ID is provided, use the latest
202                               one to be allocated.
203        cookieDomain:          domain set for cookie, if non set, web server
204                               domain name is used.  Nb. Generalised domains
205                               which don't set a specific host can be a
206                               security risk.
207        asString:              Set to True to return the cookie as string
208                               text.  If False, it is returned as a
209                               SimpleCookie instance."""
210         
211        if sessID is None:
212            # Use latest session ID allocated if none was input
213            sessID = self.__sessIDlist[-1]
214           
215        elif not isinstance(sessID, basestring):
216            raise UserSessionError, "Input session ID is not a valid string"
217                               
218            if sessID not in self.__sessIDlist:
219                raise UserSessionError, "Input session ID not found in list"
220 
221 
222        encrSessMgrWSDLuri = self.encrypt(sessMgrWSDLuri, encrKey)
223        dtExpiry = self.credWallet.proxyCert.notAfter
224       
225        # Call class method
226        cookieTagsKw = SessionCookie.tags
227        cookieTagsKw = {}.fromkeys(cookieTags)
228        cookieTagsKw[cookieTags[0]] = sessID
229        cookieTagsKw[cookieTags[1]] = encrSessMgrWSDLuri
230       
231        return str(SessionCookie(dtExpiry=dtExpiry,
232                                 cookieDomain=cookieDomain,
233                                 **cookieTagsKw))
234
235
236#_____________________________________________________________________________
237class SessionCookieError(Exception):
238    "Handle exception from SessionCookie class"
239   
240       
241#_____________________________________________________________________________
242class _MetaSessionCookie(type):
243    """Enable SessionCookie to have read only class variables e.g.
244   
245    print sessionCookie.cookieTags is allowed but,
246   
247    sessionCookie.cookieTags = None
248   
249    ... raises - AttributeError: can't set attribute"""
250    def __getTags(cls):
251        '''ndgID1 is the session ID and ndgID2 is the encrypted session
252        manager WSDL address.'''
253        return ("ndgID1", "ndgID2")
254
255    tags = property(fget=__getTags)
256
257    def __getSessIDlen(cls):
258        '''This sets the session ID length (!)'''
259        return 64
260   
261    sessIDlen = property(fget=__getSessIDlen)
262   
263   
264#_____________________________________________________________________________
265class SessionCookie(object):
266    """Class encapsulates Session Cookie for handling by UserSession and
267    LoginService"""
268   
269    __metaclass__ = _MetaSessionCookie
270
271    # Follow standard format for cookie path and expiry attributes
272    __cookiePathTag = "path"
273    __cookiePath = "/"
274    __cookieDomainTag = 'domain'
275    __cookieExpiryTag = "expires"
276 
277    # Quotes are vital (and part of the official cookei format) - otherwise it
278    # will not be parsed correctly
279    __sessCookieExpiryFmt = "\"%a, %d-%b-%Y %H:%M:%S GMT\""
280
281       
282    #_________________________________________________________________________   
283    def __init__(self, 
284                 expiryStr=None,
285                 dtExpiry=None,
286                 cookieDomain=None,
287                 **cookieTagKw):
288        """Generic cookie creation independent of individual UserSession
289        details.             
290           
291        Caller should set the cookie e.g. in a CGI script
292        print "Content-type: text/html"
293        print str(cookie) + os.linesep
294
295       
296        **cookieTagKw:         session ID for cookie (ndgID1) and
297                               encrypted Session Manager WSDL address (ndgID2)
298                               ** style arg means they can be passed as a
299                               dictionary without the caller needing to
300                               know the tag names.
301                               
302                               Use UserSession.encrypt() to encrypt the
303                               SessionManager WSDL address.  The site
304                               SessionManager holds the encryption key.
305        expiryStr|dtExpiry:    expiry date time for the cookie.  Input as a
306                               string formatted in the standard way for
307                               cookies or input a datetime type for
308                               conversion.
309        cookieDomain:          The domain for the cookie.  Default is the
310                               web server address.  Override to set to a
311                               site wide cookie e.g. .rl.ac.uk.  Nb. that this
312                               has security implications
313        """
314
315        # Domain for cookie used by createCookie method - if not set, it will
316        # default to web server domain name
317        self.__cookieDomain = cookieDomain
318       
319        try:
320            sessID = cookieTagKw['NDG-ID1']
321            encrSessMgrWSDLuri = cookieTagKw['NDG-ID2']
322           
323        except KeyError:
324            c = self.tags
325            msg = len(c) > 1 and 's %s and %s' % (', '.join(c[:-1]), c[-1]) \
326                or ' ' + c[-1]
327            raise SessionCookieError, "The Cookie tag keyword%s" % c
328
329           
330        if len(sessID) < SessionCookie.sessIDlen:
331            SessionCookieError, "Session ID has an invalid length"
332           
333        if encrSessMgrWSDLuri[:7] == 'http://' or \
334           encrSessMgrWSDLuri[:8] == 'https://':
335            SessionCookieError, "Input Session Manager WSDL URI does not " + \
336                                "appear to have been encrypted"
337                             
338        if dtExpiry:
339            if not isinstance(dtExpiry, datetime):
340                SessionCookieError, \
341                    "Expecting valid datetime object with dtExpiry keyword"
342               
343            expiryStr = dtExpiry.strftime(self.__sessCookieExpiryFmt)
344           
345        elif not expiryStr or not isinstance(expiryStr, basestring):
346            raise SessionCookieError, "No cookie expiry was set"
347           
348           
349        try:   
350            self.__cookie = SimpleCookie()
351             
352            tagValues = (sessID, encrSessMgrWSDLuri)
353            i=0
354            for tag in self.tags:
355               
356                self.__cookie[tag] = tagValues[i]
357                i += 1
358               
359                # Use standard format for cookie path and expiry
360                self.__cookie[tag][self.__cookiePathTag] = self.__cookiePath               
361                self.__cookie[tag][self.__cookieExpiryTag]= expiryStr
362                                           
363                # Make cookie as generic as possible for domains - Nb. '.uk'
364                # alone won't work but .rl.ac.uk would
365                if cookieDomain:
366                    self.__cookie[tag][self.__cookieDomainTag] = cookieDomain
367           
368        except Exception, e:
369            raise SessionCookieError, "Creating Session Cookie: %s" % e
370       
371   
372    def asSimpleCookie(self):
373        return self.__cookie
374   
375    def asString(self):
376        return self.__cookie.output()
377   
378    def __call__(self):
379        return self.asSimpleCookie()
380   
381    def __str__(self):
382        return self.asString()
383   
384    def __repr__(self):
385        return self.asString()
386
387
388    #_________________________________________________________________________
389    def __setCookieDomain(self, cookieDomain):
390        """Set domain for cookie - set to None to assume domain of web server
391        """
392
393        if not isinstance(cookieDomain, basestring) and \
394           cookieDomain is not None:
395            raise SessionCookieError, \
396                "Expecting string or None type for \"cookieDomain\""
397                       
398        self.__cookieDomain = cookieDomain
399
400    cookieDomain = property(fset=__setCookieDomain, doc="Set cookie domain")
401       
402   
403    #_________________________________________________________________________
404    @classmethod   
405    def isValid(cls, cookie, raiseExcep=False):
406        """Check cookie has the expected session keys.  Cookie may be a
407        string or SimpleCookie type"""
408       
409        if isinstance(cookie, basestring):
410            cookie = SimpleCookie(cookie)
411           
412        elif not isinstance(cookie, SimpleCookie):
413            if raiseExcep:
414                raise SessionCookieError,"Input cookie must be a string or "+\
415                                        "SimpleCookie type"
416            else:
417                return False
418       
419        missingTags = [tag for tag in self.tags if tag not in cookie]
420        if missingTags:
421            if raiseExcep:
422                raise SessionCookieError, \
423            "Input cookie missing security tag(s): " + ", ".join(missingTags)
424            else:
425                return False
426
427        if len(cookie[self.tags[0]].value) < cls.sessIDlen:
428            if raiseExcep:
429                raise SessionCookieError, "Session ID has an invalid length"
430            else:
431                return False
432       
433        return True
434   
435
436#_____________________________________________________________________________
437class SessionMgrError(Exception):   
438    """Exception handling for NDG Session Manager class."""
439
440
441#_____________________________________________________________________________
442class SessionMgr(dict):
443    """NDG authentication and session handling"""
444
445    # valid configuration property keywords
446    __validElem = \
447    {
448        'caCertFile':     None,
449        'certFile':       None,
450        'keyFile':        None,
451        'keyPPhrase':     None, 
452        'sessMgrEncrKey': None, 
453        'sessMgrWSDLuri': None,
454        'cookieDomain':   None, 
455        'myProxyProp':    None, 
456        'credReposProp':  ('modFilePath', 'modName', 'className', 'propFile')
457    }
458
459   
460    #_________________________________________________________________________
461    def __init__(self, propFilePath=None, credReposPPhrase=None, **prop):       
462        """Create a new session manager to manager NDG User Sessions
463       
464        propFilePath:        path to properties file
465        credReposPPhrase:    for credential repository if not set in
466                             properties file
467        **prop:              set any other properties corresponding to the
468                             tags in the properties file"""       
469
470        # Base class initialisation
471        dict.__init__(self)
472
473        # Key user sessions by session ID
474        self.__sessDict = {}
475
476        # Key user sessions by user DN
477        self.__dnDict = {}
478
479        # Credential Repository interface only set if properties file is set
480        # otherwise explict calls are necessary to set credReposProp via
481        # setProperties/readProperties and then loadCredReposInterface
482        self.__credRepos = None
483       
484
485        # MyProxy interface
486        try:
487            self.__myPx = MyProxy()
488           
489        except Exception, e:
490            raise SessionMgrError, "Creating MyProxy interface: %s" % e
491
492               
493        # Dictionary to hold properties     
494        self.__prop = {}
495       
496       
497        # Set properties from file
498        if propFilePath is not None:
499            self.readProperties(propFilePath)
500
501            # Call here as we can safely expect that all Credential Repository
502            # parameters have been set above
503            self.loadCredReposInterface()
504
505       
506        # Set any properties that were provided by keyword input
507        #
508        # Nb. If any are duplicated with tags in the properties file they
509        # will overwrite the latter
510        #
511        # loadCredReposInterface must be called explicitly if propFilePath
512        # wasn't set.  This is because if properties are passed by keyword
513        # alone there is no guarantee that those needed to load the interface
514        # will be present.  readProperties however, requires that all the
515        # required parameters are present in the properties file.
516        self.setProperties(**prop)
517       
518       
519    #_________________________________________________________________________
520    def loadCredReposInterface(self, credReposPPhrase=None, Force=False):
521        """
522        Pick up and instantiate Credential Repository interface class from
523        properties file settings/keywords set by setProperties/__init__
524       
525        @param credReposPPhrase: password for CredentialRepository database
526            This is passed into the Credential Repository object but may not
527            be needed.  e.g. the custom class could pick up a password from
528            the properties file for it - ['credRepos']['propFilePath']
529        @type credReposPPhrase: string
530        @param Force: flag to force reload of Credential Repository instance
531        @type Force: boolean
532        @return: None
533        """
534       
535        # Don't bother if object has already been created.  Use Force=True
536        # to override and force reload
537        if Force is False and self.__credRepos is not None:
538            return
539       
540        # Credentials repository - permanent store of user credentials
541        try:
542            try:
543                # Temporarily extend system path ready for import
544                sysPathBak = sys.path[:]
545                sys.path.append(self.__prop['credReposProp']['modFilePath'])
546               
547                # Import module name specified in properties file
548                credReposMod = \
549                    __import__(self.__prop['credReposProp']['modName'],
550                               globals(),
551                               locals(),
552                               [self.__prop['credReposProp']['className']])
553   
554                credReposClass = eval(\
555                'credReposMod.' + self.__prop['credReposProp']['className'])
556            finally:
557                sys.path[:] = sysPathBak
558
559        except KeyError, e:
560            raise SessionMgrError, \
561        'Missing %s element for credential repository module import' % str(e)
562                       
563        except Exception, e:
564            raise SessionMgrError, \
565                        'Importing credential repository module: %s' % str(e)
566
567        # Check class inherits from CredWallet.CredRepos abstract base class
568        if not issubclass(credReposClass, CredRepos):
569            raise SessionMgrError, \
570                "Credential Repository class %s must be inherited from %s" % \
571                (credReposClass, CredRepos)
572
573
574        # Instantiate custom class
575        try:
576            self.__credRepos = credReposClass(\
577                      propFilePath=self.__prop['credReposProp']['propFile'],
578                      dbPPhrase=credReposPPhrase)
579           
580        except Exception, e:
581            raise SessionMgrError, \
582            "Error instantiating Credential Repository interface: " + str(e)
583     
584       
585    #_________________________________________________________________________       
586    def __delitem__(self, key):
587        "Session Manager keys cannot be removed"       
588        raise KeyError('Keys cannot be deleted from '+self.__class__.__name__)
589
590
591    def __getitem__(self, key):
592        self.__class__.__name__ + """ behaves as data dictionary of Session
593        Manager properties
594        """
595        if key not in self.__prop:
596            raise KeyError("Invalid key " + key)
597       
598        return self.__prop[key]
599   
600   
601    def __setitem__(self, key, item):
602        self.__class__.__name__ + """ behaves as data dictionary of Session
603        Manager properties"""
604        self.setProperties(**{key: item})
605       
606
607    def clear(self):
608        raise KeyError("Data cannot be cleared from "+self.__class__.__name__)
609   
610    def keys(self):
611        return self.__prop.keys()
612
613    def items(self):
614        return self.__prop.items()
615
616    def values(self):
617        return self.__prop.values()
618
619    def has_key(self, key):
620        return self.__prop.has_key(key)
621
622    # 'in' operator
623    def __contains__(self, key):
624        return key in self.__prop
625           
626
627    #_________________________________________________________________________
628    def readProperties(self, propFilePath=None, propElem=None):
629        """Read Session Manager properties from an XML file or cElementTree
630        node
631       
632        @param propFilePath: file path for XML properties file
633        @type propFilePath: string
634        @param propElem: pass in existing ElementTree treeroot
635        @type propElem: Element
636        @ruturn: None"""
637
638        if propFilePath is not None:
639            try:
640                tree = ElementTree.parse(propFilePath)
641                propElem = tree.getroot()
642
643            except IOError, e:
644                raise SessionMgrError, \
645                                "Error parsing properties file \"%s\": %s" % \
646                                (e.filename, e.strerror)
647               
648            except Exception, e:
649                raise SessionMgrError, \
650                    "Error parsing properties file: \"%s\": %s" % \
651                    (propFilePath, e)
652
653        if propElem is None:
654            raise SessionMgrError, \
655            "Parsing properties file \"%s\": root element is not defined" % \
656            propFilePath
657
658
659        filtElemTxt = lambda elem: os.path.expandvars(elem.text).strip()
660       
661        missingElem = []
662        getMissingElem = lambda targ, ref: [e for e in targ if e not in ref]
663        invalidElem = []
664        try:
665            for elem in propElem:
666                if elem.tag == 'myProxyProp':
667                    self.__myPx.readProperties(propElem=elem)
668   
669                elif elem.tag == 'credReposProp':
670                    self.__prop['credReposProp'] = \
671                                dict([(e.tag, filtElemTxt(e)) for e in elem])
672                           
673                    # Check for missing elements
674                    missingElem.extend(getMissingElem(\
675                                           self.__validElem['credReposProp'],
676                                           self.__prop['credReposProp']))
677                       
678                elif elem.tag in self.__validElem:
679                    # Strip white space but not in the case of pass-phrase
680                    # field as pass-phrase might contain leading or
681                    # trailing white space
682                    if elem.text and elem.tag != 'keyPPhrase':
683                       
684                        # Check for environment variables in file paths
685                        self.__prop[elem.tag] = filtElemTxt(elem)
686                    else:
687                        self.__prop[elem.tag] = elem.text                                         
688                else:
689                    invalidElem.append(elem.tag)
690               
691        except Exception, e:
692            raise SessionMgrError, \
693                "Error parsing tag \"%s\" in properties file \"%s\": %s" % \
694                (elem.tag, propFilePath, e)
695
696        missingElem.extend(getMissingElem(self.__prop, self.__validElem))
697        errMsg = ''
698       
699        if invalidElem != []:
700            errMsg = 'Invalid elements: "%s"\n' % '", "'.join(invalidElem)
701
702        if missingElem != []:
703            errMsg += 'Missing elements: "%s"\n' % '", "'.join(missingElem)
704
705        if errMsg:
706            raise SessionMgrError, errMsg +  "for properties file \"%s\"" % \
707                                                                propFilePath
708       
709
710    #_________________________________________________________________________
711    def setProperties(self, **prop):
712        """Update existing properties from an input dictionary
713        Check input keys are valid names"""
714       
715        for key in prop.keys():
716            if key not in self.__validElem:
717                raise SessionMgrError, "Property name \"%s\" is invalid" % key
718
719
720        for key, value in prop.items():
721                       
722            if key == 'myProxyProp':
723                self.__myPx.setProperties(prop[key])
724   
725            elif key == 'credReposProp':
726                self.__prop['credReposProp'] = prop[key].copy()
727
728            elif key in self.__validElem:
729                # Only update other keys if they are not None or ""
730                if value:
731                    self.__prop[key] = value               
732            else:
733                raise SessionMgrError, \
734                "Key \"%s\" is not a valid Session Manager property" % key
735
736
737    #_________________________________________________________________________
738    def addUser(self, caConfigFilePath=None, caPassPhrase=None, **reqKeys):       
739        """Register a new user with an NDG data centre
740       
741        addUser([caConfigFilePath, ]|[, caPassPhrase]
742                |[, userName=u, pPhrase=p])
743
744        returns XML formatted response message
745       
746        caConfigFilePath|caPassPhrase:  pass phrase for SimpleCA's
747                                        certificate.  Set via file or direct
748                                        string input respectively.  Set here
749                                        to override setting [if any] made at
750                                        object creation.
751       
752                                        Passphrase is only required if
753                                        SimpleCA is instantiated on the local
754                                        machine.  If SimpleCA WS is called no
755                                        passphrase is required.
756                                       
757        **reqKeys:                      use as alternative to
758                                        reqXMLtxt keyword - pass in
759                                        username and pass-phrase for new user
760                                        unencrypted as keywords username
761                                        and pPhrase respectively.  See
762                                        SessionMgrIO.AddUserRequest class for
763                                        reference."""
764             
765        try:
766            # Add new user certificate to MyProxy Repository
767            #
768            # Changed so that record is NOT added to UserID table of
769            # CredentialRepository.  Instead, a check can be made when a new
770            # Attribute Certificate credential is added: if no entry is
771            # present for the user, add them into the UserID table at this
772            # point.
773            #
774            # By removing the add record call, MyProxy and Credential
775            # Repository can be independent of one another
776            user = self.__myPx.addUser(reqKeys['userName'],
777                                       reqKeys['pPhrase'],
778                                       caConfigFilePath=caConfigFilePath,
779                                       caPassPhrase=caPassPhrase,
780                                       retDN=True)           
781        except Exception, e:
782            return AddUserResp(errMsg=str(e))
783
784        return AddUserResp(errMsg='')
785   
786   
787    #_________________________________________________________________________       
788    def connect(self, **reqKeys):       
789        """Create a new user session or connect to an existing one:
790
791        connect([getCookie=True/False][createServerSess=Tue/False, ]
792                [, userName=u, pPhrase=p]|[, proxyCert=px]|[, sessID=id])
793
794        getCookie:              If True, allocate a user session with a
795                                wallet in the session manager and return a
796                                cookie containing the new session ID
797                                allocated.  If set False, return a proxy
798                                certificate only.  The client is then
799                                responsible for Credential Wallet management.
800        createServerSess:       If set to True, the SessionMgr will create
801                                and manage a session for the user.  Nb.
802                                this flag is ignored and set to True if
803                                getCookie is set.  For command line case,
804                                where getCookie is False, it's possible
805                                to choose to have a client or server side
806                                session using this keyword.
807        reqXMLtxt:              encrypted XML containing user credentials -
808                                user name, pass-phrase or proxy cert etc
809        reqKeys:                username and pass-phrase or the proxy"""
810       
811
812        if 'sessID' in reqKeys:
813           
814            # Connect to an existing session identified by a session ID and
815            # return equivalent proxy cert
816            userSess = self.__connect2UserSession(sessID=sessID)
817            return ConnectResp(proxyCert=userSess.credWallet.proxyCertTxt)
818       
819        elif 'proxyCert' in reqKeys:
820            # Connect to an existing session identified by a proxy
821            # certificate and return an equivalent session cookie
822            userSess = self.__connect2UserSession(proxyCert=proxyCert)
823            sessCookie = userSess.createCookie(self.__prop['sessMgrWSDLuri'],
824                                               self.__prop['sessMgrEncrKey'])
825            return ConnectResp(sessCookie=sessCookie)
826       
827        else:
828            # Create a fresh session
829            try:           
830                # Get a proxy certificate to represent users ID for the new
831                # session
832                proxyCert = self.__myPx.getDelegation(reqKeys['userName'], 
833                                                      reqKeys['pPhrase'])   
834            except Exception, e:
835                raise SessionMgrError, "Delegating from MyProxy: %s" % e
836
837            bGetCookie = 'getCookie' in reqKeys and reqKeys['getCookie']
838                                               
839            bCreateServerSess = 'createServerSess' in reqKeys and \
840                                            reqKeys['createServerSess']
841                                           
842            if bGetCookie or bCreateServerSess:
843                # Session Manager creates and manages user's session
844                userSess = self.__createUserSession(proxyCert)
845 
846                               
847            if bGetCookie:
848               
849                # Web browser client - Return session cookie
850                userSess.cookieDomain = self.__prop['cookieDomain']
851
852                sessCookie = userSess.createCookie(\
853                                            self.__prop['sessMgrWSDLuri'],
854                                            self.__prop['sessMgrEncrKey'])
855               
856                try:
857                    # Encrypt response if a client public key is available
858                    return ConnectResp(sessCookie=sessCookie)
859               
860                except Exception, e:
861                    raise SessionMgrError, \
862                                "Error formatting connect response: %s" % e             
863            else:
864                # NDG Command line client - Return proxy certificate
865                return ConnectResp(proxyCert=proxyCert)       
866       
867       
868    #_________________________________________________________________________       
869    def __createUserSession(self, proxyCert):
870        """Create a new user session from input user credentials       
871        and return
872       
873        """
874       
875        try:   
876            # Check for an existing session for the same user
877            try:
878                userDN = str(X509CertParse(proxyCert).dn)
879               
880            except Exception, e:
881                raise SessionMgrError, \
882                "Parsing input proxy certificate DN for session create: %s"%\
883                                                                        str(e)
884
885            if userDN in self.__dnDict:
886                # Update existing session with proxy cert and add a new
887                # session ID to access it - a single session can be accessed
888                # via multiple session IDs e.g. a user may wish to access the
889                # same session from the their desktop PC and their laptop.
890                # Different session IDs are allocated in each case.
891                userSess = self.__dnDict[userDN]
892                userSess.addNewSessID()
893               
894            else:
895                # Create a new user session using the new proxy certificate
896                # and session ID
897                #
898                # Nb. Client pub/pri key info to allow message level
899                # encryption for responses from Attribute Authority WS
900                userSess = UserSession(proxyCert, 
901                                   caPubKeyFilePath=self.__prop['caCertFile'],
902                                   clntPubKeyFilePath=self.__prop['certFile'],
903                                   clntPriKeyFilePath=self.__prop['keyFile'],
904                                   clntPriKeyPwd=self.__prop['keyPPhrase'],
905                                   credRepos=self.__credRepos)
906
907                # Also allow access by user DN
908                self.__dnDict[userDN] = userSess
909
910                               
911            newSessID = userSess.latestSessID
912           
913            # Check for unique session ID
914            if newSessID in self.__sessDict:
915                raise SessionMgrError, \
916                    "New Session ID is already in use:\n\n %s" % newSessID
917
918            # Add new session to list                 
919            self.__sessDict[newSessID] = userSess
920                   
921            # Return new session
922            return userSess
923       
924        except Exception, e:
925            raise SessionMgrError, "Creating User Session: %s" % str(e)
926
927
928    #_________________________________________________________________________       
929    def __connect2UserSession(self, **idKeys):
930        """Connect to an existing session by providing a valid session ID or
931        proxy certificate
932
933        __connect2UserSession([proxyCert]|[sessID])
934       
935        proxyCert:    proxy certificate string corresponding to an existing
936                      session to connect to.
937        sessID:       similiarly, a web browser session ID linking to an
938                      an existing session."""
939       
940           
941        # Look for a session corresponding to this ID
942        if 'sessID' in idKeys:
943            try:
944                # Check matched session has not expired
945                userSess = self.__sessDict[idKeys['sessID']]
946               
947            except KeyError:
948                # User session not found with given ID
949                raise SessionMgrError, \
950                        "No user session found matching input session ID"
951                       
952            try:
953                userSess.credWallet.isValid(raiseExcep=True)
954                return userSess
955                       
956            except Exception, e:
957                raise SessionMgrError, \
958                        "Matching session ID to existing user session: %s" % e
959               
960       
961        elif 'proxyCert' in idKeys:
962            try:
963                userDN = str(X509CertParse(idKeys['proxyCert']).dn)
964               
965            except Exception, e:
966                raise SessionMgrError, \
967                "Parsing input proxy certificate DN for session connect: %s"%\
968                                                                        str(e)
969            try:
970                userSess = self.__dnDict[userDN]
971                       
972            except KeyError:
973                # User session not found with given proxy cert
974                raise SessionMgrError, \
975                    "No user session found matching input proxy certificate"
976                   
977            try:
978                # Check matched session has not expired
979                userSess.credWallet.isValid(raiseExcep=True)
980                return userSess
981                                       
982            except Exception, e:
983                raise SessionMgrError(\
984                "Matching proxy certificate to existing user session: %s" % e)
985        else:
986            raise SessionMgrError,\
987                                '"sessID" or "proxyCert" keywords must be set'
988
989
990    #_________________________________________________________________________       
991    def deleteUserSession(self, sessID=None, proxyCert=None):
992        """Delete an existing session by providing a valid session ID or
993        proxy certificate - use for user logout
994
995        __deleteUserSession([proxyCert]|[sessID])
996       
997        proxyCert:    proxy certificate corresponding to an existing
998                      session to connect to.
999        sessID:       similiarly, a web browser session ID linking to an
1000                      an existing session."""
1001       
1002           
1003        # Look for a session corresponding to the session ID/proxy cert.
1004        if sessID:
1005            try:
1006                userSess = self.__sessDict[sessID]
1007               
1008            except KeyError:
1009                raise SessionMgrError, \
1010                    "Deleting user session - no matching session ID exists"
1011
1012            # Get associated user Distinguished Name
1013            userDN = userSess.credWallet.proxyCert.dn
1014           
1015        elif proxyCert:
1016            try:
1017                userDN = str(X509CertParse(idKeys['proxyCert']).dn)
1018               
1019            except Exception, e:
1020                raise SessionMgrError, \
1021                "Parsing input proxy certificate DN for session connect: %s"%\
1022                                                                        str(e)
1023            try:
1024                userSess = self.__dnDict[userDN]
1025                       
1026            except KeyError:
1027                # User session not found with given proxy cert
1028                raise SessionMgrError, \
1029                    "No user session found matching input proxy certificate"
1030        else:
1031            # User session not found with given ID
1032            raise SessionMgrError, \
1033                                '"sessID" or "proxyCert" keywords must be set'
1034 
1035        # Delete associated sessions
1036        try:
1037            # Each session may have a number of session IDs allocated to
1038            # it
1039            for userSessID in userSess.sessIDlist:
1040                del self.__sessDict[userSessID]
1041
1042            del self.__dnDict[userDN]
1043           
1044        except Exception, e:
1045            raise SessionMgrError, "Deleting user session: %s" % e       
1046
1047
1048    #_________________________________________________________________________
1049    def reqAuthorisation(self, **reqKeys):
1050        """For given sessID, request authorisation from an Attribute Authority
1051        given by aaWSDL.  If sucessful, an attribute certificate is
1052        returned.
1053
1054        **reqKeys:            pass equivalent to XML as keywords instead.
1055                              See SessionMgrIO.AuthorisationReq class
1056        """
1057       
1058        # Web browser client input will include the encrypted address of the
1059        # Session Manager where the user's session is held.
1060        if 'encrSessMgrWSDLuri' in reqKeys:
1061           
1062            # Decrypt the URI for where the user's session resides
1063            userSessMgrWSDLuri = UserSession.decrypt(\
1064                                                reqKeys['encrSessMgrWSDLuri'],
1065                                                self.__prop['sessMgrEncrKey'])
1066                                               
1067            # Check the address against the address of THIS Session Manager 
1068            if userSessMgrWSDLuri != self.__prop['sessMgrWSDLuri']:
1069               
1070                # Session is held on a remote Session  Manager
1071                userSessMgrResp = self.__redirectAuthorisationReq(\
1072                                                        userSessMgrWSDLuri,
1073                                                        **reqKeys)
1074
1075                # Reset response by making a new AuthorisationResp object
1076                # The response from the remote Session Manager will still
1077                # contain the encrypted XML sent by it.  This should be
1078                # discarded
1079                return userSessMgrResp
1080
1081           
1082        # User's session resides with THIS Session Manager / no encrypted
1083        # WSDL address passed in (as in command line context for security) ...
1084
1085           
1086        # Retrieve session corresponding to user's session ID using relevant
1087        # input credential
1088        idKeys = {}
1089        if 'sessID' in reqKeys:
1090            idKeys['sessID'] = reqKeys['sessID']
1091           
1092        elif 'proxyCert' in reqKeys:
1093            idKeys['proxyCert'] = reqKeys['proxyCert']           
1094        else:
1095            raise SessionMgrError,'Expecting "sessID" or "proxyCert" keywords'
1096                               
1097        userSess = self.__connect2UserSession(**idKeys)
1098
1099
1100        # Copy keywords to be passed onto the request to the attribute
1101        # authority
1102        #
1103        # Nb. the following keys aren't required
1104        delKeys = ('proxyCert',
1105                   'sessID',
1106                   'encrCert',
1107                   'encrSessMgrWSDLuri', 
1108                   'aaPubKey')
1109                   
1110        aaKeys = dict([i for i in reqKeys.items() if i[0] not in delKeys])
1111
1112
1113        if 'aaPubKey' not in reqKeys:
1114            # Get public key using WS
1115            try:
1116                aaClnt = AttAuthorityClient(aaWSDL=reqKeys['aaWSDL'])               
1117                reqKeys['aaPubKey'] = aaClnt.getPubKey()
1118
1119            except Exception, e:
1120                raise SessionMgrError, \
1121                    "Retrieving Attribute Authority public key: "+ str(e)
1122                               
1123                                                       
1124        # Make a temporary file to hold Attribute Authority Public Key. 
1125        # The Credential Wallet needs this to encrypt requests to the
1126        # Attribute Authority
1127        try:
1128            aaPubKeyTmpFile = tempfile.NamedTemporaryFile()
1129            open(aaPubKeyTmpFile.name, "w").write(reqKeys['aaPubKey'])
1130            aaKeys['aaPubKeyFilePath'] = aaPubKeyTmpFile.name
1131           
1132        except IOError, (errNo, errMsg):
1133            raise SessionMgrError, "Making temporary file for Attribute " + \
1134                                  "Authority public key: %s" % errMsg
1135               
1136        except Exception, e:
1137            raise SessionMgrError, "Making temporary file for Attribute " + \
1138                                  "Authority public key: %s" % str(e)
1139
1140                                             
1141        # User's Credential Wallet carries out authorisation request to the
1142        # Attribute Authority
1143        try:
1144            attCert = userSess.credWallet.reqAuthorisation(**aaKeys)
1145           
1146            # AuthorisationResp class formats a response message in XML and
1147            # allow dictionary-like access to XML tags
1148            resp = AuthorisationResp(attCert=attCert, 
1149                                     statCode=AuthorisationResp.accessGranted)
1150           
1151        except CredWalletAuthorisationDenied, e:
1152            # Exception object containa a list of attribute certificates
1153            # which could be used to re-try to get authorisation via a mapped
1154            # certificate
1155            resp = AuthorisationResp(extAttCertList=e.extAttCertList,
1156                                     statCode=AuthorisationResp.accessDenied,
1157                                     errMsg=str(e))
1158       
1159        except Exception, e:
1160            # Some other error occured - create an error Authorisation
1161            # response
1162            resp = AuthorisationResp(statCode=AuthorisationResp.accessError,
1163                                     errMsg=str(e))
1164   
1165        return resp
1166
1167
1168    #_________________________________________________________________________
1169    def __redirectAuthorisationReq(self, userSessMgrWSDLuri, **reqKeys):
1170        """Handle case where User session resides on another Session Manager -
1171        forward the request"""
1172       
1173        # Instantiate WS proxy for remote session manager
1174        try:
1175            sessClnt = SessionClient(smWSDL=userSessMgrWSDLuri,
1176                                 clntPubKeyFilePath=self.__prop['certFile'],
1177                                 clntPriKeyFilePath=self.__prop['keyFile'])           
1178        except Exception, e:
1179            raise SessionMgrError(\
1180                        "Re-directing authorisation request to \"%s\": %s" % \
1181                        (userSessMgrWSDLuri, str(e)))
1182
1183           
1184        # Call remote session manager's authorisation request method
1185        # and return result to caller
1186        try:
1187            # encrCert key not needed - it gets set above via
1188            # 'clntPubKeyFilePath'
1189            if 'encrCert' in reqKeys:
1190                del reqKeys['encrCert']
1191               
1192            # Call remote SessionMgr where users session lies
1193            redirectAuthResp = sessClnt.reqAuthorisation(\
1194                                    clntPriKeyPwd=self.__prop['keyPPhrase'],
1195                                    **reqKeys)
1196         
1197            return redirectAuthResp
1198       
1199        except Exception, e:
1200            raise SessionMgrError(\
1201        "Forwarding Authorisation request for Session Manager \"%s\": %s" %\
1202                (userSessMgrWSDLuri, e))
1203
1204
1205    #_________________________________________________________________________
1206    def auditCredRepos(self):
1207        """Remove expired Attribute Certificates from the Credential
1208        Repository"""
1209        self.__credRepos.auditCredentials()
Note: See TracBrowser for help on using the repository browser.