source: security/trunk/python/NDG/Session.py @ 496

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

AttAuthority?.py + AttCert?.py: added NDG prefix to imports to allow for
use of NDG package in python site packages dir.

Session.py: UserSession?.createCookie() - experimented with setting domain
so that cookie is visible across .ac.uk sites.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1"""NDG Session Management and security includes SessionMgr, UserSession,
2Credentials Wallet and 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
17# Temporary store of certificates for use with CredWallet reqAuthorisation()
18import tempfile
19
20# SQLObject Database interface
21from sqlobject import *
22
23# MYSQL exceptions have no error message associated with them so include here
24# to allow an explicit trap around database calls
25import _mysql_exceptions
26
27# Access Attribute Authority's web service using ZSI - allow pass if not loaded
28# since it's possible to make AttAuthority instance locally without using
29# the WS
30aaImportError = True
31try:
32    from ZSI import ServiceProxy
33    import socket # handle socket errors from WS
34    aaImportError = False
35   
36except ImportError:
37    pass
38
39# Likewise - may want to use WS and not use AttAuthority locally in which case
40# no need to import it
41try:
42    from AttAuthority import *
43    aaImportError = False
44except:
45    pass
46
47if aaImportError:
48    raise ImportError("Either AttAuthority or ZSI modules must be " + \
49                      "present to allow interoperation with Attribute " +\
50                      "Authorities")
51
52# Authentication X.509 Certificate
53from X509 import *
54
55# Authorisation - attribute certificate
56from AttCert import *
57
58# MyProxy server interface
59from MyProxy import *
60
61# Placing of session ID on client
62from Cookie import SimpleCookie
63
64# For parsing of properties file
65import cElementTree as ElementTree
66
67# Base 64 encode session IDs if returned in strings - urandom's output may
68# not be suitable for printing!
69import base64
70
71
72#_____________________________________________________________________________
73class CredWalletError(Exception):   
74    """Exception handling for NDG CredentialWallet class."""
75   
76    def __init__(self, msg):
77        self.__msg = msg
78         
79    def __str__(self):
80        return self.__msg
81
82
83
84
85#_____________________________________________________________________________
86class CredWalletAuthorisationDenied(Exception, UserDict):   
87    """Handling exception where CredWallet is denied authorisation by an
88    Attribute Authority."""
89   
90    def __init__(self, msg=None, extAttCertList=[], trustedHostInfo={}):
91        """Raise exception for authorisation denied with option to give
92        caller hint to certificates that could used to try to obtain a
93        mapped certificate
94       
95        msg:                error message
96        extAttCertList:     list of candidate Attribute Certificates that
97                            could be used to try to get a mapped certificate
98                            from the target Attribute Authority
99        trustedHostInfo:    dictionary of indexed by host name giving details
100                            of WSDL URI and roles for trusted hosts"""
101
102        # Base class initialisation
103        UserDict.__init__(self)
104
105        self.__dat = {}
106        self.__dat['msg'] = msg
107        self.__dat['trustedHostInfo'] = trustedHostInfo
108        self.__dat['extAttCertList'] = extAttCertList
109
110       
111    def __str__(self):
112        return self.__dat['msg']
113
114   
115    def __delitem__(self, key):
116        "keys cannot be removed"       
117        raise CredWalletError('Keys cannot be deleted from ' + \
118                              CredWalletAuthorisationDenied.__name__)
119
120
121    def __setitem__(self, key, value):
122        """Dictionary items can't be set"""
123        raise CredWalletError("Dictionary items can't be set")
124
125
126    def __getitem__(self, key):
127        """Enable access using dictionary like behaviour"""
128        if key not in self.__dat:
129            raise CredWalletError('Key "%s" not recognised' % key)
130
131        return self.__dat[key]
132
133   
134    def clear(self):
135        """Override UserDict default behaviour"""
136        raise CredWalletError("Data cannot be cleared from " + \
137                              CredWalletAuthorisationDenied.__name__)
138
139   
140    def copy(self):
141        """Override UserDict default behaviour"""
142        return self.__dat
143
144   
145    def keys(self):
146        return self.__dat.keys()
147
148
149    def items(self):
150        return self.__dat.items()
151
152
153    def values(self):
154        return self.__dat.values()
155
156
157    def has_key(self):
158        return self.__dat.has_key()
159
160   
161    def getExtAttCertList(self):
162        """Return list of candidate Attribute Certificates that could be used
163        to try to get a mapped certificate from the target Attribute Authority
164        """
165        return self.__extAttCertList
166
167
168
169
170#_____________________________________________________________________________       
171class CredWallet(UserDict):
172    """Volatile store of user credentials associated with a user session"""
173
174    def __init__(self,
175                 proxyCertTxt,
176                 credRepos=None,
177                 credReposPropFilePath=None,
178                 bMapFromTrustedHosts=False,
179                 bSetExtAttCertList=True):
180        """Create store of user credentials for their current session
181
182        proxy certificate:      users proxy certificate as string text
183        credReposDbURI:         Credential Repository Database URI
184        bMapFromTrustedHosts:   sets behaviour for reqAuthorisation().  If
185                                set True and authorisation fails with the
186                                given Attribute Authority, attempt to get
187                                authorisation using Attribute Certificates
188                                issued by other trusted AAs
189        bSetExtAttCertList:     behaviour for reqAuthorisation().  If True,
190                                and authorisation fails with the given
191                                Attribute Authority, return a list of
192                                Attribute Certificates from other trusted AAs
193                                which could be used to obtain a mapped
194                                Attribute Certificate on a subsequent
195                                authorisation attempt"""
196
197
198        # Base class initialisation
199        UserDict.__init__(self)
200
201
202        # Check the proxy certificate and make an NDG.X509Cert instance
203        self.setProxyCert(proxyCertTxt)
204
205
206        # Set behaviour for authorisation requests
207        self.__bMapFromTrustedHosts = bMapFromTrustedHosts
208        self.__bSetExtAttCertList = bSetExtAttCertList
209       
210       
211        # Get the distinguished name from the proxy certificate
212        self.__dn = self.__proxyCert.getDN().serialise()
213
214
215        # Make a connection to the Credentials Repository
216        #
217        # Fudge file setting for now
218        # P J Kershaw 10/06/05
219        if credRepos is not None:
220            if not isinstance(credRepos, CredRepos):
221                raise CredWalletError(\
222                    "Input Credentials Repository instance is not invalid")
223
224            self.__credRepos = credRepos
225        else:
226            try:
227                self.__credRepos = CredRepos(credReposPropFilePath)
228               
229            except Exception, e:
230                raise CredWalletError(\
231                            "Error accessing credentials repository: %s" % e)
232       
233       
234        # Credentials are stored as a dictionary one element per attribute
235        # certicate held and indexed by certificate issuer name
236        self.__credentials = {}
237
238
239        # Check for valid attribute certificates for the user
240        try:
241            userCred = self.__credRepos.getCredentials(self.__dn)
242
243        except Exception, e:
244            raise CredWalletError(
245                "Error updating wallet with credentials from repository: %s"%\
246                e)
247
248
249        # Update wallet with attribute certificates stored in the repository
250        # Store ID and certificate instantiated as an AttCert type
251        try:
252            for cred in userCred:
253               
254                attCert = AttCertParse(cred.attCert)
255                issuerName = attCert['issuerName']
256               
257                self.__credentials[issuerName] = \
258                                            {'id':cred.id, 'attCert':attCert}
259        except Exception, e:
260            try:
261                raise CredWalletError(
262                                "Error parsing Attribute Certificate ID '" + \
263                                cred.id + "' retrieved from the " + \
264                                "Credentials Repository: %s" % e)               
265            except:
266                raise CredWalletError("Error parsing Attribute " + \
267                                      "Certificate retrieved from the " + \
268                                      "Credentials Repository: %s:" % e)
269       
270       
271        # Filter out expired or otherwise invalid certificates
272        self.audit()
273       
274
275
276
277    def __str__(self):
278        return "<Credential Wallet instance>"
279
280   
281    def __delitem__(self, key):
282        "CredWallet keys cannot be removed"       
283        raise CredWalletError('Keys cannot be deleted from ' + \
284                              CredWallet.__name__)
285
286
287    def __setitem__(self, key, value):
288        """Enable access to __proxyCertTxt using dictionary like
289        behaviour"""
290        if key == 'proxyCertTxt':
291            self.setProxyCert(value)
292        else:
293            raise CredWalletError('Key "%s" not recognised' % key)
294
295
296    def __getitem__(self, key):
297        """Enable access to __proxyCertTxt using dictionary like
298        behaviour"""
299        if key == 'proxyCertTxt':
300            return self.__proxyCertTxt
301       
302        elif key == 'proxyCert':
303            return self.__proxyCert
304       
305        elif key == 'credentials':
306            return self.__credentials
307        else:
308            raise CredWalletError('Key "%s" not recognised' % key)
309
310
311    def clear(self):
312        """Override UserDict default behaviour"""
313        raise CredWalletError("Data cannot be cleared from " + \
314                              CredWallet.__name__)
315
316   
317    def copy(self):
318        """Override UserDict default behaviour"""
319        raise CredWalletError("A copy cannot be made of "+CredWallet.__name__)
320
321   
322    def keys(self):
323        return ['proxyCertTxt', 'proxyCert', 'credentials']
324
325
326    def items(self):
327        return [('proxyCertTxt', self.__proxyCertTxt)]
328
329
330    def values(self):
331        return [self.__proxyCertTxt]
332
333
334    def has_key(self):
335        return self.__dat.has_key()
336
337
338
339       
340    def setProxyCert(self, proxyCertTxt):
341        """Set a new proxy certificate for the wallet
342
343        proxyCert: input certificate as a string"""
344       
345        try:
346            if not isinstance(proxyCertTxt, basestring):
347                raise CredWalletError(\
348                                "Proxy Certificate must be input as a string")
349        except Exception, e:
350            raise CredWalletError("Input proxy certificate: %s" % e)
351
352        self.__proxyCertTxt = proxyCertTxt
353        self.__proxyCert = X509Cert()
354        self.__proxyCert.parse(proxyCertTxt)
355
356
357
358
359    def isValid(self, **x509CertKeys):
360        """Check wallet's proxy cert.  If epxired return False"""
361        try:
362            return self.__proxyCert.isValidTime(**x509CertKeys)
363
364        except Exception, e:
365            raise CredWalletError("Credential Wallet: %s" % e)
366
367
368   
369    def addCredential(self, attCert, bUpdateCredRepos=True):
370        """Add a new attribute certificate to the list of credentials held.
371        Return True if certificate was added otherwise False.  - If an
372        existing certificate from the same issuer has a later expiry it will
373        take precence and the new input certificate is ignored.
374
375        attCert:           new attribute Certificate to be added
376        bUpdateCredRepos:   if set to True, the repository will be updated
377                            with the new credentials also"""
378
379        # Check input
380        try:
381            if not isinstance(attCert, AttCert):
382                raise CredWalletError(\
383                    "Attribute Certificate must be an AttCert type object")
384                   
385        except Exception, e:
386            raise CredWalletError("Attribute Certificate input: %s" % e)
387
388
389        # Check certificate validity
390        try:
391            attCert.isValid(raiseExcep=True)
392           
393        except AttCertError, e:
394            raise CredWalletError("Adding Credential: %s" % e)
395       
396
397        # Check to see if there is an existing Attribute Certificate held
398        # that was issued by the same host.  If so, compare the expiry time.
399        # The one with the latest expiry will be retained and the other
400        # ingored
401        bUpdateCred = True
402        issuerName = attCert['issuerName']
403       
404        if issuerName in self.__credentials:
405            # There is an existing certificate held with the same issuing
406            # host name as the new certificate
407            attCertOld = self.__credentials[issuerName]['attCert']
408
409            # Get expiry times in datetime format to allow comparison
410            dtAttCertOldNotAfter = attCertOld.getValidityNotAfter(\
411                                                            asDatetime=True)
412            dtAttCertNotAfter = attCert.getValidityNotAfter(asDatetime=True)
413
414            # If the new certificate has an earlier expiry time then ignore it
415            bUpdateCred = dtAttCertNotAfter > dtAttCertOldNotAfter
416
417               
418        if bUpdateCred:
419            # Update: Nb. -1 ID value flags item as new.  Items read in
420            # from the CredentialRepository during creation of the wallet will
421            # have +ve IDs previously allocated by the database
422            self.__credentials[issuerName] = {'id': -1, 'attCert': attCert}
423
424            # Update the Credentials Repository - the permanent store of user
425            # authorisation credentials.  This allows credentials for previous
426            # sessions to be re-instated
427            if bUpdateCredRepos:
428                self.updateCredRepos()
429
430        # Flag to caller to indicate whether the input certificate was added
431        # to the credentials or an exsiting certificate from the same issuer
432        # took precedence
433        return bUpdateCred
434           
435
436
437    def audit(self):
438        """Check the credentials held in the wallet removing any that have
439        expired or are otherwise invalid."""
440
441        # Nb. No signature check is carried out.  To do a check, access is
442        # needed to the cert of the CA that issued the Attribute Authority's
443        # cert
444        #
445        # P J Kershaw 12/09/05
446        for item in self.__credentials.items():
447            if not item[1]['attCert'].isValid(chkSig=False):
448                del self.__credentials[item[0]]
449
450
451
452               
453    def updateCredRepos(self, auditCred=True):
454        """Copy over non-persistent credentials held by wallet into the
455        perminent repository."""
456
457        # Filter out invalid certs unless auditCred flag is explicitly set to
458        # false
459        if auditCred: self.audit()
460
461        # Update the database - only add new entries i.e. with an ID of -1
462        attCertList = [i['attCert'] for i in self.__credentials.values() \
463                        if i['id'] == -1]
464
465        self.__credRepos.addCredentials(self.__dn, attCertList)
466
467
468       
469    def __reqAuthorisation(self,
470                           aaWSDL=None,
471                           aaPropFilePath=None,
472                           extAttCert=None,
473                           bDebug=False):
474       
475        """Wrapper to Attribute Authority authorisation request.  See
476        reqAuthorisation for the classes' public interface.
477
478        To call the Attibute Authority as a Web Service, specify a WSDL
479        otherwise set the properties file path.
480       
481        If successful, a new attribute certificate is issued to the user
482        and added into the wallet
483
484        aaWSDL|aaPropFilePath:  to call as a web service, specify the file
485                                path or URI for the Attribute Authority's
486                                WSDL.  Otherwise, to run on the local machine,
487                                specify a local Attribute Authority
488                                configuration file.
489                               
490        extAttCert:            an existing Attribute Certificate which can be
491                                used to making a mapping should the user not
492                                be registered with the Attribute Authority"""
493
494        if extAttCert is not None:
495            if not isinstance(extAttCert, AttCert):
496                raise CredWalletError(\
497                    "Input Attribute Certificate must be AttCert type")
498
499            extAttCertTxt = extAttCert.asString()
500        else:
501            extAttCertTxt = '' # None
502
503           
504        if aaWSDL is not None:
505
506            if not isinstance(aaWSDL, basestring):
507                raise CredWalletError("Attribute Authority WSDL file " + \
508                                      "path must be a valid string")
509
510            try:               
511                # Get Attribute Authority web service interface
512                if bDebug:
513                    traceFile = sys.stderr
514                else:
515                    traceFile = None
516                   
517                aaSrv = ServiceProxy(aaWSDL,
518                                     use_wsdl=True,
519                                     tracefile=traceFile)
520               
521                # Call Attribute Authority's Web service
522                resp=aaSrv.reqAuthorisation(usrProxyCert=self.__proxyCertTxt,
523                                            usrAttCert=extAttCertTxt)
524
525            except socket.error, e:
526                raise CredWalletError("Requesting authorisation: %s" % e[1])
527               
528            except Exception, e:
529                raise CredWalletError("Requesting authorisation: %s" % e)
530
531
532            # Check the status code returned from the authorisation request
533            if resp['statCode'] == 'AccessError':
534                raise CredWalletError(str(resp['errMsg']))
535           
536            elif resp['statCode'] == 'AccessDenied':
537                raise CredWalletAuthorisationDenied(\
538                            "Authorisation denied: %s" % str(resp['errMsg']))
539
540            elif resp['statCode'] == 'AccessGranted':
541                attCertTxt = resp['attCert']
542
543            else:
544                raise CredWalletError("Attribute Authority authorisation " + \
545                                      "status code not recognised")
546           
547        elif aaPropFilePath is not None:
548
549            # Call local based Attribute Authority with settings from the
550            # configuration file aaPropFilePath
551
552            if not isinstance(aaPropFilePath, basestring):
553                raise CredWalletError("Attribute Authority Configuration " + \
554                                      "file path must be a valid string")
555                                   
556            try:
557                # Make a new attribute authority instance
558                aa = AttAuthority(aaPropFilePath)
559
560                # Request a new attribute certificate from the Attribute
561                # Authority
562                attCertTxt = aa.authorise(\
563                                    usrProxyCertFileTxt=self.__proxyCertTxt,
564                                    extAttCertFileTxt=extAttCertTxt)
565               
566            except AttAuthorityAccessDenied, e:
567                raise CredWalletAuthorisationDenied(\
568                                    "Authorisation denied: %s" % e)
569           
570            except Exception, e:
571                raise CredWalletError("Requesting authorisation: %s" % e)
572
573        else:
574            raise CredWalletError("Error requesting authorisation: " + \
575                                  "a WSDL file or Attribute Authority " + \
576                                  "configuration file must be specified")
577
578       
579        # Convert text into Attribute Certificate object
580        try:
581            attCert = AttCertParse(attCertTxt)
582           
583        except Exception, e:
584            raise CredWalletError("Parsing Attribute Certificate returned " +\
585                                  "from authorisation request: %s" % e)
586       
587
588        # The Attribute Authority's certificate and it's CA certificate
589        # are required in order to verify that the signature is valid.
590        # This check is carried out by addCredential()
591        try:
592            aaCertRec = \
593                self.__credRepos.AACertificate.selectBy(dn=attCert['issuer'])
594           
595        except Exception, e:
596            raise CredWalletError("Accessing certificate for %s: %s",
597                                                (attCert['issuer'], str(e)))
598
599        if not aaCertRec.count():
600            raise CredWalletError("No certificate found in repository " + \
601                                  "matching '" + attCert['issuer'] + "'")
602       
603
604        # Parse Attribute Authority certificate read from database record
605        try:
606            aaCert = X509CertParse(aaCertRec[0].cert)
607
608        except Exception, e:
609            raise CredWalletError("Attribute Authority Certificate " + \
610                                  "returned from authorisation request: " + \
611                                  "%s" % e)
612
613
614        # Search for CA certificate of Attribute Authority
615        try:
616            # CA Certificates DN is present in Attribute Authority Certificate
617            # issuer field
618            caCertDN = aaCert.getIssuer().serialise()
619            caCertRec = self.__credRepos.AACertificate.selectBy(dn=caCertDN)
620           
621        except Exception, e:
622            raise CredWalletError("Accessing certificate for %s: %s",
623                                                (aaCert.getDN(), str(e)))
624
625        if not caCertRec.count():
626            raise CredWalletError("No certificate found in repository " + \
627                                  "matching '" + aaCert.getDN() + "'")
628
629
630        # Make temporary store for certificate extracted from database -
631        # AttCert class handles certificates via files
632        caCertFile = tempfile.NamedTemporaryFile('w', -1, '.pem', 'caCert-')
633        open(caCertFile.name, 'w').write(caCertRec[0].cert)
634
635        # Update attribute Certificate ready for validation in addCredential()
636        attCert.setCertFilePathList(caCertFile.name)
637
638       
639        # Add credential into wallet
640        #
641        # Nb. if the certificates signature is invalid, it will be rejected
642        self.addCredential(attCert)
643
644
645        return attCert
646
647
648
649
650    def getAATrustedHostInfo(self,
651                             userRole,
652                             aaWSDL=None,
653                             aaPropFilePath=None):
654        """Wrapper to Attribute Authority getTrustedHostInfo
655       
656        userRole:               get hosts which have a mpping to this role
657        aaWSDL|aaPropFilePath:  to call as a web service, specify the file
658                                path or URI for the Attribute Authority's
659                                WSDL.  Otherwise, to run on the local machine,
660                                specify a local Attribute Authority
661                                configuration file."""
662
663        if not isinstance(userRole, basestring) or not userRole:
664            raise CredWalletError("User Role must be a valid string")
665
666       
667        if aaWSDL is not None:
668
669            if not isinstance(aaWSDL, basestring):
670                raise CredWalletError("Attribute Authority WSDL file " + \
671                                      "path must be a valid string")
672
673            try:               
674                # Get Attribute Authority web service interface
675                aaSrv = ServiceProxy(aaWSDL, use_wsdl=True)
676               
677                # Call Attribute Authority's Web service
678                resp = aaSrv.getTrustedHostInfo(usrRole=userRole)
679                if resp['errMsg']:
680                    raise Exception(resp['errMsg'])
681
682                # De-serialise output into a dictionary of roles indexed by
683                # host name
684                hostList = []
685                for host in resp['trustedHostInfo']:
686                    hostSplit = re.split("\s*:\s*", str(host))
687                    roleList = re.split("\s*,\s*", hostSplit[2])
688                   
689                    hostList.append((hostSplit[0], \
690                                    {'wsdl': hostSplit[1], 'role': roleList}))
691
692                return dict(hostList)
693           
694            except socket.error, e:
695                raise CredWalletError("Requesting trusted host info: %s" % \
696                                      e[1])               
697            except Exception, e:
698                raise CredWalletError("Requesting trusted host info: %s" % e)
699
700           
701        elif aaPropFilePath is not None:
702
703            # Call local based Attribute Authority with settings from the
704            # configuration file aaPropFilePath
705
706            if not instance(aaWSDL, basestring):
707                raise CredWalletError("Attribute Authority Configuration " + \
708                                      "file path must be a valid string")
709                                   
710            try:
711                # Make a new attribute authority instance
712                aa = AttAuthority(aaPropFilePath)
713
714                # Request a new attribute certificate from the Attribute
715                # Authority
716                return aa.getTrustedHosts(userRole)
717               
718            except Exception, e:
719                raise CredWalletError("Requesting trusted host info: %s" % e)
720
721        else:
722            raise CredWalletError("Error requesting trusted hosts info: " + \
723                                  "a WSDL file or Attribute Authority " + \
724                                  "configuration file must be specified")
725
726
727   
728
729    def reqAuthorisation(self,
730                         reqRole=None,
731                         aaWSDL=None,
732                         aaPropFilePath=None,
733                         bMapFromTrustedHosts=None,
734                         bSetExtAttCertList=None,
735                         extAttCertList=None,
736                         extTrustedHostList=None):
737       
738        """For a given role, get authorisation from an Attribute Authority
739        using a user's proxy certificate.  If this fails try to make a mapped
740        Attribute Certificate by using a certificate from another host which
741        has a trust relationship to the Attribute Authority in question.
742
743        reqRole:                the required role to get access for
744        aaWSDL|aaPropFilePath:  to call as a web service, specify the file
745                                path or URI for the Attribute Authority's
746                                WSDL.  Otherwise, to run on the local machine,
747                                specify a local Attribute Authority
748                                configuration file.
749
750        bMapFromTrustedHosts:   if authorisation fails via the user's proxy
751                                certificate, then it is possible to get a
752                                mapped certificate by using certificates from
753                                other AA's.  Set this flag to True, to allow
754                                this second stage of generating a mapped
755                                certificate from the certificate stored in the
756                                wallet credentials.
757
758                                If set to False, it is possible to return the
759                                list of certificates available for mapping and
760                                then choose which one or ones to use for
761                                mapping by re-calling reqAuthorisation with
762                                extAttCertList set to these certificates
763
764                                The list is returned via
765                                CredWalletAuthorisationDenied exception
766
767                                If no value is set, the default value held
768                                in self.__bMapFromTrustedHosts is used
769
770        bSetExtAttCertList:     make a list of of certificates
771                                from other Attribute Authorities.  If
772                                bMapFromTrustedHosts is set True this flag is
773                                overriden and effectively set to True.
774
775                                If no value is set, the default value held
776                                in self.__bSetExtAttCertList is used
777                               
778        extAttCertList:         Attribute Certificate or list of certificates
779                                from other Attribute Authorities.  These can
780                                be used to get a mapped certificate if access
781                                fails based on the user's proxy certificate
782                                credentials.  They are tried out in turn until
783                                access is granted so the order of the list
784                                decides the order in which they will be tried
785
786        extTrustedHostList:     same as extAttCertList keyword, but instead
787                                providing Attribute Certificates, give a list
788                                of Attribute Authority hosts.  These will be
789                                matched up to Attribute Certificates held in
790                                the wallet.  Matching certificates will then
791                                be used to try to get mapped authorisation.
792                               
793        The procedure is:
794
795        1) Try authorisation using proxy certificate
796        2) If the Attribute Authority (AA) doesn't recognise the certificate,
797        find out any other hosts which have a trust relationship to the AA.
798        3) Look for Attribute Certificates held in the wallet corresponding
799        to these hosts.
800        4) If no Attribute Certificates are available, call the relevant
801        hosts' AAs to get certificates
802        5) Finally, use these new certificates to try to obtain a mapped
803        certificate from the original AA
804        6) If this fails access is denied"""
805
806
807        # Check for settings from input, if not set use previous settings
808        # made
809        if bMapFromTrustedHosts is not None:
810            self.__bMapFromTrustedHosts = bMapFromTrustedHosts
811
812        if bSetExtAttCertList is not None:
813            self.__bSetExtAttCertList = bSetExtAttCertList
814
815
816        # Check for external Attribute Certificates
817        if extTrustedHostList:
818            if not self.__bMapFromTrustedHosts:
819                raise CredWalletError("A list of trusted hosts has been " + \
820                                      "input but mapping from trusted " + \
821                                      "hosts is set to disallowed")
822           
823            if isinstance(extTrustedHostList, basestring):
824                extTrustedHostList = [extTrustedHostList]
825
826            # Nb. Any extAttCertList is overriden by extTrustedHostList being
827            # set
828            extAttCertList = []
829            for hostName in extTrustedHostList:
830
831                if hostName in self.__credentials:
832                    extAttCertList.append(\
833                                    self.__credentials[hostName]['attCert'])
834
835
836        # Repeat authorisation attempts until succeed or means are exhausted       
837        while True:
838           
839            # Check for candidate certificates for mapping
840            try:
841                # If list is set get the next cert
842                extAttCert = extAttCertList.pop()
843
844            except AttributeError:
845               
846                # No List set - attempt authorisation without
847                # using mapping from trusted hosts
848                extAttCert = None
849                               
850            except IndexError:
851               
852                # List has been emptied without authorisation succeeding -
853                # give up
854                raise CredWalletAuthorisationDenied(\
855                    "Attempting to obtained a mapped certificate: " + \
856                    "no external attribute certificates are available")
857
858
859            # Request Authorisation from Attribute Authority
860            try:
861                attCert = self.__reqAuthorisation(aaWSDL=aaWSDL,
862                                                aaPropFilePath=aaPropFilePath,
863                                                extAttCert=extAttCert)               
864                # Access granted
865                return attCert
866           
867            except CredWalletAuthorisationDenied, authorisationDenied:
868
869                # If a required role was set then it's possible to go
870                # to get certificates with mapped roles from trusted hosts
871                if not reqRole:
872                    raise CredWalletAuthorisationDenied(\
873                        "No user role was input in order to map to " + \
874                        "a role in a trusted host")
875
876
877                #  Use the input required role and the AA's trusted host list
878                # to identify attribute certificates from other hosts which
879                # could be used to make a mapped certificate
880                try:
881                    trustedHostInfo = self.getAATrustedHostInfo(reqRole,
882                                                aaWSDL=aaWSDL,
883                                                aaPropFilePath=aaPropFilePath)
884                except Exception, e:
885                    raise CredWalletError("Getting trusted hosts: %s" % e)
886
887                if not trustedHostInfo:
888                    raise CredWalletAuthorisationDenied(\
889                        "Attribute Authority has no trusted hosts with " + \
890                        "which to make a mapping")
891
892
893                if not bMapFromTrustedHosts and not bSetExtAttCertList:
894                    # Creating a mapped certificate is not allowed - raise
895                    # authorisation denied exception saved from earlier
896                    raise authorisationDenied
897
898               
899                # Initialise external certificate list here - if none are
900                # found IndexError will be raised on the next iteration and
901                # an access denied error will be raised
902                extAttCertList = []
903
904                # Look for Attribute Certificates with matching issuer host
905                # names
906                for hostName in self.__credentials:
907
908                    # Nb. Candidate certificates for mappings must have
909                    # original provenance and contain at least one of the
910                    # required roles
911                    attCert = self.__credentials[hostName]['attCert']
912                   
913                    if hostName in trustedHostInfo and attCert.isOriginal():                       
914                        for role in attCert.getRoles():
915                            if role in trustedHostInfo[hostName]['role']:                               
916                                extAttCertList.append(attCert)
917
918
919                if not extAttCertList:
920                    # No certificates in the wallet matched the trusted host
921                    # and required roles
922                    #
923                    # Try each host in turn in order to get a certificate with
924                    # the required credentials in order to do a mapping
925                    for i in trustedHostInfo.items():
926
927                        try:
928                            extAttCert = self.__reqAuthorisation(\
929                                                       aaWSDL=i[1]['wsdl'])
930
931                            # Check the certificate contains at least one of
932                            # the required roles
933                            roles = extAttCert.getRoles()
934                            if [True for r in roles if r in i[1]['role']]:
935                               extAttCertList.append(extAttCert)
936
937                               # For efficiency, stop once obtained a valid
938                               # cert - but may want complete list for user to
939                               # choose from
940                               #break
941                               
942                        except Exception, e:
943                            pass    # ignore any errors and continue
944                   
945                if not extAttCertList:                       
946                    raise CredWalletAuthorisationDenied(\
947                        "No certificates are available with which to " + \
948                        "make a mapping to the Attribute Authority")
949
950
951                if not bMapFromTrustedHosts:
952                   
953                    # Exit here returning the list of candidate certificates
954                    # that could be used to make a mapped certificate
955                    msg = "User is not registered with Attribute " + \
956                          "Authority - retry using one of the returned " + \
957                          "Attribute Certificates obtained from other " + \
958                          "trusted hosts"
959                         
960                    raise CredWalletAuthorisationDenied(msg=msg,
961                                            extAttCertList=extAttCertList,
962                                            trustedHostInfo=trustedHostInfo)
963               
964            except Exception, authorisationError:
965                # Authorisation request raised an error other than access
966                # denied
967                raise authorisationError
968
969
970
971       
972#_____________________________________________________________________________
973class CredReposError(Exception):   
974    """Exception handling for NDG Credential Repository class."""
975   
976    def __init__(self, msg):
977        self.__msg = msg
978         
979    def __str__(self):
980        return self.__msg
981   
982
983
984
985#_____________________________________________________________________________
986class CredRepos:
987    """Interface to Credentials Repository Database"""
988
989    # valid configuration property keywords
990    __validKeys = ['dbURI']
991   
992
993    def __init__(self, propFilePath=None, **prop):
994        """Initialise Credentials Repository Database object.
995
996        If the connection string or properties file is set a connection
997        will be made
998
999        dbURI:              <db type>://<username>:<passwd>@<hostname>/dbname
1000        propFilePath: file path to properties file
1001
1002        Nb. propFilePath setting overrides input dbURI
1003        """
1004           
1005        self.__con = None
1006        self.__prop = {}
1007       
1008        if propFilePath is not None:
1009           
1010            # Read database URI set in file
1011            self.readProperties(propFilePath)
1012           
1013        elif prop != {}:
1014           
1015            # Database URI may have been set as an input keyword argument
1016            self.setProperties(**prop)
1017
1018
1019
1020
1021    def __setConnection(self, dbURI):
1022        """Establish a database connection from a database URI
1023       
1024        dbURI: '<db type>://<username>:<passwd>:<hostname>/dbname"""
1025
1026        try:
1027            self.__con = connectionForURI(dbURI)
1028        except Exception, e:
1029            raise CredReposError("Error connecting to database: %s" % e)
1030
1031        # Copy the connection object into the table classes
1032        CredRepos.User._connection = self.__con
1033        CredRepos.UserCredential._connection = self.__con
1034        CredRepos.AACertificate._connection = self.__con
1035         
1036
1037
1038
1039    def setProperties(self, **prop):
1040        """Update existing properties from an input dictionary
1041        Check input keys are valid names"""
1042       
1043        for key in prop.keys():
1044            if key not in self.__validKeys:
1045                raise CredReposError("Property name \"%s\" is invalid" % key)
1046               
1047        self.__prop.update(prop)
1048
1049
1050        # Update connection setting
1051        if 'dbURI' in prop:
1052            self.__setConnection(prop['dbURI'])
1053               
1054
1055
1056       
1057    def readProperties(self, propFilePath=None, propElem=None):
1058
1059        """Read the configuration properties for the CredentialRepository
1060
1061        propFilePath|propElem
1062
1063        propFilePath: set to read from the specified file
1064        propElem:     set to read beginning from a cElementTree node"""
1065
1066        if propFilePath is not None:
1067
1068            try:
1069                tree = ElementTree.parse(propFilePath)
1070                propElem = tree.getroot()
1071               
1072            except IOError, e:
1073                raise CredReposError(\
1074                                "Error parsing properties file \"%s\": %s" % \
1075                                (e.filename, e.strerror))
1076
1077            except Exception, e:
1078                raise CredReposError("Error parsing properties file: %s" % \
1079                                    str(e))
1080
1081        if propElem is None:
1082            raise CredReposError("Root element for parsing is not defined")
1083
1084
1085        # Read properties into a dictionary
1086        prop = dict([(elem.tag, elem.text) for elem in propElem])
1087        self.setProperties(**prop)
1088
1089           
1090
1091    def addUser(self, userName, dn):
1092        """A new user to Credentials Repository"""
1093        try:
1094            self.User(userName=userName, dn=dn)
1095
1096        except Exception, e:
1097            raise CredReposError("Error adding new user '%s': %s" % \
1098                                                        (userName, e))
1099
1100
1101
1102                           
1103    def auditCredentials(self, **attCertValidKeys):
1104        """Check the attribute certificates held in the repository and delete
1105        any that have expired
1106
1107        attCertValidKeys:  keywords which set how to check the Attribute
1108                            Certificate e.g. check validity time, XML
1109                            signature, version etc.  Default is check
1110                            validity time only"""
1111
1112        if attCertValidKeys == {}:
1113            # Default to check only the validity time
1114            attCertValidKeys = {    'chkTime':          True,
1115                                    'chkVersion':       False,
1116                                    'chkProvenance':    False,
1117                                    'chkSig':           False }
1118           
1119        try:
1120            credList = self.UserCredential.select()
1121           
1122        except Exception, e:
1123            raise CredReposError("Selecting credentials from repository: %s",\
1124                                 e)
1125
1126        # Iterate through list of credentials deleting records where the
1127        # certificate is invalid
1128        try:
1129            for cred in credList:
1130                attCert = AttCertParse(cred.attCert)
1131               
1132                if not attCert.isValid(**attCertValidKeys):
1133                    self.UserCredential.delete(cred.id)
1134                   
1135        except Exception, e:
1136            try:
1137                raise CredReposError("Deleting credentials for '%s': %s",
1138                                                       (cred.dn, e))
1139            except:
1140                raise CredReposError("Deleting credentials: %s", e)
1141
1142
1143
1144
1145    def getCredentials(self, dn):
1146        """Get the list of credentials for a given user's DN"""
1147
1148        try:
1149            return self.UserCredential.selectBy(dn=dn)
1150           
1151        except Exception, e:
1152            raise CredReposError("Selecting credentials for %s: %s" % (dn, e))
1153
1154
1155
1156       
1157    def addCredentials(self, dn, attCertList):
1158        """Add new attribute certificates for a user.  The user must have
1159        been previously registered in the repository
1160
1161        dn:             users Distinguished name
1162        attCertList:   list of attribute certificates"""
1163       
1164        try:
1165            userCred = self.User.selectBy(dn=dn)
1166           
1167            if userCred.count() == 0:
1168                raise CredReposError("User \"%s\" is not registered" % dn)
1169
1170        # Make explicit trap for MySQL interface error since it has no error
1171        # message associated with it
1172        except _mysql_exceptions.InterfaceError, e:
1173            raise CredReposError("Checking for user \"%s\": %s" % \
1174                                 (dn, "MySQL interface error"))
1175       
1176        except Exception, e:
1177            raise CredReposError("Checking for user \"%s\":" % (dn, e))
1178
1179       
1180        # Carry out check? - filter out certs in db where a new cert
1181        # supercedes it - i.e. expires later and has the same roles
1182        # assigned - May be too complicated to implement
1183        #uniqAttCertList = [attCert for attCert in attCertList \
1184        #    if min([attCert == cred.attCert for cred in userCred])]
1185       
1186               
1187        # Update database with new entries
1188        try:
1189            for attCert in attCertList:
1190                self.UserCredential(dn=dn, attCert=attCert.asString())
1191
1192        except _mysql_exceptions.InterfaceError, e:
1193            raise CredReposError("Adding new user credentials for " + \
1194                                 "user %s: %s" % (dn,"MySQL interface error"))
1195        except Exception, e:
1196            raise CredReposError("Adding new user credentials for " + \
1197                                 "user %s: %s" % (dn, e))
1198
1199
1200
1201
1202    #_________________________________________________________________________
1203    # Database tables defined using SQLObject derived classes
1204    # Nb. These are class variables of the CredRepos class
1205    class User(SQLObject):
1206        """SQLObject derived class to define Credentials Repository db table
1207        to store user information"""
1208
1209        # to be assigned to connectionForURI(<db URI>)
1210        _connection = None
1211
1212        # Force table name
1213        _table = "User"
1214
1215        userName = StringCol(dbName='userName', length=30)
1216        dn = StringCol(dbName='dn', length=128)
1217
1218
1219    class UserCredential(SQLObject):
1220        """SQLObject derived class to define Credentials Repository db table
1221        to store user credentials information"""
1222
1223        # to be assigned to connectionForURI(<db URI>)
1224        _connection = None
1225
1226        # Force table name
1227        _table = "UserCredential"
1228
1229       
1230        # User name field binds with UserCredential table
1231        dn = StringCol(dbName='dn', length=128)
1232
1233        # Store complete attribute certificate text
1234        attCert = StringCol(dbName='attCert')
1235
1236
1237    class AACertificate(SQLObject):
1238        """SQLObject derived class to define Credentials Repository db table
1239        to store certificates of recognised Attribute Authorities and their
1240        CAs as required
1241
1242        These certificates are needed to check the validity of Attribute
1243        Certificates when they are granted from Attribute Authorities"""
1244
1245        # to be assigned to connectionForURI(<db URI>)
1246        _connection = None
1247
1248        # Force table name
1249        _table = "AACertificate"
1250
1251       
1252        # Distinguished Name for Attribute Authority
1253        dn = StringCol(dbName='dn', length=128)
1254
1255        # Name identifier as it appears in Attribute Certificate issuerName
1256        # field
1257        name = StringCol(dbName='name', length=30)
1258
1259        # Store complete attribute certificate text
1260        cert = StringCol(dbName='cert')
1261
1262
1263
1264
1265#_____________________________________________________________________________
1266class UserSessionError(Exception):   
1267    """Exception handling for NDG User Session class."""
1268   
1269    def __init__(self, msg):
1270        self.__msg = msg
1271         
1272    def __str__(self):
1273        return self.__msg
1274
1275
1276
1277
1278#_____________________________________________________________________________
1279class UserSession(UserDict):
1280    """Session details - created when a user logs into NDG"""
1281
1282    # Session ID
1283    __sessIDlen = 128
1284    __sessIDtagName = "Hash"
1285
1286    # Follow standard format for cookie path expiry attributes
1287    __sessCookieExpiryTagName = "expires"
1288    __sessCookiePathTagName = "path"
1289   
1290    __sessCookiePath = "/"
1291    __sessCookieExpiryFmt = "%a, %d-%b-%Y %H:%M:%S GMT"
1292
1293
1294    def __init__(self, *credWalletArgs, **credWalletKeys):
1295
1296        # Base class initialisation
1297        UserDict.__init__(self)
1298
1299        # Each User Session has one or more browser sessions associated with
1300        # it.  These are stored in a list
1301        self.__sessID = []
1302        self.createSessID()
1303        self.credWallet = CredWallet(*credWalletArgs, **credWalletKeys)
1304
1305
1306
1307               
1308    def __repr__(self):
1309        "Represent User Session"       
1310        return "<UserSession instance>"
1311
1312               
1313    def __delitem__(self, key):
1314        "UserSession keys cannot be removed"       
1315        raise UserSessionError('Keys cannot be deleted from ' + \
1316                               UserSession.__name__)
1317
1318
1319    def __getitem__(self, key):
1320        """Enable access to list of session IDs using dictionary like
1321        behaviour"""
1322        if key == 'sessID':
1323            return self.__sessID
1324        else:
1325            raise UserSessionError('Key "%s" not recognised' % key)
1326
1327
1328    def clear(self):
1329        """Override UserDict default behaviour"""
1330        raise UserSessionError("Data cannot be cleared from " + \
1331                               UserSession.__name__)
1332
1333   
1334    def copy(self):
1335        """Override UserDict default behaviour"""
1336        raise UserSessionError("A copy cannot be made of " + \
1337                               UserSession.__name__)
1338
1339   
1340    def keys(self):
1341        return ['sessID']
1342
1343
1344    def items(self):
1345        return [('sessID', self.__sessID)]
1346
1347
1348    def values(self):
1349        return [self.__sessID]
1350
1351
1352    def createSessID(self):
1353        """Add a new session ID to be associated with this UserSession
1354        instance"""
1355
1356        # base 64 encode output from urandom - raw output from urandom is
1357        # causes problems when passed over SOAP.  A consequence of this is
1358        # that the string length of the session ID will almost certainly be
1359        # longer than SessionMgr.__sessIDlen
1360        self.__newSessID = base64.b64encode(os.urandom(self.__sessIDlen))
1361        self.__sessID.append(self.__newSessID)
1362       
1363
1364    def newSessID(self):
1365        """Get the session ID most recently allocated"""
1366        return self.__newSessID
1367
1368
1369    def getExpiryStr(self):
1370        """Return session expiry date/time as would formatted for a cookie"""
1371
1372        try:
1373            # Proxy certificate's not after time determines the expiry
1374            dtNotAfter = self.credWallet['proxyCert'].getNotAfter()
1375
1376            return dtNotAfter.strftime(self.__sessCookieExpiryFmt)
1377        except Exception, e:
1378            UserSessionError("getExpiry: %s" % e)
1379           
1380   
1381    def createCookie(self, sessID=None, asString=True):
1382        """Create cookie containing session ID and expiry
1383
1384        sessID:     if no session ID is provided, return the latest one to
1385                    be allocated.
1386        asString:   Set to True to return the cookie as string text.  If
1387                    False, it is returned as a SimpleCookie type."""
1388
1389        try:
1390            if sessID is None:
1391                sessID = self.__sessID[-1]
1392               
1393            sessCookie = SimpleCookie()
1394            sessCookie[self.__sessIDtagName] = sessID
1395
1396            # Use standard format for cookie path and expiry
1397            sessCookie[self.__sessIDtagName][self.__sessCookiePathTagName] =\
1398                                                        self.__sessCookiePath
1399           
1400            sessCookie[self.__sessIDtagName][self.__sessCookieExpiryTagName] =\
1401                                                            self.getExpiryStr()
1402                                       
1403            # Make cookie as generic as possible for domains - Nb. '.uk'
1404            # alone won't work
1405            sessCookie[self.__sessIDtagName]['domain'] = '.ac.uk'
1406
1407                       
1408            # Caller should set the cookie e.g. in a CGI script
1409            # print "Content-type: text/html"
1410            # print cookie.output() + os.linesep
1411            if asString:
1412                return sessCookie.output() + os.linesep + sessCookie2.output()
1413            else:
1414                return sessCookie
1415           
1416        except Exception, e:
1417            UserSessionError("createCookie: %s" % e)
1418
1419
1420
1421
1422#_____________________________________________________________________________
1423class SessionMgrError(Exception):   
1424    """Exception handling for NDG Session Manager class."""
1425   
1426    def __init__(self, msg):
1427        self.__msg = msg
1428         
1429    def __str__(self):
1430        return self.__msg
1431
1432
1433
1434
1435#_____________________________________________________________________________
1436class SessionMgrAuthorisationDenied(CredWalletAuthorisationDenied):   
1437    """Handling exception where authorisation is denied to a session by an
1438    Attribute Authority."""
1439
1440    # This class can be an exact copy of the CredWallet equivalent
1441    pass
1442
1443
1444
1445
1446#_____________________________________________________________________________
1447class SessionMgr:
1448    """NDG login and session handling"""
1449
1450    # valid configuration property keywords
1451    __validKeys = ['myProxyProp', 'credReposProp']
1452
1453   
1454    def __init__(self, propFilePath=None, **prop):       
1455        """Create a new session manager to manager NDG User Sessions"""       
1456
1457        # MyProxy interface
1458        try:
1459            self.__myPx = MyProxy()
1460           
1461        except Exception, e:
1462            raise SessionMgrError("Creating MyProxy interface: %s" % e)
1463
1464       
1465        # Credentials repository - permanent stroe of user credentials
1466        try:
1467            self.__credRepos = CredRepos()
1468           
1469        except Exception, e:
1470            raise SessionMgrError(\
1471                    "Creating credentials repository interface: %s" % e)
1472
1473        self.__sessList = []
1474
1475
1476        if propFilePath is not None:
1477            self.readProperties(propFilePath)
1478
1479
1480           
1481
1482    def readProperties(self, propFilePath=None, propElem=None):
1483        """Read XML properties from a file or cElementTree node"""
1484
1485        if propFilePath is not None:
1486
1487            try:
1488                tree = ElementTree.parse(propFilePath)
1489                propElem = tree.getroot()
1490
1491            except IOError, e:
1492                raise SessionMgrError(\
1493                                "Error parsing properties file \"%s\": %s" % \
1494                                (e.filename, e.strerror))
1495               
1496            except Exception, e:
1497                raise SessionMgrError("Error parsing properties file: %s" % e)
1498
1499        if propElem is None:
1500            raise SessionMgrError("Root element for parsing is not defined")
1501
1502        # Get properties for MyProxy and CredentialRepository
1503        for elem in propElem:
1504            if elem.tag == 'myProxyProp':
1505                self.__myPx.readProperties(propElem=elem)
1506
1507            if elem.tag == 'credReposProp':
1508                self.__credRepos.readProperties(propElem=elem)
1509           
1510
1511
1512
1513    def setProperties(self, **prop):
1514        """Update existing properties from an input dictionary
1515        Check input keys are valid names"""
1516       
1517        for key in prop.keys():
1518            if key not in self.__validKeys:
1519                raise SessionMgrError("Property name \"%s\" is invalid" % key)
1520
1521        if 'myProxyProp' in prop:
1522            self.__myPx.setProperties(prop['myProxyProp'])
1523
1524        if 'credReposProp' in prop:
1525            self.__credRepos.setProperties(prop['credReposProp'])
1526           
1527
1528       
1529    def addUser(self,
1530                userName,
1531                userPassPhrase,
1532                caConfigFilePath=None,
1533                caPassPhrase=None):
1534       
1535        """Register a new user with NDG"
1536
1537        userName:                       user name for new user
1538       
1539        userPassPhrase:                 selected passphrase for new user
1540       
1541        caConfigFilePath|caPassPhrase:  pass phrase for SimpleCA's
1542                                        certificate.  Set via file or direct
1543                                        string input respectively.  Set here
1544                                        to override setting [if any] made at
1545                                        object creation.
1546       
1547                                        Passphrase is only required if
1548                                        SimpleCA is instantiated on the local
1549                                        machine.  If SimpleCA WS is called no
1550                                        passphrase is required."""
1551       
1552        try:
1553            # Add new user certificate to MyProxy Repository
1554            user = self.__myPx.addUser(userName,
1555                                       userPassPhrase,
1556                                       caConfigFilePath=caConfigFilePath,
1557                                       caPassPhrase=caPassPhrase,
1558                                       retDN=True)
1559           
1560            # Add to user database
1561            self.__credRepos.addUser(userName, user['dn'])
1562           
1563        except Exception, e:
1564            raise SessionMgrError("Error registering new user: %s" % e)
1565
1566
1567
1568       
1569    def connect(self, userName=None, passPhrase=None, sessID=None):       
1570        """Create and return a new user session or connect to an existing
1571        one:
1572
1573        connect([userName, passPhrase]|[sessID])
1574
1575        userName, passPhrase:   set username and pass-phrase to create a new
1576                                user session
1577        sessID:                 give the browser session ID corresponding to
1578                                an existing session"""
1579       
1580
1581        if sessID is not None:
1582            # Connect to session identified by session ID
1583            return self.__connect2UserSession(sessID)
1584        else:
1585            # Create a fresh session
1586            return self.__createUserSession(userName, passPhrase)
1587
1588
1589
1590
1591    def __createUserSession(self, userName, passPhrase):
1592        """Create a new user session from input user credentials"""
1593       
1594        if not userName:
1595            raise SessionMgrError("Username is null")
1596       
1597        if not passPhrase:
1598            raise SessionMgrError("Passphrase is null")
1599
1600       
1601        try:           
1602            # Get a proxy certificate to represent users ID for the new
1603            # session
1604            proxyCert = self.__myPx.getDelegation(userName, passPhrase)
1605
1606        except Exception, e:
1607            raise SessionMgrError("MyProxy: %s" % e)
1608
1609        try:   
1610            # Search for an existing session for the same user
1611            userSess = None
1612            for u in self.__sessList:
1613                if u.credWallet['proxyCert'].getDN()['CN'] == userName:
1614
1615                    # Existing session found
1616                    userSess = u
1617
1618                    # Replace it's Proxy Certificate with a more up to date
1619                    # one
1620                    userSess.credWallet.setProxyCert(proxyCert)
1621                    break
1622               
1623
1624            if userSess is None:
1625                # Create a new user session using the new proxy certificate
1626                # and session ID
1627                userSess = UserSession(proxyCert, credRepos=self.__credRepos)
1628               
1629                newSessID = userSess.newSessID()
1630               
1631                # Check for unique session ID
1632                for existingUserSess in self.__sessList:
1633                    if newSessID in existingUserSess['sessID']:
1634                        raise SessionMgrError(\
1635                            "Session ID is not unique:\n\n %s" % newSessID)
1636
1637                # Add new session to list                 
1638                self.__sessList.append(userSess)
1639
1640            return userSess
1641
1642        except Exception, e:
1643            raise SessionMgrError("Creating User Session: %s" % e)
1644
1645
1646
1647
1648    def __connect2UserSession(self, sessID):
1649        """Connect to an existing session by providing a valid session ID
1650
1651        sessID: ID corresponding to session to connect to."""
1652       
1653           
1654        # Look for a session corresponding to this ID
1655        try:
1656            for userSess in self.__sessList:
1657                if sessID in userSess['sessID']:
1658
1659                    # Check matched session has not expired
1660                    userSess.credWallet.isValid(raiseExcep=True)
1661                    return userSess
1662                   
1663        except Exception, e:
1664            raise SessionMgrError("User Session: %s" % e)
1665                       
1666
1667
1668        # User session not found
1669        raise SessionMgrError("No user session found with ID: " + sessID)
1670
1671
1672
1673
1674    def reqAuthorisation(self,
1675                         userName=None,
1676                         passPhrase=None,
1677                         sessID=None,
1678                         **reqAuthorisationKeys):
1679        """For given sessID, request authorisation from an Attribute Authority
1680        given by aaWSDL.  If sucessful, an attribute certificate is
1681        returned.
1682
1683        userName, passPhrase:   set username and pass-phrase to create a new
1684                                user session
1685        sessID:                 give the browser session ID corresponding to
1686                                an existing session
1687        **reqAuthorisationKeys: keywords used by CredWallet.reqAuthorisation
1688        """
1689
1690        # Connection keys will make a new session ID or retrieve an existing
1691        # one
1692        userSess = self.connect(userName=userName,
1693                                passPhrase=passPhrase,
1694                                sessID=sessID)
1695
1696        # Session's wallet requests authorisation
1697        try:
1698            attCert = userSess.credWallet.reqAuthorisation(\
1699                                                    **reqAuthorisationKeys)       
1700            return attCert
1701           
1702        except CredWalletAuthorisationDenied, e:
1703            # Raise exception containing list of attribute certificates
1704            # which could be used to re-try to get authorisation via a mapped
1705            # certificate
1706            raise SessionMgrAuthorisationDenied(str(e), e['extAttCertList'])
1707       
1708        except Exception, e:
1709            raise e
1710   
1711
1712
1713
1714    def getUserCredentials(self, **connectKeys):
1715        """Return the Attribute Certificates held by a particular user
1716
1717        **connectKeys:  userName, passPhrase|sessID for UserSession"""
1718        return self.connect(**connectKeys)['credentials']
1719
1720
1721
1722   
1723    def auditCredRepos(self):
1724        """Remove expired Attribute Certificates from the Credential
1725        Repository"""
1726        self.__credRepos.auditCredentials()
1727
1728
1729
1730       
1731def reqAuthorisationTest(userName, passPhrase=None, passPhraseFilePath='tmp'):
1732
1733    import pdb
1734    pdb.set_trace()
1735
1736    try:
1737        if passPhrase is None:
1738            passPhrase = open(passPhraseFilePath).read().strip()
1739           
1740        # Start session manager
1741        sessMgr = SessionMgr("./sessionMgrProperties.xml")
1742
1743        # Create a new session
1744        userSess = sessMgr.connect(userName, passPhrase)
1745
1746        # Request authorisation from a data centre
1747        return sessMgr.reqAuthorisation(\
1748                            aaWSDL='./attAuthority.wsdl', 
1749                            #aaPropFilePath='./attAuthorityProperties.xml',
1750                            sessID=userSess['sessID'][0])
1751
1752    except Exception, e:
1753        print str(e)
1754       
1755
1756
1757
1758def addUserTest(userName,
1759                userPassPhrase,
1760                caConfigFilePath="tmp.txt",
1761                caPassPhrase=None):
1762
1763    import pdb
1764    pdb.set_trace()
1765
1766    try:
1767        # Add a new user using the session manager
1768        sessMgr = SessionMgr("./sessionMgrProperties.xml")
1769        sessMgr.addUser(userName,
1770                        userPassPhrase,
1771                        caConfigFilePath=caConfigFilePath)
1772       
1773    except Exception, e:
1774        print str(e)
Note: See TracBrowser for help on using the repository browser.