source: TI12-security/trunk/python/NDG/Session.py @ 667

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

security.py: updates for authorise to allow mapping + updated fonts for display.

ndgSessionClient.py: command line script to enable all SessionClient? functionality to
be called from the command line by passing various flags.

SessionMgrServer?.py: get rid of CVS ID macro

Session.py: updates to error messages.

  • 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) 2005 CCLRC & NERC
9
10This software may be distributed under the terms of the Q Public License,
11version 1.0 or later.
12"""
13
14cvsID = '$Id$'
15
16# SQLObject Database interface
17from sqlobject import *
18
19# MYSQL exceptions have no error message associated with them so include here
20# to allow an explicit trap around database calls
21import _mysql_exceptions
22
23# Placing of session ID on client
24from Cookie import SimpleCookie
25
26# Time module for use with cookie expiry
27from time import strftime
28from datetime import datetime
29
30# For parsing of properties files
31import cElementTree as ElementTree
32
33# Base 64 encode session IDs if returned in strings - urandom's output may
34# not be suitable for printing!
35import base64
36
37# Session Manager WSDL URI in cookie
38from Crypto.Cipher import AES
39
40# Check Session Mgr WSDL URI is encrypted
41from urllib import urlopen
42
43# Credential Wallet
44from NDG.CredWallet import *
45
46# MyProxy server interface
47from NDG.MyProxy import *
48
49# Tools for interfacing with SessionMgr WS
50from NDG.SessionMgrIO import *
51
52# Use to pipe output from ZSI ServiceProxy
53from cStringIO import StringIO
54
55#_____________________________________________________________________________
56class UserSessionError(Exception):   
57    """Exception handling for NDG User Session class."""
58   
59    def __init__(self, msg):
60        self.__msg = msg
61         
62    def __str__(self):
63        return self.__msg
64
65
66
67
68#_____________________________________________________________________________
69# Inheriting from 'object' allows Python 'new-style' class with Get/Set
70# access methods
71class UserSession(object):
72    """Session details - created when a user logs into NDG"""
73
74    # Session ID
75    __sessIDlen = 128
76
77    __cookieTagNames = ("NDG-ID1", "NDG-ID2")
78
79    # Follow standard format for cookie path and expiry attributes
80    __cookiePathTagName = "path"
81    __cookiePath = "/"
82    __cookieDomainTagName = 'domain'
83    __cookieExpiryTagName = "expires"
84       
85    __sessCookieExpiryFmt = "%a, %d-%b-%Y %H:%M:%S GMT"
86
87
88    def __init__(self, *credWalletArgs, **credWalletKeys):
89        """Initialise UserSession with args and keywords to CredWallet"""
90       
91        # Each User Session has one or more browser sessions associated with
92        # it.  These are stored in a list
93        self.__sessIDlist = []
94        self.__createSessID()
95        self.__credWallet = CredWallet(*credWalletArgs, **credWalletKeys)
96
97               
98    def __repr__(self):
99        "Represent User Session"       
100        return "<UserSession instance>"
101
102
103    #_________________________________________________________________________
104    # CredWallet access
105    def __getCredWallet(self):
106        """Get Credential Wallet instance"""
107        return self.__credWallet
108   
109    credWallet = property(fget=__getCredWallet,
110                          doc="Read-only access to CredWallet instance")
111
112
113    #_________________________________________________________________________
114    # CredWallet access
115    def __getSessIDlist(self):
116        """Get Session ID list - last item is latest allocated for this
117        session"""
118        return self.__sessIDlist
119   
120    sessIDlist = property(fget=__getSessIDlist,
121                          doc="Read-only access to Session ID list")
122
123       
124    def __latestSessID(self):
125        """Get the session ID most recently allocated"""
126        return self.__sessIDlist[-1]
127   
128    # Publish as an attribute
129    latestSessID = property(fget=__latestSessID,
130                            doc="Latest Session ID allocated")
131
132
133    def __createSessID(self):
134        """Add a new session ID to be associated with this UserSession
135        instance"""
136
137        # base 64 encode output from urandom - raw output from urandom is
138        # causes problems when passed over SOAP.  A consequence of this is
139        # that the string length of the session ID will almost certainly be
140        # longer than SessionMgr.__sessIDlen
141        sessID = base64.b64encode(os.urandom(self.__sessIDlen))
142        self.__sessIDlist.append(sessID)
143
144
145    def __getExpiryStr(self):
146        """Return session expiry date/time as would formatted for a cookie"""
147
148        try:
149            # Proxy certificate's not after time determines the expiry
150            dtNotAfter = self.credWallet.proxyCert.notAfter
151
152            return dtNotAfter.strftime(self.__sessCookieExpiryFmt)
153        except Exception, e:
154            UserSessionError("getExpiry: %s" % e)
155           
156   
157    def createCookie(self, encrSessMgrWSDLuri, sessID=None, asString=True):
158        """Create cookies for session ID Session Manager WSDL address
159
160        encrSessMgrWSDLuri:    encrypted WSDL address for Session Mananger
161                               WSDL
162        sessID:                if no session ID is provided, use the latest
163                               one to be allocated.
164        asString:              Set to True to return the cookie as string
165                               text.  If False, it is returned as a
166                               SimpleCookie instance."""
167
168        if not encrSessMgrWSDLuri:
169            raise UserSessionError("No encrypted WSDL address set")
170                           
171        elif not isinstance(encrSessMgrWSDLuri, basestring):
172            raise UserSessionError(\
173                            "Encrypted WSDL address must be a valid string")
174           
175           
176        # Try to ensure WSDL address has been passed encrypted
177        if encrSessMgrWSDLuri.find("http://") != -1 or \
178           encrSessMgrWSDLuri.find("https://") != -1:
179               raise UserSessionError(\
180           "Session Manager WSDL address appears not to be encrypted: %s" % \
181           encrSessMgrWSDLuri)
182        else:
183            # Sledge hammer approach
184            try:
185                urlopen(encrSessMgrWSDLuri)
186                raise UserSessionError(\
187           "Session Manager WSDL address appears not to be encrypted: %s" % \
188           encrSessMgrWSDLuri)
189               
190            except:
191                pass   
192
193           
194        try:
195            if sessID is None:
196                # Use latest session ID allocated if none was input
197                sessID = self.__sessIDlist[-1]
198               
199            elif not isinstance(sessID, basestring):
200                raise UserSessionError(\
201                                    "Input session ID is not a valid string")
202                                   
203                if sessID not in self.__sessIDlist:
204                    raise UserSessionError(\
205                                        "Input session ID not found in list")
206 
207           
208            sessCookie = SimpleCookie()
209           
210            tagValues = (sessID, encrSessMgrWSDLuri)
211           
212            i=0
213            for tagName in self.__cookieTagNames:
214   
215                sessCookie[tagName] = tagValues[i]
216                i += 1
217               
218                # Use standard format for cookie path and expiry
219                sessCookie[tagName][self.__cookiePathTagName] = \
220                                                        self.__cookiePath
221               
222                sessCookie[tagName][self.__cookieExpiryTagName]=\
223                                                        self.__getExpiryStr()
224                                           
225                # Make cookie as generic as possible for domains - Nb. '.uk'
226                # alone won't work
227                sessCookie[tagName][self.__cookieDomainTagName] = '.rl.ac.uk'#'glue.badc.rl.ac.uk'
228           
229           
230            # Caller should set the cookie e.g. in a CGI script
231            # print "Content-type: text/html"
232            # print cookie.output() + os.linesep
233            if asString:
234                return sessCookie.output()
235            else:
236                return sessCookie
237           
238        except Exception, e:
239            UserSessionError("Creating Cookie: %s" % e)
240
241
242#_____________________________________________________________________________
243class SessionMgrError(Exception):   
244    """Exception handling for NDG Session Manager class."""
245   
246    def __init__(self, msg):
247        self.__msg = msg
248         
249    def __str__(self):
250        return self.__msg
251
252
253#_____________________________________________________________________________
254class SessionMgr(object):
255    """NDG authentication and session handling"""
256
257    # valid configuration property keywords
258    __validKeys = [    'caCertFile',
259                       'certFile',
260                       'keyFile',
261                       'keyPPhrase', 
262                       'sessMgrWSDLkey', 
263                       'sessMgrWSDLuri', 
264                       'myProxyProp', 
265                       'credReposProp']
266
267   
268    #_________________________________________________________________________
269    def __init__(self, 
270                 propFilePath=None, 
271                 credReposPPhrase=None, 
272                 **prop):       
273        """Create a new session manager to manager NDG User Sessions
274       
275        propFilePath:        path to properties file
276        credReposPPhrase:    for credential repository if not set in
277                             properties file
278        **prop:              set any other properties corresponding to the
279                             tags in the properties file"""       
280
281        # MyProxy interface
282        try:
283            self.__myPx = MyProxy()
284           
285        except Exception, e:
286            raise SessionMgrError("Creating MyProxy interface: %s" % e)
287
288       
289        # Credentials repository - permanent stroe of user credentials
290        try:
291            self.__credRepos = SessionMgrCredRepos()
292           
293        except Exception, e:
294            raise SessionMgrError(\
295                    "Creating credential repository interface: %s" % e)
296
297        self.__sessList = []
298
299        # Dictionary to hold properties
300        self.__prop = {}
301       
302       
303        # Set properties from file
304        if propFilePath is not None:
305            self.readProperties(propFilePath,
306                                credReposPPhrase=credReposPPhrase)
307
308
309        # Set any properties that were provided by keyword input
310        #
311        # Nb. If any are duplicated with tags in the properties file they
312        # will overwrite the latter
313        self.setProperties(**prop)
314           
315
316    #_________________________________________________________________________
317    def readProperties(self,
318                       propFilePath=None,
319                       propElem=None,
320                       credReposPPhrase=None):
321        """Read Session Manager properties from an XML file or cElementTree
322        node"""
323
324        if propFilePath is not None:
325
326            try:
327                tree = ElementTree.parse(propFilePath)
328                propElem = tree.getroot()
329
330            except IOError, e:
331                raise SessionMgrError(\
332                                "Error parsing properties file \"%s\": %s" % \
333                                (e.filename, e.strerror))
334               
335            except Exception, e:
336                raise SessionMgrError("Error parsing properties file: %s" % e)
337
338        if propElem is None:
339            raise SessionMgrError("Root element for parsing is not defined")
340
341        for elem in propElem:
342            if elem.tag == 'myProxyProp':
343                self.__myPx.readProperties(propElem=elem)
344
345            elif elem.tag == 'credReposProp':
346                self.__credRepos.readProperties(propElem=elem,
347                                                dbPPhrase=credReposPPhrase)
348            elif elem.tag in self.__validKeys:
349                # Check for environment variables in file paths
350                tagCaps = elem.tag.upper()
351                if 'FILE' in tagCaps or 'PATH' in tagCaps or 'DIR' in tagCaps:
352                    elem.text = os.path.expandvars(elem.text)
353                   
354                self.__prop[elem.tag] = elem.text               
355            else:
356                raise SessionMgrError(\
357                    "\"%s\" is not a valid properties file tag" % elem.tag)
358
359
360    #_________________________________________________________________________
361    def setProperties(self, **prop):
362        """Update existing properties from an input dictionary
363        Check input keys are valid names"""
364       
365        for key in prop.keys():
366            if key not in self.__validKeys:
367                raise SessionMgrError("Property name \"%s\" is invalid" % key)
368
369
370        for key, value in prop.items():
371                       
372            if key == 'myProxyProp':
373                self.__myPx.setProperties(prop[key])
374   
375            elif key == 'credReposProp':
376                self.__credRepos.setProperties(prop[key])
377
378            elif key in self.__validKeys:
379                # Only update other keys if they are not None or ""
380                if value:
381                    self.__prop[key] = value
382               
383            else:
384                raise SessionMgrError(\
385                    "Key \"%s\" is not a valid Session Manager property" %
386                    key)
387
388
389    #_________________________________________________________________________
390    def addUser(self, 
391                caConfigFilePath=None,
392                caPassPhrase=None,
393                reqXMLtxt=None, 
394                **addUserReqKeys):       
395        """Register a new user with NDG data centre
396       
397        addUser([caConfigFilePath, ]|[, caPassPhrase][, reqXMLtxt]
398                |[, userName=u, pPhrase=p])
399
400        returns XML formatted response message
401       
402        caConfigFilePath|caPassPhrase:  pass phrase for SimpleCA's
403                                        certificate.  Set via file or direct
404                                        string input respectively.  Set here
405                                        to override setting [if any] made at
406                                        object creation.
407       
408                                        Passphrase is only required if
409                                        SimpleCA is instantiated on the local
410                                        machine.  If SimpleCA WS is called no
411                                        passphrase is required.
412                                       
413        reqXMLtxt:                      XML containing credentials - may be
414                                        encrypted or plain text
415                                        for new user: username, pass-phrase
416        addUserReqKeys:                 use as alternative to
417                                        reqXMLtxt keyword - pass in
418                                        username and pass-phrase for new user
419                                        unencrypted as keywords username
420                                        and pPhrase respectively."""
421       
422
423        if reqXMLtxt is not None:
424            if not isinstance(reqXMLtxt, basestring):
425                raise SessionMgrError(\
426                    "New user credentials must be a string")
427                                       
428            try:
429                # Assume text was input encrypted
430                #
431                # UserReq object returned behaves like a dictionary
432                addUserReqKeys = AddUserReq(encrXMLtxt=reqXMLtxt,
433                                    encrPriKeyFilePath=self.__prop['keyFile'],
434                                    encrPriKeyPwd=self.__prop['keyPPhrase'])
435            except:
436                try:
437                    # Check for plain text input
438                    addUserReqKeys = AddUserReq(xmlTxt=reqXMLtxt)
439                                   
440                except Exception, e:
441                    raise SessionMgrError(\
442                        "Error parsing user credentials: %s" % e)
443       
444        try:
445            # Add new user certificate to MyProxy Repository
446            user = self.__myPx.addUser(addUserReqKeys['userName'],
447                                       addUserReqKeys['pPhrase'],
448                                       caConfigFilePath=caConfigFilePath,
449                                       caPassPhrase=caPassPhrase,
450                                       retDN=True)
451           
452            # Add to user database
453            self.__credRepos.addUser(addUserReqKeys['userName'], user['dn'])
454           
455        except Exception, e:
456            raise SessionMgrError("%s" % e)
457
458
459        return str(AddUserResp(errMsg=''))
460   
461   
462    #_________________________________________________________________________       
463    def connect(self, reqXMLtxt=None, **connectReqKeys):       
464        """Create and return a new user session or connect to an existing
465        one:
466
467        connect([, reqXMLtxt=txt]|
468                [getCookie=True/False][createServerSess=Tue/False, ]
469                [, userName=u, pPhrase=p]|[, proxyCert=px]|[, sessID=id])
470
471        getCookie:              If True, allocate a user session with a
472                                wallet in the session manager and return a
473                                cookie containing the new session ID
474                                allocated.  If set False, return a proxy
475                                certificate only.  The client is then
476                                responsible for Credential Wallet management.
477        createServerSess:       If set to True, the SessionMgr will create
478                                and manage a session for the user.  Nb.
479                                this flag is ignored and set to True if
480                                getCookie is set.  For command line case,
481                                where getCookie is False, it's possible
482                                to choose to have a client or server side
483                                session using this keyword.
484        reqXMLtxt:              encrypted XML containing user credentials -
485                                user name, pass-phrase or proxy cert etc
486        connectReqKeys:         username and pass-phrase or the proxy
487                                certificate or browser session ID
488                                corresponding to an existing session"""
489       
490
491        if reqXMLtxt is not None:
492            if not isinstance(reqXMLtxt, basestring):
493                raise SessionMgrError(\
494                    "Encrypted user credentials must be a string")
495                                       
496            # Decrypt and parse
497            try:
498                # Connect request object returned behaves like a dictionary
499                connectReqKeys = ConnectReq(encrXMLtxt=reqXMLtxt,
500                                    encrPriKeyFilePath=self.__prop['keyFile'],
501                                    encrPriKeyPwd=self.__prop['keyPPhrase'])
502            except:
503                try:
504                    connectReqKeys = ConnectReq(xmlTxt=reqXMLtxt)
505                   
506                except Exception, e:
507                    raise SessionMgrError(\
508                        "Error parsing user credentials: %s" % e)
509
510       
511            if 'encrCert' in connectReqKeys:
512                # ConnectResp class expects the public key to be in a file
513                # - Copy public key string content into a temporary file
514                try:
515                    fdPubKeyTmp = NamedTemporaryFile()
516                    open(fdPubKeyTmp.name).write(connectReqKeys['encrCert'])
517                   
518                    clntPubKeyFilePath = fdPubKeyTmp.name
519                   
520                except Exception, e:
521                    raise SessionMgrError(\
522                    "Error creating temporary file for client public key: %s"\
523                        % e)
524            else:
525                clntPubKeyFilePath = None
526
527                                         
528        if 'sessID' in connectReqKeys:
529           
530            # Connect to an existing session identified by a session ID
531            userSess = self.__connect2UserSession(sessID=sessID)
532            return userSess.credWallet.proxyCertTxt
533       
534        elif 'proxyCert' in connectReqKeys:
535            # Connect to an existing session identified by a session ID or
536            # proxy certificate
537           
538            # What should this return??
539            # PJK 20/12/05
540            return self.__connect2UserSession(sessID=sessID, 
541                                              proxyCert=proxyCert)
542        else:
543            # Create a fresh session
544            proxyCert = self.__delegateProxy(connectReqKeys['userName'], 
545                                             connectReqKeys['pPhrase'])
546
547            bGetCookie = 'getCookie' in connectReqKeys and \
548                                                connectReqKeys['getCookie']
549                                               
550            bCreateServerSess = 'createServerSess' in connectReqKeys and \
551                                            connectReqKeys['createServerSess']
552                                           
553            if bGetCookie or bCreateServerSess:
554                # Session Manager creates and manages user's session
555                userSess = self.__createUserSession(proxyCert)
556 
557                               
558            if bGetCookie:
559               
560                # Web browser client - Return session cookie
561                sessCookie = userSess.createCookie(self.encrSessMgrWSDLuri)
562               
563                try:
564                    # Encrypt response if a client public key is available
565                    connectResp = ConnectResp(sessCookie=sessCookie,
566                                      encrPubKeyFilePath=clntPubKeyFilePath)
567                except Exception, e:
568                    raise SessionMgrError(\
569                        "Error formatting connect response: %s" % e)               
570            else:
571                # NDG Command line client - Return proxy certificate
572                try:
573                    connectResp = ConnectResp(proxyCert=proxyCert,
574                                      encrPubKeyFilePath=clntPubKeyFilePath)
575                except Exception, e:
576                    raise SessionMgrError(\
577                        "Error formatting connect response: %s" % e)
578               
579            # Return connection response as XML formatted string
580            return str(connectResp)
581           
582               
583    #_________________________________________________________________________       
584    def __delegateProxy(self, userName, passPhrase):
585        """Delegate a proxy certificate ID from input user credentials"""
586       
587        if not userName:
588            raise SessionMgrError(\
589                            "Getting proxy delegation: username is null")
590       
591        if not passPhrase:
592            raise SessionMgrError(\
593                            "Getting proxy delegation: pass-phrase is null")
594       
595        try:           
596            # Get a proxy certificate to represent users ID for the new
597            # session
598            return self.__myPx.getDelegation(userName, passPhrase)
599
600        except Exception, e:
601            raise SessionMgrError("Delegating from MyProxy: %s" % e)
602       
603       
604    #_________________________________________________________________________       
605    def __createUserSession(self, proxyCert):
606        """Create a new user session from input user credentials       
607        and return
608       
609        """
610       
611        try:   
612            # Search for an existing session for the same user
613            userSess = None
614            # PJK 16/12/05 - DON'T search for existing sessions make a new one
615            # even if the user has one already. 
616            # !! This allows users to have multiple sessions !!
617#            for u in self.__sessList:
618#                if u.credWallet['proxyCert'].dn['CN'] == userName:
619#
620#                    # Existing session found
621#                    userSess = u
622#
623#                    # Replace it's Proxy Certificate with a more up to date
624#                    # one
625#                    userSess.credWallet.proxyCert = proxyCert
626#                    break
627               
628
629            if userSess is None:
630                # Create a new user session using the new proxy certificate
631                # and session ID
632                userSess=UserSession(proxyCert, 
633                                     caCertFilePath=self.__prop['caCertFile'],
634                                     credRepos=self.__credRepos)               
635                newSessID = userSess.latestSessID
636               
637                # Check for unique session ID
638                for existingUserSess in self.__sessList:
639                    if newSessID in existingUserSess.sessIDlist:
640                        raise SessionMgrError(\
641                            "Session ID is not unique:\n\n %s" % newSessID)
642
643                # Add new session to list                 
644                self.__sessList.append(userSess)
645
646            # Return new session
647            return userSess
648       
649        except Exception, e:
650            raise SessionMgrError("Creating User Session: %s" % e)
651
652
653    #_________________________________________________________________________       
654    def __connect2UserSession(self, **idKeys):
655        """Connect to an existing session by providing a valid session ID
656
657        __connect2UserSession([proxyCert]|[sessID])
658       
659        proxyCert:    proxy certificate corresponding to an existing
660                      session to connect to.
661        sessID:       similiarly, a web browser session ID linking to an
662                      an existing session."""
663       
664           
665        # Look for a session corresponding to this ID
666        if 'sessID' in idKeys:
667            try:
668                for userSess in self.__sessList:
669                    if idKeys['sessID'] in userSess.sessIDlist:
670   
671                        # Check matched session has not expired
672                        userSess.credWallet.isValid(raiseExcep=True)
673                        return userSess
674                       
675            except Exception, e:
676                raise SessionMgrError(\
677                "Matching session ID to existing user session: %s" % e)
678               
679            # User session not found with given ID
680            raise SessionMgrError(\
681                "No user session found matching input session ID")
682       
683        elif 'proxyCert' in idKeys:
684            try:
685                for userSess in self.__sessList:
686                    if userSess.credWallet.proxyCertTxt==idKeys['proxyCert']:
687                       
688                        # Check matched session has not expired
689                        userSess.credWallet.isValid(raiseExcep=True)
690                        return userSess
691                                       
692            except Exception, e:
693                raise SessionMgrError(\
694                "Matching proxy certificate to existing user session: %s" % e)
695               
696            # User session not found with given proxy cert
697            raise SessionMgrError(\
698                    "No user session found matching input proxy certificate")
699        else:
700            raise SessionMgrError(\
701                                '"sessID" or "proxyCert" keyword must be set')
702
703
704    def __encryptSessMgrWSDLuri(self):
705        """Encrypt the WSDL address of this Session Manager's WS to allow
706        inclusion in a web browser session cookie
707       
708        The address is encrypted and then base 64 encoded"""
709       
710        # Text length must be a multiple of 16 for AES encryption
711        try:
712            mod = len(self.__prop['sessMgrWSDLuri']) % 16
713            if mod:
714                nPad = 16 - mod
715            else:
716                nPad = 0
717               
718            # Add padding
719            paddedURI = self.__prop['sessMgrWSDLuri'] + \
720                        ''.join([' ' for i in range(nPad)])
721        except Exception, e:
722            raise SessionMgrError("Error padding WSDL URI: %s" % e)
723       
724        # encrypt
725        try:
726            aes = AES.new(self.__prop['sessMgrWSDLkey'], AES.MODE_ECB)
727            return base64.b64encode(aes.encrypt(paddedURI))
728       
729        except Exception, e:
730            raise SessionMgrError("Error encrypting WSDL URI: %s" % e)
731
732
733    # Make available as read-only attribute
734    encrSessMgrWSDLuri = property(fget=__encryptSessMgrWSDLuri,
735                                  doc="Encrypted SessionMgr WSDL Address")
736                                       
737                                       
738    def decryptSessMgrWSDLuri(self, encrSessMgrWSDLuri):
739        """Decrypt the WSDL address of another Session Manager.  This
740        is required when reading a session cookie to find out which
741        Session Manager holds the client's session
742       
743        encrSessMgrWSDLuri:    base 64 encoded encrypted WSDL address"""
744
745        try:
746            aes = AES.new(self.__prop['sessMgrWSDLkey'], AES.MODE_ECB)
747           
748            # Decode from base 64
749            b64DecodedEncrSessMgrWSDLuri=base64.b64decode(encrSessMgrWSDLuri)
750           
751            # Decrypt and strip trailing spaces
752            return aes.decrypt(b64DecodedEncrSessMgrWSDLuri).strip()
753       
754        except Exception, e:
755            raise SessionMgrError("Error encrypting WSDL URI: %s" % e)
756
757
758    #_________________________________________________________________________
759    def reqAuthorisation(self, reqXMLtxt=None, **reqKeys):
760        """For given sessID, request authorisation from an Attribute Authority
761        given by aaWSDL.  If sucessful, an attribute certificate is
762        returned.
763
764        **reqKeys:              keywords used by
765        """
766
767        if reqXMLtxt is not None:
768            # Parse XML text into keywords corresponding to the input
769            # parameters
770            if not isinstance(reqXMLtxt, basestring):
771                raise SessionMgrError(\
772                            "XML Authorisation request must be a string")
773                                       
774            # Parse and decrypt as necessary
775            try:
776                # 1st assume that the request was encrypted
777                reqKeys = AuthorisationReq(encrXMLtxt=reqXMLtxt,
778                                    encrPriKeyFilePath=self.__prop['keyFile'],
779                                    encrPriKeyPwd=self.__prop['keyPPhrase'])
780            except Exception, e:
781               
782                # Error occured decrypting - Trying parsing again, but this
783                # time assuming non-encrypted
784                try:
785                    reqKeys = AuthorisationReq(xmlTxt=reqXMLtxt)
786                   
787                except Exception, e:
788                    raise SessionMgrError(\
789                        "Error parsing authorisation request: %s" % e)
790
791       
792        if 'encrSessMgrWSDLuri' in reqKeys:
793            # Decrypt the URI for where the user's session resides
794            userSessMgrWSDLuri = self.decryptSessMgrWSDLuri(\
795                                                reqKeys['encrSessMgrWSDLuri'])
796   
797                                           
798            # Check the address against the address of THIS Session Manager 
799            if userSessMgrWSDLuri != self.__prop['sessMgrWSDLuri']:
800                # User session resides on another Session Manager - forward the
801                # request
802               
803                # Instantiate WS proxy for remote session manager
804                try:
805                    smSrv = ServiceProxy(userSessMgrWSDLuri, use_wsdl=True)
806                except Exception, e:
807                    raise SessionMgrError(\
808                        "Error initialising WS for \"%s\": %s" % \
809                        (userSessMgrWSDLuri, e))
810               
811               
812                # Call remote session manager's authorisation request method
813                # and return result to caller
814                #
815                # TODO: Message level encryption public key of target SessionMgr
816                # is needed here in order to be able to apply message level
817                # encryption.  Get from cookie??
818                #
819                # P J Kershaw 02/03/06
820                try:   
821                    # Format parsed request into a new request encrypted by the
822                    # target SessionMgr's public key
823                    redirectAuthReq = AuthorisationReq(\
824                                        #encrPubKeyFilePath=userSessMgrPubKeyURI,
825                                        **dict(reqKeys.items()))
826                                       
827                    # Call remote SessionMgr where users session lies
828                    redirectAuthResp = smSrv.reqAuthorisation(\
829                                            authorisationReq=redirectAuthReq())
830                 
831                    # Parse XML contained in response                 
832                    resp = AuthorisationResp(\
833                            xmlTxt=str(redirectAuthResp['authorisationResp']))
834                    return resp
835               
836                except Exception, e:
837                    raise SessionMgrError(\
838                    "Error requesting authorisation for Session Manager %s: %s" %\
839                    (userSessMgrWSDLuri, e))
840           
841        # User's session resides with THIS Session Manager / no encrypted
842        # WSDL address passed in (as in command line context for security) ...
843
844           
845        # Retrieve session corresponding to user's session ID using relevant
846        # input credential
847        idKeys = {}
848        if 'sessID' in reqKeys:
849            idKeys['sessID'] = reqKeys['sessID']
850           
851        elif 'proxyCert' in reqKeys:
852            idKeys['proxyCert'] = reqKeys['proxyCert']           
853        else:
854            raise SessionMgrError(\
855                                'Expecting "sessID" or "proxyCert" keywords')
856                               
857        userSess = self.__connect2UserSession(**idKeys)
858
859
860        # Copy keywords to be passed onto the request to the attribute
861        # authority
862        aaKeys = dict(reqKeys.items())
863       
864        # ID keys aren't required
865        try: del aaKeys['sessID']
866        except KeyError: pass
867       
868        try: del aaKeys['encrSessMgrWSDLuri']
869        except KeyError: pass
870       
871        try: del aaKeys['proxyCert']
872        except KeyError: pass
873       
874        # Ensure CA certificate file is set
875        if not "caCertFilePath" in aaKeys:
876            aaKeys['caCertFilePath'] = self.__prop['caCertFile']
877
878
879        # Check to see if the client passed a public key to encrypt the
880        # response message
881        if 'encrCert' in reqKeys:
882            # AuthorisationResp class expects the public key to be in a file
883            # - Copy public key string content into a temporary file
884            try:
885                fdPubKeyTmp = NamedTemporaryFile()
886                open(fdPubKeyTmp.name).write(reqKeys['encrCert'])
887               
888                clntPubKeyFilePath = fdPubKeyTmp.name
889               
890            except Exception, e:
891                raise SessionMgrError(\
892                "Error creating temporary file for client public key: %s"\
893                    % e)
894        else:
895            # No client public key passed
896            clntPubKeyFilePath = None
897
898           
899        # User's Credential Wallet carries out authorisation request to the
900        # Attribute Authority
901        try:
902            attCert = userSess.credWallet.reqAuthorisation(**aaKeys)
903           
904            # AuthorisationResp class formats a response message in XML and
905            # allow dictionary-like access to XML tags
906            resp = AuthorisationResp(attCert=attCert, 
907                                     statCode=AuthorisationResp.accessGranted)
908           
909        except CredWalletAuthorisationDenied, e:
910            # Exception object containa a list of attribute certificates
911            # which could be used to re-try to get authorisation via a mapped
912            # certificate
913            resp = AuthorisationResp(extAttCertList=e.extAttCertList,
914                                     statCode=AuthorisationResp.accessDenied,
915                                     errMsg=str(e))
916       
917        except Exception, e:
918            # Some other error occured - create an error Authorisation
919            # response
920            resp = AuthorisationResp(statCode=AuthorisationResp.accessError,
921                                     errMsg=str(e))
922   
923        return resp
924
925
926    #_________________________________________________________________________
927    def auditCredRepos(self):
928        """Remove expired Attribute Certificates from the Credential
929        Repository"""
930        self.__credRepos.auditCredentials()
931
932
933
934       
935def reqAuthorisationTest(userName, passPhrase=None, passPhraseFilePath='tmp'):
936
937    import pdb
938    pdb.set_trace()
939
940    try:
941        if passPhrase is None:
942            passPhrase = open(passPhraseFilePath).read().strip()
943           
944        # Start session manager
945        sessMgr = SessionMgr("./sessionMgrProperties.xml")
946
947        # Create a new session
948        userSess = sessMgr.connect(userName, passPhrase)
949
950        # Request authorisation from a data centre
951        return sessMgr.reqAuthorisation(\
952                            aaWSDL='./attAuthority.wsdl', 
953                            #aaPropFilePath='./attAuthorityProperties.xml',
954                            sessID=userSess['sessID'][0])
955
956    except Exception, e:
957        print str(e)
958       
959
960
961
962def addUserTest(userName,
963                userPassPhrase,
964                caConfigFilePath="tmp.txt",
965                caPassPhrase=None):
966
967    import pdb
968    pdb.set_trace()
969
970    try:
971        # Add a new user using the session manager
972        sessMgr = SessionMgr("./sessionMgrProperties.xml")
973        sessMgr.addUser(userName,
974                        userPassPhrase,
975                        caConfigFilePath=caConfigFilePath)
976       
977    except Exception, e:
978        print str(e)
979   
980
981
982
983#_____________________________________________________________________________
984class SessionMgrCredRepos(CredRepos):
985    """Interface to Credential Repository Database
986   
987    Nb. inherits from CredWallet.CredRepos to ensure correct interface
988    to the wallet"""
989
990    # valid configuration property keywords
991    __validKeys = ['dbURI']
992   
993
994    def __init__(self, propFilePath=None, dbPPhrase=None, **prop):
995        """Initialise Credentials Repository Database object.
996
997        If the connection string or properties file is set a connection
998        will be made
999
1000        dbURI:              <db type>://<username>:<passwd>@<hostname>/dbname
1001        propFilePath: file path to properties file
1002
1003        Nb. propFilePath setting overrides input dbURI
1004        """
1005           
1006        self.__con = None
1007        self.__prop = {}
1008       
1009        if propFilePath is not None:
1010           
1011            # Read database URI set in file
1012            self.readProperties(propFilePath, dbPPhrase=dbPPhrase)
1013           
1014        elif prop != {}:
1015           
1016            # Database URI may have been set as an input keyword argument
1017            self.setProperties(dbPPhrase=dbPPhrase, **prop)
1018
1019
1020
1021
1022    def __setConnection(self,
1023                        dbType=None,
1024                        dbUserName=None,
1025                        dbPPhrase=None,
1026                        dbHostname=None,
1027                        dbName=None,
1028                        dbURI=None,
1029                        chkConnection=True):
1030        """Establish a database connection from a database URI
1031
1032        pass a URI OR the parameters to construct the URI
1033           
1034        dbURI: "<db type>://<username>:<passwd>:<hostname>/dbname"
1035
1036        or
1037
1038        dbURI: "<db type>://<username>:%PPHRASE%:<hostname>/dbname"
1039        + passPhrase
1040
1041        - %PPHRASE% is substituted with the input passPhrase keyword
1042       
1043        or
1044       
1045        dbType:         database type e.g. 'mysql'
1046        dbUserName:     username
1047        dbPPhrase:      pass-phrase
1048        dbHostname:     name of host where database resides
1049        dbName:         name of the database
1050
1051
1052        chkConnection:  check that the URI is able to connect to the
1053        """
1054
1055        try:
1056            if dbURI:
1057                # Check for pass-phrase variable set in URI '%PPHRASE%'
1058                dbURIspl = dbURI.split('%')
1059                if len(dbURIspl) == 3:
1060                   
1061                    if dbPPhrase is None:
1062                        raise CredReposError("No database pass-phrase set")
1063                   
1064                    dbURI = dbURIspl[0] + dbPPhrase + dbURIspl[2]
1065               
1066            else:
1067                # Construct URI from individual inputs
1068                dbURI = dbType + '://' + dbUserName + ':' + dbPPhrase + \
1069                        ':' + dbHostname + '/' + dbName
1070        except Exception, e:
1071            # Checking form missing keywords
1072            raise CredReposError("Error creating database URI: %s" % e)
1073
1074        try:
1075            self.__con = connectionForURI(dbURI)
1076        except Exception, e:
1077            raise CredReposError("Error creating database connection: %s" % e)
1078
1079        if chkConnection:
1080            try:
1081                self.__con.makeConnection()
1082            except _mysql_exceptions.OperationalError, (errNum, errMsg):
1083                raise CredReposError(\
1084                    "Error connecting to Credential Repository: %s" % errMsg)
1085               
1086            except Exception, e:
1087                raise CredReposError(\
1088                    "Error connecting to Credential Repository: %s" % e)
1089
1090           
1091        # Copy the connection object into the table classes
1092        SessionMgrCredRepos.User._connection = self.__con
1093        SessionMgrCredRepos.UserCredential._connection = self.__con
1094         
1095
1096
1097
1098    def setProperties(self, dbPPhrase=None, **prop):
1099        """Update existing properties from an input dictionary
1100        Check input keys are valid names"""
1101       
1102        for key in prop.keys():
1103            if key not in self.__validKeys:
1104                raise CredReposError("Property name \"%s\" is invalid" % key)
1105               
1106        self.__prop.update(prop)
1107
1108
1109        # Update connection setting
1110        if 'dbURI' in prop:
1111            self.__setConnection(dbURI=prop['dbURI'],
1112                                 dbPPhrase=dbPPhrase)
1113               
1114
1115
1116       
1117    def readProperties(self,
1118                       propFilePath=None,
1119                       propElem=None,
1120                       dbPPhrase=None):
1121        """Read the configuration properties for the CredentialRepository
1122
1123        propFilePath|propElem
1124
1125        propFilePath: set to read from the specified file
1126        propElem:     set to read beginning from a cElementTree node"""
1127
1128        if propFilePath is not None:
1129
1130            try:
1131                tree = ElementTree.parse(propFilePath)
1132                propElem = tree.getroot()
1133               
1134            except IOError, e:
1135                raise CredReposError(\
1136                                "Error parsing properties file \"%s\": %s" % \
1137                                (e.filename, e.strerror))
1138
1139            except Exception, e:
1140                raise CredReposError("Error parsing properties file: %s" % \
1141                                    str(e))
1142
1143        if propElem is None:
1144            raise CredReposError("Root element for parsing is not defined")
1145
1146
1147        # Read properties into a dictionary
1148        prop = {}
1149        for elem in propElem:
1150                   
1151            # Check for environment variables in file paths
1152            tagCaps = elem.tag.upper()
1153            if 'FILE' in tagCaps or 'PATH' in tagCaps or 'DIR' in tagCaps:
1154                elem.text = os.path.expandvars(elem.text)
1155
1156            prop[elem.tag] = elem.text
1157           
1158        self.setProperties(dbPPhrase=dbPPhrase, **prop)
1159
1160           
1161
1162    def addUser(self, userName, dn):
1163        """A new user to Credentials Repository"""
1164        try:
1165            self.User(userName=userName, dn=dn)
1166
1167        except Exception, e:
1168            raise CredReposError("Error adding new user '%s': %s" % \
1169                                                        (userName, e))
1170
1171
1172
1173                           
1174    def auditCredentials(self, **attCertValidKeys):
1175        """Check the attribute certificates held in the repository and delete
1176        any that have expired
1177
1178        attCertValidKeys:  keywords which set how to check the Attribute
1179                            Certificate e.g. check validity time, XML
1180                            signature, version etc.  Default is check
1181                            validity time only"""
1182
1183        if attCertValidKeys == {}:
1184            # Default to check only the validity time
1185            attCertValidKeys = {    'chkTime':          True,
1186                                    'chkVersion':       False,
1187                                    'chkProvenance':    False,
1188                                    'chkSig':           False }
1189           
1190        try:
1191            credList = self.UserCredential.select()
1192           
1193        except Exception, e:
1194            raise CredReposError("Selecting credentials from repository: %s",\
1195                                 e)
1196
1197        # Iterate through list of credentials deleting records where the
1198        # certificate is invalid
1199        try:
1200            for cred in credList:
1201                attCert = AttCertParse(cred.attCert)
1202               
1203                if not attCert.isValid(**attCertValidKeys):
1204                    self.UserCredential.delete(cred.id)
1205                   
1206        except Exception, e:
1207            try:
1208                raise CredReposError("Deleting credentials for '%s': %s",
1209                                                       (cred.dn, e))
1210            except:
1211                raise CredReposError("Deleting credentials: %s", e)
1212
1213
1214
1215
1216    def getCredentials(self, dn):
1217        """Get the list of credentials for a given user's DN"""
1218
1219        try:
1220            return self.UserCredential.selectBy(dn=dn)
1221           
1222        except Exception, e:
1223            raise CredReposError("Selecting credentials for %s: %s" % (dn, e))
1224
1225
1226
1227       
1228    def addCredentials(self, dn, attCertList):
1229        """Add new attribute certificates for a user.  The user must have
1230        been previously registered in the repository
1231
1232        dn:             users Distinguished name
1233        attCertList:   list of attribute certificates"""
1234       
1235        try:
1236            userCred = self.User.selectBy(dn=dn)
1237           
1238            if userCred.count() == 0:
1239                raise CredReposError("User \"%s\" is not registered" % dn)
1240
1241        # Make explicit trap for MySQL interface error since it has no error
1242        # message associated with it
1243        except _mysql_exceptions.InterfaceError, e:
1244            raise CredReposError("Checking for user \"%s\": %s" % \
1245                                 (dn, "MySQL interface error"))
1246       
1247        except Exception, e:
1248            raise CredReposError("Checking for user \"%s\":" % (dn, e))
1249
1250       
1251        # Carry out check? - filter out certs in db where a new cert
1252        # supercedes it - i.e. expires later and has the same roles
1253        # assigned - May be too complicated to implement
1254        #uniqAttCertList = [attCert for attCert in attCertList \
1255        #    if min([attCert == cred.attCert for cred in userCred])]
1256       
1257               
1258        # Update database with new entries
1259        try:
1260            for attCert in attCertList:
1261                self.UserCredential(dn=dn, attCert=attCert.asString())
1262
1263        except _mysql_exceptions.InterfaceError, e:
1264            raise CredReposError("Adding new user credentials for " + \
1265                                 "user %s: %s" % (dn,"MySQL interface error"))
1266        except Exception, e:
1267            raise CredReposError("Adding new user credentials for " + \
1268                                 "user %s: %s" % (dn, e))
1269
1270
1271    def _initTables(self, prompt=True):
1272        """Use with EXTREME caution - this method will initialise the database
1273        tables removing any previous records entered"""
1274 
1275        if prompt:
1276            resp = raw_input(\
1277        "Are you sure you want to initialise the database tables? (yes/no)")
1278   
1279            if resp.upper() != "YES":
1280                print "Tables unchanged"
1281                return
1282       
1283        self.User.createTable()
1284        self.UserCredential.createTable()
1285        print "Tables created"
1286
1287           
1288    #_________________________________________________________________________
1289    # Database tables defined using SQLObject derived classes
1290    # Nb. These are class variables of the SessionMgrCredRepos class
1291    class User(SQLObject):
1292        """SQLObject derived class to define Credentials Repository db table
1293        to store user information"""
1294
1295        # to be assigned to connectionForURI(<db URI>)
1296        _connection = None
1297
1298        # Force table name
1299        _table = "User"
1300
1301        userName = StringCol(dbName='userName', length=30)
1302        dn = StringCol(dbName='dn', length=128)
1303
1304
1305    class UserCredential(SQLObject):
1306        """SQLObject derived class to define Credentials Repository db table
1307        to store user credentials information"""
1308
1309        # to be assigned to connectionForURI(<db URI>)
1310        _connection = None
1311
1312        # Force table name
1313        _table = "UserCredential"
1314
1315       
1316        # User name field binds with UserCredential table
1317        dn = StringCol(dbName='dn', length=128)
1318
1319        # Store complete attribute certificate text
1320        attCert = StringCol(dbName='attCert')
Note: See TracBrowser for help on using the repository browser.