source: TI12-security/trunk/python/NDG/CredWallet.py @ 674

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

* Working version for mapped certificates *

ndgSessionClient.py:

  • renamed dict to argDict to avoid clash with existing arg var.
  • added code for handling ext attCert list and trusted host list files.
  • Changed so that --connect and --req-autho can be specified together so that a connect call is

concatenated with a call to request authorisation.

attAuthorityIOtest.py: unit tests for AttAuthorityIO classes.
attCertTest.py: unit tests for AttCert?.

AttAuthorityIO.py:

attAuthority_services_server.py: update and fixes to GetTrustedHostInfo? WS stub.

AttAuthority?.py:

AttCert?.py: !! important fix - added nonzero method so that test on AttCert? instance
yields True e.g. if attCert: ...

Session.py:

  • SessionMgr?.readProperties - only strip white space from XML data if it's string type -

elementtree sets content to None if an empty tag is present in the XML it's reading.

CredWallet?.py: changes to WS calls - use AttAuthorityIO classes - AuthorisationReq/?
AuthorisationResp? + TrustedHostInfoReq/TrustedHostInfoResp?.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1"""NDG Credentials Wallet
2
3NERC Data Grid Project
4
5P J Kershaw 30/11/05
6
7Copyright (C) 2005 CCLRC & NERC
8
9This software may be distributed under the terms of the Q Public License,
10version 1.0 or later.
11"""
12
13cvsID = '$Id$'
14
15
16# Temporary store of certificates for use with CredWallet reqAuthorisation()
17import tempfile
18
19# Keyword formatting/XML message creation for Attribute Authority WS
20from AttAuthorityIO import *
21
22# Access Attribute Authority's web service using ZSI - allow pass if not loaded
23# since it's possible to make AttAuthority instance locally without using
24# the WS
25aaImportError = True
26try:
27    from ZSI import ServiceProxy
28    import socket # handle socket errors from WS
29    aaImportError = False
30   
31except ImportError:
32    pass
33
34# Likewise - may want to use WS and not use AttAuthority locally in which case
35# no need to import it
36try:
37    from AttAuthority import *
38    aaImportError = False
39except:
40    pass
41
42if aaImportError:
43    raise ImportError("Either AttAuthority or ZSI modules must be " + \
44                      "present to allow interoperation with Attribute " +\
45                      "Authorities")
46
47# Authentication X.509 Certificate
48from X509 import *
49
50# Authorisation - attribute certificate
51from AttCert import *
52
53
54#_____________________________________________________________________________
55class CredWalletError(Exception):   
56    """Exception handling for NDG CredentialWallet class."""
57   
58    def __init__(self, msg):
59        self.__msg = msg
60         
61    def __str__(self):
62        return self.__msg
63
64
65
66
67#_____________________________________________________________________________
68class CredWalletAuthorisationDenied(Exception):   
69    """Handling exception where CredWallet is denied authorisation by an
70    Attribute Authority."""
71   
72    def __init__(self, msg=None, extAttCertList=[], trustedHostInfo={}):
73        """Raise exception for authorisation denied with option to give
74        caller hint to certificates that could used to try to obtain a
75        mapped certificate
76       
77        msg:                error message
78        extAttCertList:     list of candidate Attribute Certificates that
79                            could be used to try to get a mapped certificate
80                            from the target Attribute Authority
81        trustedHostInfo:    dictionary indexed by host name giving details
82                            of WSDL URI and roles for trusted hosts"""
83
84        self.__msg = msg
85        self.__trustedHostInfo = trustedHostInfo
86        self.__extAttCertList = extAttCertList
87
88       
89    def __str__(self):
90        return self.__msg
91
92
93    def __getMsg(self):
94        """Get message text"""
95        return self.__msg
96
97    msg = property(fget=__getMsg, doc="Error message text")
98
99
100    def __getTrustedHostInfo(self):
101        """Get message text"""
102        return self.__msg
103
104    trustedHostInfo = property(fget=__getTrustedHostInfo, 
105                               doc="WSDL and roles details for trusted hosts")
106   
107   
108    def __getExtAttCertList(self):
109        """Return list of candidate Attribute Certificates that could be used
110        to try to get a mapped certificate from the target Attribute Authority
111        """
112        return self.__extAttCertList
113
114
115    extAttCertList = property(fget=__getExtAttCertList,
116                              doc="list of candidate Attribute " + \
117                              "Certificates that could be used " + \
118                              "to try to get a mapped certificate " + \
119                              "from the target Attribute Authority")
120
121
122
123#_____________________________________________________________________________       
124# CredWallet is a 'new-style' class inheriting from "object" and making use
125# of new Get/Set methods for hiding of attributes
126class CredWallet(object):
127    """Volatile store of user credentials associated with a user session"""
128
129    def __init__(self,
130                 proxyCertTxt,
131                 caCertFilePath=None,
132                 clntCertFilePath=None,
133                 credRepos=None,
134                 mapFromTrustedHosts=False,
135                 rtnExtAttCertList=True):
136        """Create store of user credentials for their current session
137
138        proxy certificate:      users proxy certificate as string text
139        caCertFilePath:         Certificate Authority's certificate - used in
140                                validation of signed Attribute Certificates.
141                                If not set here, it must be input in call
142                                to reqAuthorisation
143        clntCertFilePath:       Public key certificate for this client.
144                                Setting this enables return message from AA
145                                WSDL to be encrypted by the AA.
146        credRepos:              Credential Repository instance
147        mapFromTrustedHosts:   sets behaviour for reqAuthorisation().  If
148                                set True and authorisation fails with the
149                                given Attribute Authority, attempt to get
150                                authorisation using Attribute Certificates
151                                issued by other trusted AAs
152        rtnExtAttCertList:     behaviour for reqAuthorisation().  If True,
153                                and authorisation fails with the given
154                                Attribute Authority, return a list of
155                                Attribute Certificates from other trusted AAs
156                                which could be used to obtain a mapped
157                                Attribute Certificate on a subsequent
158                                authorisation attempt"""
159
160
161        # Makes implicit call to __setProxyCert - Checks the proxy certificate
162        # and make an NDG.X509Cert instance
163        self.proxyCertTxt = proxyCertTxt
164       
165        if caCertFilePath:
166            self.__setCAcertFilePath(caCertFilePath)
167        else:
168            self.__caCertFilePath = None
169           
170        if clntCertFilePath:
171            self.__setClntCertFilePath(clntCertFilePath)
172        else:
173            self.__clntCertFilePath = None
174                 
175        self.__credRepos = credRepos
176       
177        # Set behaviour for authorisation requests
178        self.__mapFromTrustedHosts = mapFromTrustedHosts
179        self.__rtnExtAttCertList = rtnExtAttCertList
180       
181       
182        # Get the distinguished name from the proxy certificate
183        self.__dn = self.__proxyCert.dn.serialise()
184       
185       
186        # Credentials are stored as a dictionary one element per attribute
187        # certicate held and indexed by certificate issuer name
188        self.__credentials = {}
189
190
191        # Make a connection to the Credentials Repository
192        if self.__credRepos:
193            if not isinstance(self.__credRepos, CredRepos):
194                raise CredWalletError(\
195                    "Input Credentials Repository instance must be of a "+\
196                    "class derived from \"CredRepos\"")
197   
198       
199            # Check for valid attribute certificates for the user
200            try:
201                userCred = self.__credRepos.getCredentials(self.__dn)
202   
203            except Exception, e:
204                raise CredWalletError(
205                "Error updating wallet with credentials from repository: " + \
206                    str(e))
207   
208   
209            # Update wallet with attribute certificates stored in the repository
210            # Store ID and certificate instantiated as an AttCert type
211            try:
212                for cred in userCred:
213                   
214                    attCert = AttCertParse(cred.attCert)
215                    issuerName = attCert['issuerName']
216                   
217                    self.__credentials[issuerName] = \
218                                             {'id':cred.id, 'attCert':attCert}
219            except Exception, e:
220                try:
221                    raise CredWalletError(
222                            "Error parsing Attribute Certificate ID '" + \
223                                    cred.id + "' retrieved from the " + \
224                                    "Credentials Repository: %s" % str(e))               
225                except:
226                    raise CredWalletError("Error parsing Attribute " + \
227                                          "Certificate retrieved from " + \
228                                          "the Credentials Repository: %s:" \
229                                          % e)
230           
231           
232            # Filter out expired or otherwise invalid certificates
233            self.audit()
234       
235
236
237
238    def __str__(self):
239        return "<Credential Wallet instance>"
240
241    #_________________________________________________________________________   
242    def __setProxyCert(self, proxyCertTxt):
243        """Set a new proxy certificate for the wallet
244
245        proxyCertTxt: input certificate as a string"""
246       
247        try:
248            if not isinstance(proxyCertTxt, basestring):
249                raise CredWalletError(\
250                                "Proxy Certificate must be input as a string")
251        except Exception, e:
252            raise CredWalletError("Input proxy certificate: %s" % e)
253
254        self.__proxyCertTxt = proxyCertTxt
255        self.__proxyCert = X509Cert()
256        self.__proxyCert.parse(proxyCertTxt)
257   
258
259    #_________________________________________________________________________
260    # Set Proxy Certificate text also updates the proxyCert X509Cert
261    # instance
262    def __setProxyCertTxt(self, value):
263        """Set proxy cert string and from it update equivalent X509Cert
264        instance"""
265        self.__setProxyCert(value)
266
267           
268    def __getProxyCertTxt(self):
269        """Get proxy cert as a string"""
270        return self.__proxyCertTxt
271 
272       
273    def __delProxyCertTxt(self):
274        """Prevent deletion of proxy cert string"""
275        raise AttributeError("\"proxyCertTxt\" cannot be deleted")
276
277 
278    # Publish attribute as read/write
279    proxyCertTxt = property(fget=__getProxyCertTxt,
280                            fset=__setProxyCertTxt,
281                            fdel=__delProxyCertTxt,
282                            doc="String text of proxy certificate")
283
284    def __getProxyCert(self):
285        """Get proxy cert X509Cert instance"""
286        return self.__proxyCert
287
288
289    # Proxy Cert instance is read-only - to set it, set proxyCertTxt
290    proxyCert = property(fget=__getProxyCert,
291                         doc="X.509 proxy certificate instance")
292   
293   
294    #_________________________________________________________________________
295    # Credentials are read-only
296    def __getCredentials(self):
297        return self.__credentials
298
299    # Publish attribute
300    credentials = property(fget=__getCredentials,
301                           doc="List of Attribute Certificates")   
302
303
304    #_________________________________________________________________________
305    def __setCAcertFilePath(self, caCertFilePath):
306       
307        if not isinstance(caCertFilePath, basestring):
308            raise CredWalletError(\
309                "Input CA Certificate file path is not valid")
310               
311        self.__caCertFilePath = caCertFilePath
312       
313       
314    caCertFilePath = property(fset=__setCAcertFilePath,
315                              doc="CA Certificate  - use to check AC XML Sig")
316
317
318    #_________________________________________________________________________
319    def __setClntCertFilePath(self, clntCertFilePath):
320       
321        if not isinstance(clntCertFilePath, basestring):
322            raise CredWalletError(\
323                "Input Client Certificate file path is not valid")
324               
325        self.__clntCertFilePath = clntCertFilePath
326       
327       
328    clntCertFilePath = property(fset=__setClntCertFilePath,
329                    doc="Client Certificate  - use to encrypt resp from AA")
330
331
332
333    def isValid(self, **x509CertKeys):
334        """Check wallet's proxy cert.  If expired return False"""
335        try:
336            return self.__proxyCert.isValidTime(**x509CertKeys)
337
338        except Exception, e:
339            raise CredWalletError("Credential Wallet: %s" % e)
340
341
342   
343    def addCredential(self, attCert, bUpdateCredRepos=True):
344        """Add a new attribute certificate to the list of credentials held.
345        Return True if certificate was added otherwise False.  - If an
346        existing certificate from the same issuer has a later expiry it will
347        take precence and the new input certificate is ignored.
348
349        attCert:            new attribute Certificate to be added
350        bUpdateCredRepos:   if set to True, and a repository exisits it will
351                            be updated with the new credentials also"""
352
353        # Check input
354        try:
355            if not isinstance(attCert, AttCert):
356                raise CredWalletError(\
357                    "Attribute Certificate must be an AttCert type object")
358                   
359        except Exception, e:
360            raise CredWalletError("Attribute Certificate input: %s" % e)
361
362
363        # Check certificate validity
364        try:
365            attCert.isValid(raiseExcep=True)
366           
367        except AttCertError, e:
368            raise CredWalletError("Adding Credential: %s" % e)
369       
370
371        # Check to see if there is an existing Attribute Certificate held
372        # that was issued by the same host.  If so, compare the expiry time.
373        # The one with the latest expiry will be retained and the other
374        # ingored
375        bUpdateCred = True
376        issuerName = attCert['issuerName']
377       
378        if issuerName in self.__credentials:
379            # There is an existing certificate held with the same issuing
380            # host name as the new certificate
381            attCertOld = self.__credentials[issuerName]['attCert']
382
383            # Get expiry times in datetime format to allow comparison
384            dtAttCertOldNotAfter = attCertOld.getValidityNotAfter(\
385                                                            asDatetime=True)
386            dtAttCertNotAfter = attCert.getValidityNotAfter(asDatetime=True)
387
388            # If the new certificate has an earlier expiry time then ignore it
389            bUpdateCred = dtAttCertNotAfter > dtAttCertOldNotAfter
390
391               
392        if bUpdateCred:
393            # Update: Nb. -1 ID value flags item as new.  Items read in
394            # from the CredentialRepository during creation of the wallet will
395            # have +ve IDs previously allocated by the database
396            self.__credentials[issuerName] = {'id': -1, 'attCert': attCert}
397
398            # Update the Credentials Repository - the permanent store of user
399            # authorisation credentials.  This allows credentials for previous
400            # sessions to be re-instated
401            if self.__credRepos and bUpdateCredRepos:
402                self.updateCredRepos()
403
404        # Flag to caller to indicate whether the input certificate was added
405        # to the credentials or an exsiting certificate from the same issuer
406        # took precedence
407        return bUpdateCred
408           
409
410
411    def audit(self):
412        """Check the credentials held in the wallet removing any that have
413        expired or are otherwise invalid."""
414
415        # Nb. No signature check is carried out.  To do a check, access is
416        # needed to the cert of the CA that issued the Attribute Authority's
417        # cert
418        #
419        # P J Kershaw 12/09/05
420        for key, val in self.__credentials.items():
421            if not val['attCert'].isValid(chkSig=False):
422                del self.__credentials[key]
423
424
425
426               
427    def updateCredRepos(self, auditCred=True):
428        """Copy over non-persistent credentials held by wallet into the
429        perminent repository."""
430
431        if not self.__credRepos:
432            raise CredWalletError(
433                  "No Credential Repository has been created for this wallet")
434                           
435        # Filter out invalid certs unless auditCred flag is explicitly set to
436        # false
437        if auditCred: self.audit()
438
439        # Update the database - only add new entries i.e. with an ID of -1
440        attCertList = [i['attCert'] for i in self.__credentials.values() \
441                        if i['id'] == -1]
442
443        self.__credRepos.addCredentials(self.__dn, attCertList)
444
445       
446    def __reqAuthorisation(self,
447                           aaPropFilePath=None,
448                           aaWSDL=None,
449                           aaCertFilePath=None,
450                           extAttCert=None,
451                           bDebug=False):
452       
453        """Wrapper to Attribute Authority authorisation request.  See
454        reqAuthorisation for the classes' public interface.
455
456        To call the Attribute Authority as a Web Service, specify a WSDL
457        otherwise set the properties file path.
458       
459        If successful, a new attribute certificate is issued to the user
460        and added into the wallet
461
462        aaWSDL|aaPropFilePath:  to call as a web service, specify the file
463                                path or URI for the Attribute Authority's
464                                WSDL.  Otherwise, to run on the local machine,
465                                specify a local Attribute Authority
466                                configuration file.
467 
468        aaCertFilePath:         Public key certificate for Attribute
469                                Authority.  Pass this to enable message level
470                                encryption of outgoing message to AA WS.
471                                Applies only where aaWSDL is set.  If omitted,
472                                outgoing message is not enrypted.  In this
473                                case SSL could be used instead to encrypt the
474                                message.
475        extAttCert:             an existing Attribute Certificate which can be
476                                used to making a mapping should the user not
477                                be registered with the Attribute Authority"""
478
479        if extAttCert is not None:
480            if not isinstance(extAttCert, AttCert):
481                raise CredWalletError(\
482                    "Input Attribute Certificate must be AttCert type")
483           
484        if aaWSDL is not None:
485            if not isinstance(aaWSDL, basestring):
486                raise CredWalletError("Attribute Authority WSDL file " + \
487                                      "path must be a valid string")
488
489            if self.__clntCertFilePath:
490                clntCertTxt = \
491                    X509Cert(filePath=self.__clntCertFilePath).asString()
492            else:
493                clntCertTxt = None
494               
495               
496            try:               
497                # Get Attribute Authority web service interface
498                if bDebug:
499                    traceFile = sys.stderr
500                else:
501                    traceFile = None
502                   
503                aaSrv = ServiceProxy(aaWSDL,
504                                     use_wsdl=True,
505                                     tracefile=traceFile)
506               
507                # Format XML request message
508                #
509                # Message will be encrypted if aaCertFilePath was set
510                authorisationReq = AuthorisationReq(\
511                                            proxyCert=self.__proxyCertTxt,
512                                            userAttCert=extAttCert,
513                                            clntCert=clntCertTxt,
514                                            encrPubKeyFilePath=aaCertFilePath)
515                             
516                # Call Attribute Authority's Web service
517                resp = aaSrv.reqAuthorisation(\
518                                         authorisationReq=authorisationReq())
519
520            except socket.error, (dummy, e):
521                raise CredWalletError("Requesting authorisation: %s" % str(e))
522               
523            except Exception, e:
524                raise CredWalletError("Requesting authorisation: %s" % e)
525
526
527            # Parse the response
528            authorisationResp = AuthorisationResp(\
529                                    xmlTxt=str(resp['authorisationResp']))
530                                   
531            # Check the status code returned from the authorisation request
532            if authorisationResp['statCode'] == authorisationResp.accessError:
533                raise CredWalletError(authorisationResp['errMsg'])
534           
535            elif authorisationResp['statCode'] == \
536                                            authorisationResp.accessDenied:
537                raise CredWalletAuthorisationDenied(\
538                    "Authorisation denied: %s" % authorisationResp['errMsg'])
539
540            elif authorisationResp['statCode'] == \
541                                            authorisationResp.accessGranted:
542                attCert = authorisationResp['credential']
543
544            else:
545                raise CredWalletError("Attribute Authority authorisation " + \
546                                      "status code not recognised")
547           
548        elif aaPropFilePath is not None:
549
550            # Call local based Attribute Authority with settings from the
551            # configuration file aaPropFilePath
552
553            if not isinstance(aaPropFilePath, basestring):
554                raise CredWalletError("Attribute Authority Configuration " + \
555                                      "file path must be a valid string")
556                                   
557            try:
558                # Make a new attribute authority instance
559                aa = AttAuthority(aaPropFilePath)
560
561                # Request a new attribute certificate from the Attribute
562                # Authority
563                attCert = aa.authorise(proxyCert=self.__proxyCertTxt,
564                                       userAttCert=extAttCert)
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        # Update attribute Certificate instance with CA's certificate ready
580        # for signature check in addCredential()
581        if self.__caCertFilePath is None:
582            raise CredWalletError("No CA certificate has been set")
583       
584        attCert.certFilePathList = self.__caCertFilePath
585
586       
587        # Add credential into wallet
588        #
589        # Nb. if the certificates signature is invalid, it will be rejected
590        self.addCredential(attCert)
591
592
593        return attCert
594
595
596
597
598    def getAATrustedHostInfo(self,
599                             userRole,
600                             aaWSDL=None,
601                             aaPropFilePath=None):
602        """Wrapper to Attribute Authority getTrustedHostInfo
603       
604        userRole:               get hosts which have a mapping to this role
605        aaWSDL|aaPropFilePath:  to call as a web service, specify the file
606                                path or URI for the Attribute Authority's
607                                WSDL.  Otherwise, to run on the local machine,
608                                specify a local Attribute Authority
609                                configuration file."""
610
611        if not isinstance(userRole, basestring) or not userRole:
612            raise CredWalletError("User Role must be a valid string")
613
614       
615        if aaWSDL is not None:
616
617            if not isinstance(aaWSDL, basestring):
618                raise CredWalletError("Attribute Authority WSDL file " + \
619                                      "path must be a valid string")
620
621            try:               
622                # Get Attribute Authority web service interface
623                aaSrv = ServiceProxy(aaWSDL, use_wsdl=True)
624               
625                # Format request
626                trustedHostInfoReq = TrustedHostInfoReq(role=userRole)
627               
628                # Call Attribute Authority's Web service
629                resp = aaSrv.getTrustedHostInfo(\
630                                    trustedHostInfoReq=trustedHostInfoReq())
631                                   
632                # Parse response
633                trustedHostInfoResp = TrustedHostInfoResp(\
634                                    xmlTxt=str(resp['trustedHostInfoResp']))
635                if 'errMsg' in trustedHostInfoResp and \
636                   trustedHostInfoResp['errMsg']:
637                    raise Exception(trustedHostInfoResp['errMsg'])
638
639                return trustedHostInfoResp['trustedHosts']
640           
641            except socket.error, e:
642                raise CredWalletError("Requesting trusted host info: %s" % \
643                                      e[1])               
644            except Exception, e:
645                raise CredWalletError("Requesting trusted host info: %s" % e)
646
647           
648        elif aaPropFilePath is not None:
649
650            # Call local based Attribute Authority with settings from the
651            # configuration file aaPropFilePath
652
653            if not instance(aaWSDL, basestring):
654                raise CredWalletError("Attribute Authority Configuration " + \
655                                      "file path must be a valid string")
656                                   
657            try:
658                # Make a new attribute authority instance
659                aa = AttAuthority(aaPropFilePath)
660
661                # Request a new attribute certificate from the Attribute
662                # Authority
663                return aa.getTrustedHosts(userRole)
664               
665            except Exception, e:
666                raise CredWalletError("Requesting trusted host info: %s" % e)
667
668        else:
669            raise CredWalletError("Error requesting trusted hosts info: " + \
670                                  "a WSDL file or Attribute Authority " + \
671                                  "configuration file must be specified")
672
673
674    #_________________________________________________________________________
675    def reqAuthorisation(self,
676                         reqRole=None,
677                         aaPropFilePath=None,
678                         aaWSDL=None,
679                         aaCertFilePath=None,
680                         clntCertFilePath=None,
681                         caCertFilePath=None,
682                         mapFromTrustedHosts=None,
683                         rtnExtAttCertList=None,
684                         extAttCertList=None,
685                         extTrustedHostList=None):
686       
687        """For a given role, get authorisation from an Attribute Authority
688        using a user's proxy certificate.  If this fails try to make a mapped
689        Attribute Certificate by using a certificate from another host which
690        has a trust relationship to the Attribute Authority in question.
691
692        reqRole:                the required role to get access for
693        aaWSDL|aaPropFilePath:  to call as a web service, specify the file
694                                path or URI for the Attribute Authority's
695                                WSDL.  Otherwise, to run on the local machine,
696                                specify a local Attribute Authority
697                                configuration file.
698
699        aaCertFilePath:         Public key certificate for Attribute
700                                Authority.  Pass this to enable message level
701                                encryption of outgoing message to AA WS.
702                                Applies only where aaWSDL is set.  If omitted,
703                                outgoing message is not enrypted.  In this
704                                case SSL could be used instead to encrypt the
705                                message.
706                               
707        clntCertFilePath:       Public key certificate for this client.
708                                Setting this enables return message from AA
709                                WSDL to be encrypted by the AA.
710
711        caCertFilePath:         Certificate Authority's certificate used to
712                                validate the signature of any Attribute
713                                Certificate returned from the Attribute
714                                Authority
715
716        mapFromTrustedHosts:    if authorisation fails via the user's proxy
717                                certificate, then it is possible to get a
718                                mapped certificate by using certificates from
719                                other AA's.  Set this flag to True, to allow
720                                this second stage of generating a mapped
721                                certificate from the certificate stored in the
722                                wallet credentials.
723
724                                If set to False, it is possible to return the
725                                list of certificates available for mapping and
726                                then choose which one or ones to use for
727                                mapping by re-calling reqAuthorisation with
728                                extAttCertList set to these certificates
729
730                                The list is returned via
731                                CredWalletAuthorisationDenied exception
732
733                                If no value is set, the default value held
734                                in self.__mapFromTrustedHosts is used
735
736        rtnExtAttCertList:      If authorisation fails, make a list of
737                                candidate certificates from other Attribute
738                                Authorities which the user could use to retry
739                                and get a mapped certificate.
740                               
741                                If mapFromTrustedHosts is set True this flags
742                                value is overriden and effectively set to
743                                True.
744
745                                If no value is set, the default value held
746                                in self.__rtnExtAttCertList is used
747                               
748                                The list is returned via a
749                                CredWalletAuthorisationDenied exception object
750                               
751        extAttCertList:         Attribute Certificate or list of certificates
752                                from other Attribute Authorities.  These can
753                                be used to get a mapped certificate if access
754                                fails based on the user's proxy certificate
755                                credentials.  They are tried out in turn until
756                                access is granted so the order of the list
757                                decides the order in which they will be tried
758
759        extTrustedHostList:     same as extAttCertList keyword, but instead
760                                providing Attribute Certificates, give a list
761                                of Attribute Authority hosts.  These will be
762                                matched up to Attribute Certificates held in
763                                the wallet.  Matching certificates will then
764                                be used to try to get mapped authorisation.
765                               
766        The procedure is:
767
768        1) Try authorisation using proxy certificate
769        2) If the Attribute Authority (AA) doesn't recognise the certificate,
770        find out any other hosts which have a trust relationship to the AA.
771        3) Look for Attribute Certificates held in the wallet corresponding
772        to these hosts.
773        4) If no Attribute Certificates are available, call the relevant
774        hosts' AAs to get certificates
775        5) Finally, use these new certificates to try to obtain a mapped
776        certificate from the original AA
777        6) If this fails access is denied"""
778
779
780        if caCertFilePath is not None:
781            self.caCertFilePath = caCertFilePath
782           
783        if clntCertFilePath is not None:
784            self.clntCertFilePath = clntCertFilePath
785
786           
787        # Check for settings from input, if not set use previous settings
788        # made
789        if mapFromTrustedHosts is not None:
790            self.__mapFromTrustedHosts = mapFromTrustedHosts
791
792        if rtnExtAttCertList is not None:
793            self.__rtnExtAttCertList = rtnExtAttCertList
794
795
796        # Check for list of external trusted hosts (other trusted NDG data
797        # centres)
798        if extTrustedHostList:
799            if not self.__mapFromTrustedHosts:
800                raise CredWalletError("A list of trusted hosts has been " + \
801                                      "input but mapping from trusted " + \
802                                      "hosts is set to disallowed")
803           
804            if isinstance(extTrustedHostList, basestring):
805                extTrustedHostList = [extTrustedHostList]
806
807            # Nb. Any extAttCertList is overriden by extTrustedHostList being
808            # set
809            extAttCertList = []
810            for hostName in extTrustedHostList:
811
812                if hostName in self.__credentials:
813                    extAttCertList.append(\
814                                    self.__credentials[hostName]['attCert'])
815
816
817        # Repeat authorisation attempts until succeed or means are exhausted       
818        while True:
819           
820            # Check for candidate certificates for mapping
821            try:
822                # If list is set get the next cert
823                extAttCert = extAttCertList.pop()
824
825            except AttributeError:
826               
827                # No List set - attempt authorisation without
828                # using mapping from trusted hosts
829                extAttCert = None
830                               
831            except IndexError:
832               
833                # List has been emptied without authorisation succeeding -
834                # give up
835                raise CredWalletAuthorisationDenied(\
836                    "Attempting to obtained a mapped certificate: " + \
837                    "no external attribute certificates are available")
838
839
840            # Request Authorisation from Attribute Authority
841            try:
842                attCert = self.__reqAuthorisation(aaWSDL=aaWSDL,
843                                                aaPropFilePath=aaPropFilePath,
844                                                aaCertFilePath=aaCertFilePath,
845                                                extAttCert=extAttCert)               
846                # Access granted
847                return attCert
848           
849            except CredWalletAuthorisationDenied, authorisationDenied:
850
851                # If a required role was set then it's possible to go
852                # to get certificates with mapped roles from trusted hosts
853                if not reqRole:
854                    raise CredWalletAuthorisationDenied(\
855                        "No user role was input in order to map to " + \
856                        "a role in a trusted host")
857
858
859                #  Use the input required role and the AA's trusted host list
860                # to identify attribute certificates from other hosts which
861                # could be used to make a mapped certificate
862                try:
863                    trustedHostInfo = self.getAATrustedHostInfo(reqRole,
864                                                aaWSDL=aaWSDL,
865                                                aaPropFilePath=aaPropFilePath)
866                except Exception, e:
867                    raise CredWalletError("Getting trusted hosts: %s" % e)
868
869                if not trustedHostInfo:
870                    raise CredWalletAuthorisationDenied(\
871                        "Attribute Authority has no trusted hosts with " + \
872                        "which to make a mapping")
873
874
875                if not mapFromTrustedHosts and not rtnExtAttCertList:
876                    # Creating a mapped certificate is not allowed - raise
877                    # authorisation denied exception saved from earlier
878                    raise authorisationDenied
879
880               
881                # Initialise external certificate list here - if none are
882                # found IndexError will be raised on the next iteration and
883                # an access denied error will be raised
884                extAttCertList = []
885
886                # Look for Attribute Certificates with matching issuer host
887                # names
888                for hostName in self.__credentials:
889
890                    # Nb. Candidate certificates for mappings must have
891                    # original provenance and contain at least one of the
892                    # required roles
893                    attCert = self.__credentials[hostName]['attCert']
894                   
895                    if hostName in trustedHostInfo and attCert.isOriginal():                       
896                        for role in attCert.getRoles():
897                            if role in trustedHostInfo[hostName]['role']:                               
898                                extAttCertList.append(attCert)
899
900
901                if not extAttCertList:
902                    # No certificates in the wallet matched the trusted host
903                    # and required roles
904                    #
905                    # Try each host in turn in order to get a certificate with
906                    # the required credentials in order to do a mapping
907                    for key, val in trustedHostInfo.items():
908
909                        try:
910                            extAttCert = self.__reqAuthorisation(\
911                                                       aaWSDL=val['wsdl'])
912
913                            # Check the certificate contains at least one of
914                            # the required roles
915                            roles = extAttCert.getRoles()
916                            if [True for r in roles if r in val['role']]:
917                               extAttCertList.append(extAttCert)
918
919                               # For efficiency, stop once obtained a valid
920                               # cert - but may want complete list for user to
921                               # choose from
922                               #break
923                               
924                        except Exception, e:
925                            pass    # ignore any errors and continue
926                   
927                if not extAttCertList:                       
928                    raise CredWalletAuthorisationDenied(\
929                        "No certificates are available with which to " + \
930                        "make a mapping to the Attribute Authority")
931
932
933                if not mapFromTrustedHosts:
934                   
935                    # Exit here returning the list of candidate certificates
936                    # that could be used to make a mapped certificate
937                    msg = "User is not registered with Attribute " + \
938                          "Authority - retry using one of the returned " + \
939                          "Attribute Certificates obtained from other " + \
940                          "trusted hosts"
941                         
942                    raise CredWalletAuthorisationDenied(msg=msg,
943                                            extAttCertList=extAttCertList,
944                                            trustedHostInfo=trustedHostInfo)
945               
946            except Exception, authorisationError:
947                # Authorisation request raised an error other than access
948                # denied
949                raise authorisationError
950           
951 
952 
953       
954#_____________________________________________________________________________
955class CredReposError(Exception):   
956    """Exception handling for NDG Credential Repository class."""
957   
958    def __init__(self, msg):
959        self.__msg = msg
960         
961    def __str__(self):
962        return self.__msg
963 
964
965
966
967#_____________________________________________________________________________
968class CredRepos:
969    """CredWallet's interface class to a Credential Repository"""
970   
971
972    def __init__(self, propFilePath=None, dbPPhrase=None, **prop):
973        """Initialise Credential Repository abstract base class derive from
974        this class to define Credentail Repository interface Credential
975        Wallet
976
977        If the connection string or properties file is set a connection
978        will be made
979
980        dbPPhrase:     pass-phrase to database if applicable
981        propFilePath:  file path to a properties file.  This could contain
982                       configuration parameters for the repository e.g.
983                       database connection parameters
984        **prop:        any other keywords required
985        """
986        raise NotImplementedError(\
987            self.__init__.__doc__.replace('\n       ',''))
988
989
990    def addUser(self, userName, dn):
991        """A new user to Credentials Repository"""
992        raise NotImplementedError(
993            self.addUser.__doc__.replace('\n       ',''))
994
995                           
996    def auditCredentials(self, **attCertValidKeys):
997        """Check the attribute certificates held in the repository and delete
998        any that have expired
999
1000        attCertValidKeys:  keywords which set how to check the Attribute
1001                           Certificate e.g. check validity time, XML
1002                           signature, version etc.  Default is check
1003                           validity time only"""
1004        raise NotImplementedError(
1005            self.auditCredentials.__doc__.replace('\n       ',''))
1006
1007
1008    def getCredentials(self, dn):
1009        """Get the list of credentials for a given user's DN"""
1010        raise NotImplementedError(
1011            self.getCredentials.__doc__.replace('\n       ',''))
1012
1013       
1014    def addCredentials(self, dn, attCertList):
1015        """Add new attribute certificates for a user.  The user must have
1016        been previously registered in the repository
1017
1018        dn:            users Distinguished name
1019        attCertList:   list of attribute certificates"""
1020        raise NotImplementedError(
1021            self.addCredentials.__doc__.replace('\n       ',''))
1022
1023
1024
1025           
1026if __name__ == "__main__":
1027    proxyCertTxt = open('../x509up_u25157').read()
1028    credWallet = CredWallet(proxyCertTxt)
Note: See TracBrowser for help on using the repository browser.