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

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.common/ndg/security/common/CredWallet.py@2080
Revision 2080, 45.4 KB checked in by pjkersha, 13 years ago (diff)
  • Working unit test: Session Manager Client getAttCert call *

python/ndg.security.server/ndg/security/server/SessionMgr/server-config.tac:
ensure AttCert? response is explicitly converted to a string before dispatch. Previously
signature check was failing for client recipient.

python/ndg.security.common/ndg/security/common/CredWallet.py:

  • fixed - added metaclass declaration to include _MetaCredWallet. This contains some

useful class variables declared as properties.

rather every time a web service call is made via getAttCert, getAttCert and
getAATrustedHostInfo. Likewise, alternative aaPropFilePath property setting actually
instantiates a local Attribute Authority at the same time. Nb. calls can be made to
an AA web service (aaURI property) or AA running locally (pass the file path for its
properties file aaPropFilePath).

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