source: TI12-security/trunk/python/ndg.security.common/ndg/security/common/CredWallet.py @ 1711

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

SessionMgr_services_server.py: moved to SessionMgr? server package.
ndg.security.server/ndg/security/init.py: doesn't need to be a namespace
package
ndg.security.common/ndg/security/common/AttAuthorityClient.py: separated
AttAuthorityClient?* classes from SecurityClient? to the common package as
the CredWallet? needs it. CredWallet? is itself in common because it's used
by the SessionMgr? in the server package and must also be available in the
client package in case client app writers don't want to use the SessionMgr?
to keep wallet info.
ndg.security.common/ndg/security/common/CredWallet.py: modified import test -
now checks for availability of new AttAuthorityClient? module in common
package.
setup.py, setup.cfg: these are needed for making the egg that installs the
whole of the security system, server, client and common.

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