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

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

AttAuthority?.py: fixed refs userAttCertTxt -> userAttCert in get mapped certificate
block of code - not tested for some time

SessionMgrIO.py, SessionClient?.py, CredWallet?.py:
Renamed keyword/XML tag "setExtAttCertList" -> "rtnExtAttCertList"

Session.py: UserSession?.setCookie - set domain to .rl.ac.uk - will need to change to make
configurable.

  • 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       
447    def __reqAuthorisation(self,
448                           aaPropFilePath=None,
449                           aaWSDL=None,
450                           aaCertFilePath=None,
451                           extAttCert=None,
452                           bDebug=False):
453       
454        """Wrapper to Attribute Authority authorisation request.  See
455        reqAuthorisation for the classes' public interface.
456
457        To call the Attribute Authority as a Web Service, specify a WSDL
458        otherwise set the properties file path.
459       
460        If successful, a new attribute certificate is issued to the user
461        and added into the wallet
462
463        aaWSDL|aaPropFilePath:  to call as a web service, specify the file
464                                path or URI for the Attribute Authority's
465                                WSDL.  Otherwise, to run on the local machine,
466                                specify a local Attribute Authority
467                                configuration file.
468 
469        aaCertFilePath:         Public key certificate for Attribute
470                                Authority.  Pass this to enable message level
471                                encryption of outgoing message to AA WS.
472                                Applies only where aaWSDL is set.  If omitted,
473                                outgoing message is not enrypted.  In this
474                                case SSL could be used instead to encrypt the
475                                message.
476        extAttCert:             an existing Attribute Certificate which can be
477                                used to making a mapping should the user not
478                                be registered with the Attribute Authority"""
479
480        if extAttCert is not None:
481            if not isinstance(extAttCert, AttCert):
482                raise CredWalletError(\
483                    "Input Attribute Certificate must be AttCert type")
484
485            extAttCertTxt = extAttCert.asString()
486        else:
487            extAttCertTxt = '' # None
488
489           
490        if aaWSDL is not None:
491
492            if not isinstance(aaWSDL, basestring):
493                raise CredWalletError("Attribute Authority WSDL file " + \
494                                      "path must be a valid string")
495
496            if self.__clntCertFilePath:
497                clntCertTxt = \
498                    X509Cert(filePath=self.__clntCertFilePath).asString()
499            else:
500                clntCertTxt = None
501               
502               
503            try:               
504                # Get Attribute Authority web service interface
505                if bDebug:
506                    traceFile = sys.stderr
507                else:
508                    traceFile = None
509                   
510                aaSrv = ServiceProxy(aaWSDL,
511                                     use_wsdl=True,
512                                     tracefile=traceFile)
513               
514                # Format XML request message
515                #
516                # Message will be encrypted if aaCertFilePath was set
517                authorisationReq = AuthorisationReq(\
518                                            proxyCert=self.__proxyCertTxt,
519                                            userAttCert=extAttCertTxt,
520                                            clntCert=clntCertTxt,
521                                            encrPubKeyFilePath=aaCertFilePath)
522                             
523                # Call Attribute Authority's Web service
524                resp = aaSrv.reqAuthorisation(\
525                                         authorisationReq=authorisationReq())
526
527            except socket.error, (dummy, e):
528                raise CredWalletError("Requesting authorisation: %s" % str(e))
529               
530            except Exception, e:
531                raise CredWalletError("Requesting authorisation: %s" % e)
532
533
534            # Parse the response
535            authorisationResp = AuthorisationResp(\
536                                    xmlTxt=str(resp['authorisationResp']))
537                                   
538            # Check the status code returned from the authorisation request
539            if authorisationResp['statCode'] == authorisationResp.accessError:
540                raise CredWalletError(authorisationResp['errMsg'])
541           
542            elif authorisationResp['statCode'] == \
543                                            authorisationResp.accessDenied:
544                raise CredWalletAuthorisationDenied(\
545                    "Authorisation denied: %s" % authorisationResp['errMsg'])
546
547            elif authorisationResp['statCode'] == \
548                                            authorisationResp.accessGranted:
549                attCert = authorisationResp['credential']
550
551            else:
552                raise CredWalletError("Attribute Authority authorisation " + \
553                                      "status code not recognised")
554           
555        elif aaPropFilePath is not None:
556
557            # Call local based Attribute Authority with settings from the
558            # configuration file aaPropFilePath
559
560            if not isinstance(aaPropFilePath, basestring):
561                raise CredWalletError("Attribute Authority Configuration " + \
562                                      "file path must be a valid string")
563                                   
564            try:
565                # Make a new attribute authority instance
566                aa = AttAuthority(aaPropFilePath)
567
568                # Request a new attribute certificate from the Attribute
569                # Authority
570                attCert = aa.authorise(proxyCert=self.__proxyCertTxt,
571                                       userAttCertTxt=extAttCertTxt)
572               
573            except AttAuthorityAccessDenied, e:
574                raise CredWalletAuthorisationDenied(\
575                                    "Authorisation denied: %s" % e)
576           
577            except Exception, e:
578                raise CredWalletError("Requesting authorisation: %s" % e)
579
580        else:
581            raise CredWalletError("Error requesting authorisation: " + \
582                                  "a WSDL file or Attribute Authority " + \
583                                  "configuration file must be specified")
584       
585
586        # Update attribute Certificate instance with CA's certificate ready
587        # for signature check in addCredential()
588        if self.__caCertFilePath is None:
589            raise CredWalletError("No CA certificate has been set")
590       
591        attCert.certFilePathList = self.__caCertFilePath
592
593       
594        # Add credential into wallet
595        #
596        # Nb. if the certificates signature is invalid, it will be rejected
597        self.addCredential(attCert)
598
599
600        return attCert
601
602
603
604
605    def getAATrustedHostInfo(self,
606                             userRole,
607                             aaWSDL=None,
608                             aaPropFilePath=None):
609        """Wrapper to Attribute Authority getTrustedHostInfo
610       
611        userRole:               get hosts which have a mpping to this role
612        aaWSDL|aaPropFilePath:  to call as a web service, specify the file
613                                path or URI for the Attribute Authority's
614                                WSDL.  Otherwise, to run on the local machine,
615                                specify a local Attribute Authority
616                                configuration file."""
617
618        if not isinstance(userRole, basestring) or not userRole:
619            raise CredWalletError("User Role must be a valid string")
620
621       
622        if aaWSDL is not None:
623
624            if not isinstance(aaWSDL, basestring):
625                raise CredWalletError("Attribute Authority WSDL file " + \
626                                      "path must be a valid string")
627
628            try:               
629                # Get Attribute Authority web service interface
630                aaSrv = ServiceProxy(aaWSDL, use_wsdl=True)
631               
632                # Call Attribute Authority's Web service
633                resp = aaSrv.getTrustedHostInfo(usrRole=userRole)
634                if resp['errMsg']:
635                    raise Exception(resp['errMsg'])
636
637                # De-serialise output into a dictionary of roles indexed by
638                # host name
639                hostList = []
640                for host in resp['trustedHostInfo']:
641                    hostSplit = re.split("\s*:\s*", str(host))
642                    roleList = re.split("\s*,\s*", hostSplit[2])
643                   
644                    hostList.append((hostSplit[0], \
645                                    {'wsdl': hostSplit[1], 'role': roleList}))
646
647                return dict(hostList)
648           
649            except socket.error, e:
650                raise CredWalletError("Requesting trusted host info: %s" % \
651                                      e[1])               
652            except Exception, e:
653                raise CredWalletError("Requesting trusted host info: %s" % e)
654
655           
656        elif aaPropFilePath is not None:
657
658            # Call local based Attribute Authority with settings from the
659            # configuration file aaPropFilePath
660
661            if not instance(aaWSDL, basestring):
662                raise CredWalletError("Attribute Authority Configuration " + \
663                                      "file path must be a valid string")
664                                   
665            try:
666                # Make a new attribute authority instance
667                aa = AttAuthority(aaPropFilePath)
668
669                # Request a new attribute certificate from the Attribute
670                # Authority
671                return aa.getTrustedHosts(userRole)
672               
673            except Exception, e:
674                raise CredWalletError("Requesting trusted host info: %s" % e)
675
676        else:
677            raise CredWalletError("Error requesting trusted hosts info: " + \
678                                  "a WSDL file or Attribute Authority " + \
679                                  "configuration file must be specified")
680
681
682    #_________________________________________________________________________
683    def reqAuthorisation(self,
684                         reqRole=None,
685                         aaPropFilePath=None,
686                         aaWSDL=None,
687                         aaCertFilePath=None,
688                         clntCertFilePath=None,
689                         caCertFilePath=None,
690                         mapFromTrustedHosts=None,
691                         rtnExtAttCertList=None,
692                         extAttCertList=None,
693                         extTrustedHostList=None):
694       
695        """For a given role, get authorisation from an Attribute Authority
696        using a user's proxy certificate.  If this fails try to make a mapped
697        Attribute Certificate by using a certificate from another host which
698        has a trust relationship to the Attribute Authority in question.
699
700        reqRole:                the required role to get access for
701        aaWSDL|aaPropFilePath:  to call as a web service, specify the file
702                                path or URI for the Attribute Authority's
703                                WSDL.  Otherwise, to run on the local machine,
704                                specify a local Attribute Authority
705                                configuration file.
706
707        aaCertFilePath:         Public key certificate for Attribute
708                                Authority.  Pass this to enable message level
709                                encryption of outgoing message to AA WS.
710                                Applies only where aaWSDL is set.  If omitted,
711                                outgoing message is not enrypted.  In this
712                                case SSL could be used instead to encrypt the
713                                message.
714                               
715        clntCertFilePath:       Public key certificate for this client.
716                                Setting this enables return message from AA
717                                WSDL to be encrypted by the AA.
718
719        caCertFilePath:         Certificate Authority's certificate used to
720                                validate the signature of any Attribute
721                                Certificate returned from the Attribute
722                                Authority
723
724        mapFromTrustedHosts:    if authorisation fails via the user's proxy
725                                certificate, then it is possible to get a
726                                mapped certificate by using certificates from
727                                other AA's.  Set this flag to True, to allow
728                                this second stage of generating a mapped
729                                certificate from the certificate stored in the
730                                wallet credentials.
731
732                                If set to False, it is possible to return the
733                                list of certificates available for mapping and
734                                then choose which one or ones to use for
735                                mapping by re-calling reqAuthorisation with
736                                extAttCertList set to these certificates
737
738                                The list is returned via
739                                CredWalletAuthorisationDenied exception
740
741                                If no value is set, the default value held
742                                in self.__mapFromTrustedHosts is used
743
744        rtnExtAttCertList:      If authorisation fails, make a list of
745                                candidate certificates from other Attribute
746                                Authorities which the user could use to retry
747                                and get a mapped certificate.
748                               
749                                If mapFromTrustedHosts is set True this flags
750                                value is overriden and effectively set to
751                                True.
752
753                                If no value is set, the default value held
754                                in self.__rtnExtAttCertList is used
755                               
756                                The list is returned via a
757                                CredWalletAuthorisationDenied exception object
758                               
759        extAttCertList:         Attribute Certificate or list of certificates
760                                from other Attribute Authorities.  These can
761                                be used to get a mapped certificate if access
762                                fails based on the user's proxy certificate
763                                credentials.  They are tried out in turn until
764                                access is granted so the order of the list
765                                decides the order in which they will be tried
766
767        extTrustedHostList:     same as extAttCertList keyword, but instead
768                                providing Attribute Certificates, give a list
769                                of Attribute Authority hosts.  These will be
770                                matched up to Attribute Certificates held in
771                                the wallet.  Matching certificates will then
772                                be used to try to get mapped authorisation.
773                               
774        The procedure is:
775
776        1) Try authorisation using proxy certificate
777        2) If the Attribute Authority (AA) doesn't recognise the certificate,
778        find out any other hosts which have a trust relationship to the AA.
779        3) Look for Attribute Certificates held in the wallet corresponding
780        to these hosts.
781        4) If no Attribute Certificates are available, call the relevant
782        hosts' AAs to get certificates
783        5) Finally, use these new certificates to try to obtain a mapped
784        certificate from the original AA
785        6) If this fails access is denied"""
786
787
788        if caCertFilePath is not None:
789            self.caCertFilePath = caCertFilePath
790           
791        if clntCertFilePath is not None:
792            self.clntCertFilePath = clntCertFilePath
793
794           
795        # Check for settings from input, if not set use previous settings
796        # made
797        if mapFromTrustedHosts is not None:
798            self.__mapFromTrustedHosts = mapFromTrustedHosts
799
800        if rtnExtAttCertList is not None:
801            self.__rtnExtAttCertList = rtnExtAttCertList
802
803
804        # Check for list of external trusted hosts (other trusted NDG data
805        # centres)
806        if extTrustedHostList:
807            if not self.__mapFromTrustedHosts:
808                raise CredWalletError("A list of trusted hosts has been " + \
809                                      "input but mapping from trusted " + \
810                                      "hosts is set to disallowed")
811           
812            if isinstance(extTrustedHostList, basestring):
813                extTrustedHostList = [extTrustedHostList]
814
815            # Nb. Any extAttCertList is overriden by extTrustedHostList being
816            # set
817            extAttCertList = []
818            for hostName in extTrustedHostList:
819
820                if hostName in self.__credentials:
821                    extAttCertList.append(\
822                                    self.__credentials[hostName]['attCert'])
823
824
825        # Repeat authorisation attempts until succeed or means are exhausted       
826        while True:
827           
828            # Check for candidate certificates for mapping
829            try:
830                # If list is set get the next cert
831                extAttCert = extAttCertList.pop()
832
833            except AttributeError:
834               
835                # No List set - attempt authorisation without
836                # using mapping from trusted hosts
837                extAttCert = None
838                               
839            except IndexError:
840               
841                # List has been emptied without authorisation succeeding -
842                # give up
843                raise CredWalletAuthorisationDenied(\
844                    "Attempting to obtained a mapped certificate: " + \
845                    "no external attribute certificates are available")
846
847
848            # Request Authorisation from Attribute Authority
849            try:
850                attCert = self.__reqAuthorisation(aaWSDL=aaWSDL,
851                                                aaPropFilePath=aaPropFilePath,
852                                                aaCertFilePath=aaCertFilePath,
853                                                extAttCert=extAttCert)               
854                # Access granted
855                return attCert
856           
857            except CredWalletAuthorisationDenied, authorisationDenied:
858
859                # If a required role was set then it's possible to go
860                # to get certificates with mapped roles from trusted hosts
861                if not reqRole:
862                    raise CredWalletAuthorisationDenied(\
863                        "No user role was input in order to map to " + \
864                        "a role in a trusted host")
865
866
867                #  Use the input required role and the AA's trusted host list
868                # to identify attribute certificates from other hosts which
869                # could be used to make a mapped certificate
870                try:
871                    trustedHostInfo = self.getAATrustedHostInfo(reqRole,
872                                                aaWSDL=aaWSDL,
873                                                aaPropFilePath=aaPropFilePath)
874                except Exception, e:
875                    raise CredWalletError("Getting trusted hosts: %s" % e)
876
877                if not trustedHostInfo:
878                    raise CredWalletAuthorisationDenied(\
879                        "Attribute Authority has no trusted hosts with " + \
880                        "which to make a mapping")
881
882
883                if not mapFromTrustedHosts and not rtnExtAttCertList:
884                    # Creating a mapped certificate is not allowed - raise
885                    # authorisation denied exception saved from earlier
886                    raise authorisationDenied
887
888               
889                # Initialise external certificate list here - if none are
890                # found IndexError will be raised on the next iteration and
891                # an access denied error will be raised
892                extAttCertList = []
893
894                # Look for Attribute Certificates with matching issuer host
895                # names
896                for hostName in self.__credentials:
897
898                    # Nb. Candidate certificates for mappings must have
899                    # original provenance and contain at least one of the
900                    # required roles
901                    attCert = self.__credentials[hostName]['attCert']
902                   
903                    if hostName in trustedHostInfo and attCert.isOriginal():                       
904                        for role in attCert.getRoles():
905                            if role in trustedHostInfo[hostName]['role']:                               
906                                extAttCertList.append(attCert)
907
908
909                if not extAttCertList:
910                    # No certificates in the wallet matched the trusted host
911                    # and required roles
912                    #
913                    # Try each host in turn in order to get a certificate with
914                    # the required credentials in order to do a mapping
915                    for key, val in trustedHostInfo.items():
916
917                        try:
918                            extAttCert = self.__reqAuthorisation(\
919                                                       aaWSDL=val['wsdl'])
920
921                            # Check the certificate contains at least one of
922                            # the required roles
923                            roles = extAttCert.getRoles()
924                            if [True for r in roles if r in val['role']]:
925                               extAttCertList.append(extAttCert)
926
927                               # For efficiency, stop once obtained a valid
928                               # cert - but may want complete list for user to
929                               # choose from
930                               #break
931                               
932                        except Exception, e:
933                            pass    # ignore any errors and continue
934                   
935                if not extAttCertList:                       
936                    raise CredWalletAuthorisationDenied(\
937                        "No certificates are available with which to " + \
938                        "make a mapping to the Attribute Authority")
939
940
941                if not mapFromTrustedHosts:
942                   
943                    # Exit here returning the list of candidate certificates
944                    # that could be used to make a mapped certificate
945                    msg = "User is not registered with Attribute " + \
946                          "Authority - retry using one of the returned " + \
947                          "Attribute Certificates obtained from other " + \
948                          "trusted hosts"
949                         
950                    raise CredWalletAuthorisationDenied(msg=msg,
951                                            extAttCertList=extAttCertList,
952                                            trustedHostInfo=trustedHostInfo)
953               
954            except Exception, authorisationError:
955                # Authorisation request raised an error other than access
956                # denied
957                raise authorisationError
958           
959 
960 
961       
962#_____________________________________________________________________________
963class CredReposError(Exception):   
964    """Exception handling for NDG Credential Repository class."""
965   
966    def __init__(self, msg):
967        self.__msg = msg
968         
969    def __str__(self):
970        return self.__msg
971 
972
973
974
975#_____________________________________________________________________________
976class CredRepos:
977    """CredWallet's interface class to a Credential Repository"""
978   
979
980    def __init__(self, propFilePath=None, dbPPhrase=None, **prop):
981        """Initialise Credential Repository abstract base class derive from
982        this class to define Credentail Repository interface Credential
983        Wallet
984
985        If the connection string or properties file is set a connection
986        will be made
987
988        dbPPhrase:     pass-phrase to database if applicable
989        propFilePath:  file path to a properties file.  This could contain
990                       configuration parameters for the repository e.g.
991                       database connection parameters
992        **prop:        any other keywords required
993        """
994        raise NotImplementedError(\
995            self.__init__.__doc__.replace('\n       ',''))
996
997
998    def addUser(self, userName, dn):
999        """A new user to Credentials Repository"""
1000        raise NotImplementedError(
1001            self.addUser.__doc__.replace('\n       ',''))
1002
1003                           
1004    def auditCredentials(self, **attCertValidKeys):
1005        """Check the attribute certificates held in the repository and delete
1006        any that have expired
1007
1008        attCertValidKeys:  keywords which set how to check the Attribute
1009                           Certificate e.g. check validity time, XML
1010                           signature, version etc.  Default is check
1011                           validity time only"""
1012        raise NotImplementedError(
1013            self.auditCredentials.__doc__.replace('\n       ',''))
1014
1015
1016    def getCredentials(self, dn):
1017        """Get the list of credentials for a given user's DN"""
1018        raise NotImplementedError(
1019            self.getCredentials.__doc__.replace('\n       ',''))
1020
1021       
1022    def addCredentials(self, dn, attCertList):
1023        """Add new attribute certificates for a user.  The user must have
1024        been previously registered in the repository
1025
1026        dn:            users Distinguished name
1027        attCertList:   list of attribute certificates"""
1028        raise NotImplementedError(
1029            self.addCredentials.__doc__.replace('\n       ',''))
1030
1031
1032
1033           
1034if __name__ == "__main__":
1035    proxyCertTxt = open('../x509up_u25157').read()
1036    credWallet = CredWallet(proxyCertTxt)
Note: See TracBrowser for help on using the repository browser.