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

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

Refactoring of CredWallet?

  • added tests for getting mapped AC
  • unit tests now complete
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1"""NDG Credentials Wallet
2
3NERC Data Grid Project
4"""
5__author__ = "P J Kershaw"
6__date__ = "30/11/05"
7__copyright__ = "(C) 2008 STFC & NERC"
8__license__ = \
9"""This software may be distributed under the terms of the Q Public
10License, version 1.0 or later."""
11__contact__ = "P.J.Kershaw@rl.ac.uk"
12__revision__ = '$Id$'
13
14import logging
15log = logging.getLogger(__name__)
16
17# Temporary store of certificates for use with CredWallet getAttCert()
18import tempfile
19
20# Check Attribute Certificate validity times
21from datetime import datetime
22from datetime import timedelta
23
24
25# Access Attribute Authority's web service using ZSI - allow pass if not
26# loaded since it's possible to make AttAuthority instance locally without
27# using the WS
28aaImportError = True
29try:
30    # AttAuthority client package resides with CredWallet module in
31    # ndg.security.common
32    from ndg.security.common.AttAuthority import AttAuthorityClient, \
33        AttAuthorityClientError, AttributeRequestDenied, \
34        NoMatchingRoleInTrustedHosts
35    aaImportError = False
36except ImportError:
37    log.warning('Loading CredWallet without SOAP interface imports')
38    pass
39
40# Likewise - may not want to use WS and use AttAuthority locally in which case
41# no need to import it
42try:
43    from ndg.security.server.AttAuthority import AttAuthority, \
44        AttAuthorityError, AttAuthorityAccessDenied
45    aaImportError = False
46except:
47    log.warning('Loading CredWallet without Attribute Authority interface '
48                'imports')
49    pass
50
51if aaImportError:
52    raise ImportError("Either AttAuthority or AttAuthorityClient classes must "
53                      "be present to allow interoperation with Attribute "
54                      "Authorities")
55
56# Authentication X.509 Certificate
57from ndg.security.common.X509 import *
58from M2Crypto import X509, BIO, RSA
59
60# Authorisation - attribute certificate
61from ndg.security.common.AttCert import *
62from ndg.security.common.wssecurity.dom import SignatureHandler
63
64# generic parser to read INI/XML properties file
65from ndg.security.common.utils.ConfigFileParsers import \
66                                                INIPropertyFileWithValidation
67
68
69class _CredWalletException(Exception):   
70    """Generic Exception class for CredWallet module.  Overrides Exception to
71    enable writing to the log"""
72    def __init__(self, msg):
73        log.error(msg)
74        Exception.__init__(self, msg)
75
76
77class CredWalletError(_CredWalletException):   
78    """Exception handling for NDG Credential Wallet class.  Overrides Exception
79    to enable writing to the log"""
80
81
82class CredWalletAttributeRequestDenied(CredWalletError):   
83    """Handling exception where CredWallet is denied authorisation by an
84    Attribute Authority.
85 
86    @type __extAttCertList: list
87    @ivar __extAttCertList: list of candidate Attribute Certificates that
88    could be used to try to get a mapped certificate from the target
89    Attribute Authority
90   
91    @type __trustedHostInfo: dict
92    @ivar __trustedHostInfo: dictionary indexed by host name giving
93    details of Attribute Authority URI and roles for trusted hosts"""
94   
95    def __init__(self, *args, **kw):
96        """Raise exception for attribute request denied with option to give
97        caller hint to certificates that could used to try to obtain a
98        mapped certificate
99       
100        @type extAttCertList: list
101        @param extAttCertList: list of candidate Attribute Certificates that
102        could be used to try to get a mapped certificate from the target
103        Attribute Authority
104       
105        @type trustedHostInfo: dict
106        @param trustedHostInfo: dictionary indexed by host name giving
107        details of Attribute Authority URI and roles for trusted hosts"""
108       
109        self.__trustedHostInfo = kw.pop('trustedHostInfo', {})
110        self.__extAttCertList = kw.pop('extAttCertList', [])
111           
112        CredWalletError.__init__(self, *args, **kw)
113
114    def _getTrustedHostInfo(self):
115        """Get message text"""
116        return self.__trustedHostInfo
117
118    trustedHostInfo = property(fget=_getTrustedHostInfo, 
119                               doc="URI and roles details for trusted hosts")
120       
121    def _getExtAttCertList(self):
122        """Return list of candidate Attribute Certificates that could be used
123        to try to get a mapped certificate from the target Attribute Authority
124        """
125        return self.__extAttCertList
126
127    extAttCertList = property(fget=_getExtAttCertList,
128                              doc="list of candidate Attribute Certificates "
129                              "that could be used to try to get a mapped "
130                              "certificate from the target Attribute "
131                              "Authority")
132
133         
134class _MetaCredWallet(type):
135    """Enable CredWallet to have read only class variables e.g.
136   
137    print CredWallet.accessDenied
138   
139    ... is allowed but,
140   
141    CredWallet.accessDenied = None
142   
143    ... raises - AttributeError: can't set attribute"""
144   
145    def _getAccessDenied(cls):
146        '''accessDenied get method'''
147        return False
148   
149    accessDenied = property(fget=_getAccessDenied)
150   
151    def _getAccessGranted(cls):
152        '''accessGranted get method'''
153        return True
154   
155    accessGranted = property(fget=_getAccessGranted)
156
157
158# CredWallet is a 'new-style' class inheriting from "object" and making use
159# of new Get/Set methods for hiding of attributes
160class CredWallet(object):
161    """Volatile store of user credentials associated with a user session
162   
163    @type userX509Cert: string / M2Crypto.X509.X509 /
164    ndg.security.common.X509.X509Cert
165    @ivar userX509Cert: X.509 certificate for user (property attribute)
166   
167    @type userPriKey: string / M2Crypto.RSA.RSA
168    @ivar userPriKey: private key for user cert (property attribute)
169   
170    @type issuingX509Cert: string / ndg.security.common.X509.X509Cert
171    @ivar issuingX509Cert: X.509 cert for issuer of user cert (property
172    attribute)
173   
174    @type attributeAuthorityURI: string
175    @ivar attributeAuthorityURI: URI of Attribute Authority to make
176    requests to.  Setting this ALSO creates an AttAuthorityClient instance
177    self._attributeAuthorityClnt.  - See attributeAuthorityURI property for
178    details. (property attribute)
179   
180    @type attributeAuthority: ndg.security.server.AttAuthority.AttAuthority
181    @ivar attributeAuthority: Attribute Authority to make requests to. 
182    attributeAuthorityURI takes precedence over this keyword i.e. if an
183    attributeAuthorityURI has been set, then calls are made to the AA web
184    service at this location rather to any self.attributeAuthority running
185    locally. (property attribute)
186   
187    @type caCertFilePathList: string (for single file), list or tuple
188    @ivar caCertFilePathList: Certificate Authority's certificates - used
189    in validation of signed Attribute Certificates and WS-Security
190    signatures of incoming messages.  If not set here, it must
191    be input in call to getAttCert. (property attribute)
192           
193    @type credentialRepository: instance of CredentialRepository derived
194    class
195    @ivar credentialRepository: Credential Repository instance.   (property
196    attribute).  If not set, defaults to NullCredentialRepository type - see
197    class below...
198
199   
200    @type mapFromTrustedHosts: bool
201    @ivar mapFromTrustedHosts sets behaviour for getAttCert().  If
202    set True and authorisation fails with the given Attribute Authority,
203    attempt to get authorisation using Attribute Certificates issued by
204    other trusted AAs. (property attribute)
205   
206    @type rtnExtAttCertList: bool
207    @ivar rtnExtAttCertList: behaviour for getAttCert().  If True, and
208    authorisation fails with the given Attribute Authority, return a list
209    of Attribute Certificates from other trusted AAs which could be used
210    to obtain a mapped Attribute Certificate on a subsequent authorisation
211    attempt. (property attribute)
212   
213    @type attCertRefreshElapse: float / int
214    @ivar attCertRefreshElapse: used by getAttCert to determine
215    whether to replace an existing AC in the cache with a fresh one.  If
216    the existing one has less than attCertRefreshElapse time in seconds
217    left before expiry then replace it. (property attribute)
218   
219    @type wssCfgKw: dict
220    @ivar wssCfgKw: keywords to WS-Security SignatureHandler
221    used for Credential Wallet's SOAP interface to Attribute Authorities.
222    (property attribute)
223           
224    @type _credentialRepository: ndg.security.common.CredentialRepository or
225    derivative
226    @ivar _credentialRepository: reference to Credential Repository object. 
227    An optional non-volatile cache for storage of wallet info which can be
228    later restored. (Don't reference directly - see equivalent property
229    attribute)
230
231    @type _mapFromTrustedHosts: bool
232    @ivar _mapFromTrustedHosts: if true, allow a mapped attribute certificate
233    to obtained in a getAttCert call.  Set false to prevent mappings.
234    (Don't reference directly - see equivalent property attribute)
235
236    @type _rtnExtAttCertList: bool
237    @ivar _rtnExtAttCertList: if true, return a list of external attribute
238    certificates from getAttCert call. (Don't reference directly - see
239    equivalent property attribute)
240
241    @type __dn: ndg.security.common.X509.X500DN
242    @ivar __dn: distinguished name from user certificate.  (Don't reference
243    directly - see equivalent property attribute)
244
245    @type _credentials: dict       
246    @ivar _credentials: Credentials are stored as a dictionary one element per
247    attribute certificate held and indexed by certificate issuer name.
248    (Don't reference directly - see equivalent property attribute)
249
250    @type _caCertFilePathList: basestring, list, tuple or None
251    @ivar _caCertFilePathList: file path(s) to CA certificates.  If None
252    then the input is quietly ignored.  See caCertFilePathList property.
253    (Don't reference directly - see equivalent property attribute)
254
255    @type _userX509Cert: ndg.security.common.X509.X509Cert
256    @ivar _userX509Cert: X.509 user certificate instance.
257    (Don't reference directly - see equivalent property attribute)
258
259    @type _issuingX509Cert: ndg.security.common.X509.X509Cert
260    @ivar _issuingX509Cert: X.509 user certificate instance.
261    (Don't reference directly - see equivalent property attribute)
262 
263    @type _userPriKey: M2Crypto.RSA.RSA
264    @ivar _userPriKey: Private key used to sign outbound message.
265    (Don't reference directly - see equivalent property attribute)
266    """
267
268    __metaclass__ = _MetaCredWallet
269
270    _propDefs = dict(userId=None,
271                     userX509Cert=None,
272                     userX509CertFilePath=None,
273                     userPriKey=None,
274                     userPriKeyFilePath=None,
275                     issuingX509Cert=None,
276                     issuingX509CertFilePath=None,
277                     caCertFilePathList=None,
278                     sslCACertFilePathList=None,
279                     attributeAuthorityURI=None,
280                     attributeAuthority=None,
281                     credentialRepository=None,
282                     mapFromTrustedHosts=False,
283                     rtnExtAttCertList=True,
284                     attCertRefreshElapse=7200,
285                     wssCfgFilePath=None,
286                     wssCfgSection='DEFAULT',
287                     wssCfgKw={})
288   
289    _protectedAttrs = [
290        '_userX509Cert',
291        '_userX509CertFilePath',
292        '_userPriKey',
293        '_userPriKeyFilePath',
294        '_userPriKeyPwd',
295        '_issuingX509Cert',
296        '_issuingX509CertFilePath',
297        '_attributeAuthorityClnt',
298        '_attributeAuthority',
299        '_caCertFilePathList',
300        '_sslCACertFilePathList',
301        '_credentialRepository',
302        '_mapFromTrustedHosts',
303        '_rtnExtAttCertList',
304        '_attCertRefreshElapse',
305        '_cfg',
306        '_credentials',
307        '_dn',
308        '_attributeAuthorityURI'
309    ]
310   
311    __slots__ = _propDefs.keys() + _protectedAttrs
312   
313    def __init__(self, 
314                 cfg=None, 
315                 cfgFileSection='DEFAULT', 
316                 cfgPrefix='', 
317                 wssCfgKw={},
318                 **kw):
319        """Create store of user credentials for their current session
320
321        @type cfg: string / ConfigParser object
322        @param cfg: if a string type, this is interpreted as the file path to
323        a configuration file, otherwise it will be treated as a ConfigParser
324        object
325        @type cfgSection: string
326        @param cfgSection: sets the section name to retrieve config params
327        from
328        @type cfgPrefix: basestring
329        @param cfgPrefix: apply a prefix to all CredWallet config params so
330        that if placed in a file with other parameters they can be
331        distinguished
332        @type cfgKw: dict
333        @param cfgKw: set parameters as key value pairs."""
334
335        log.debug("Calling CredWallet.__init__ ...")
336
337        # Initialise attributes - 1st protected ones
338        attr = {}.fromkeys(CredWallet._protectedAttrs)
339       
340        # ... then properties
341        attr.update(CredWallet._propDefs)
342        for k, v in attr.items():
343            try:
344                setattr(self, k, v)
345            except AttributeError, e:
346                # FIXME: remove this test exception handling code
347                pass
348           
349        # Update attributes from a config file
350        if cfg:
351            self.parseConfig(cfg, section=cfgFileSection, prefix=cfgPrefix)
352
353        # Update attributes from keywords passed
354        for k,v in kw.items():
355            setattr(self, k, v)
356
357        # Get the distinguished name from the user certificate
358        if self._userX509Cert:
359            self._dn = self._userX509Cert.dn.serialise()
360       
361       
362        # Credentials are stored as a dictionary one element per attribute
363        # certicate held and indexed by certificate issuer name
364        self._credentials = {}
365
366
367        # Make a connection to the Credentials Repository
368        if self._credentialRepository is None:
369            log.info('Applying default CredentialRepository %r for user '
370                     '"%s"' % (NullCredentialRepository, self.userId))
371            self._credentialRepository = NullCredentialRepository()
372        else:
373            log.info('Checking CredentialRepository for credentials for user '
374                     '"%s"' % self.userId)
375           
376            if not isinstance(self._credentialRepository,CredentialRepository):
377                raise CredWalletError("Input Credential Repository instance "
378                                      "must be of a class derived from "
379                                      "\"CredentialRepository\"")
380   
381       
382            # Check for valid attribute certificates for the user
383            try:
384                self._credentialRepository.auditCredentials(self.userId)
385                userCred = self._credentialRepository.getCredentials(self.userId)
386   
387            except Exception, e:
388                log.error("Error updating wallet with credentials from "
389                          "repository: %s" % e)
390                raise
391   
392   
393            # Update wallet with attribute certificates stored in the
394            # repository.  Store ID and certificate instantiated as an AttCert
395            # type
396            try:
397                for cred in userCred: 
398                    attCert = AttCertParse(cred.attCert)
399                    issuerName = attCert['issuerName']
400                   
401                    self._credentials[issuerName] = {'id':cred.id, 
402                                                     'attCert':attCert}   
403            except Exception, e:
404                try:
405                    raise CredWalletError("Error parsing Attribute Certificate"
406                                          " ID '%s' retrieved from the " 
407                                          "Credentials Repository: %s" % 
408                                          (cred.id, e))           
409                except:
410                    raise CredWalletError("Error parsing Attribute "
411                                          "Certificate retrieved from the "
412                                          "Credentials Repository: %s:" % e)
413           
414            # Filter out expired or otherwise invalid certificates
415            self.audit()
416
417    def parseConfig(self, cfg, prefix='', section='DEFAULT'):
418        '''Extract parameters from _cfg config object'''
419       
420        if isinstance(cfg, basestring):
421            cfgFilePath = os.path.expandvars(cfg)
422            self._cfg = None
423        else:
424            cfgFilePath = None
425            self._cfg = cfg
426           
427        # Configuration file properties are held together in a dictionary
428        readAndValidate = INIPropertyFileWithValidation()
429        prop = readAndValidate(cfgFilePath,
430                               cfg=self._cfg,
431                               validKeys=CredWallet._propDefs,
432                               prefix=prefix,
433                               sections=(section,))
434       
435        # Keep a copy of config for use by WS-Security SignatureHandler parser
436        if self._cfg is None:
437            self._cfg = readAndValidate.cfg
438       
439        # Copy prop dict into object attributes - __slots__ definition and
440        # property methods will ensure only the correct attributes are set
441        for key, val in prop.items():
442            setattr(self, key, val)
443
444
445    def _getAttCertRefreshElapse(self):
446        return self._attCertRefreshElapse
447   
448    def _setAttCertRefreshElapse(self, val):
449        if isinstance(val, (float, int)):
450            self._attCertRefreshElapse = val
451           
452        elif isinstance(val, basestring):
453            self._attCertRefreshElapse = float(val)
454        else:
455            raise AttributeError("Expecting int, float or string type input "
456                                 "for attCertRefreshElapse")
457           
458    attCertRefreshElapse = property(fget=_getAttCertRefreshElapse, 
459                                    fset=_setAttCertRefreshElapse,
460                                    doc="If an existing one has AC less than "
461                                        "attCertRefreshElapse time in seconds "
462                                        "left before expiry then replace it")
463   
464    def _setX509Cert(self, cert):
465        """filter and convert input cert to signing verifying cert set
466        property methods.  For signingCert, set to None if it is not to be
467        included in the SOAP header.  For verifyingCert, set to None if this
468        cert can be expected to be retrieved from the SOAP header of the
469        message to be verified
470       
471        @type: ndg.security.common.X509.X509Cert / M2Crypto.X509.X509 /
472        string or None
473        @param cert: X.509 certificate. 
474       
475        @rtype ndg.security.common.X509.X509Cert
476        @return X.509 certificate object"""
477       
478        if cert is None or isinstance(cert, X509Cert):
479            # ndg.security.common.X509.X509Cert type / None
480            return cert
481           
482        elif isinstance(cert, X509.X509):
483            # M2Crypto.X509.X509 type
484            return X509Cert(m2CryptoX509=cert)
485           
486        elif isinstance(cert, basestring):
487            return X509CertParse(cert)
488       
489        else:
490            raise AttributeError("X.509 Cert. must be type: "
491                                 "ndg.security.common.X509.X509Cert, "
492                                 "M2Crypto.X509.X509 or a base64 encoded "
493                                 "string")
494
495    def _setUserX509Cert(self, userX509Cert):
496        "Set property method for X.509 user cert."
497        self._userX509Cert = self._setX509Cert(userX509Cert)
498       
499
500    def _getUserX509Cert(self):
501        """Get user cert X509Cert instance"""
502        return self._userX509Cert
503
504    userX509Cert = property(fget=_getUserX509Cert,
505                            fset=_setUserX509Cert,
506                            doc="X.509 user certificate instance")
507 
508    def _setUserX509CertFilePath(self, filePath):
509        "Set user X.509 cert file path property method"
510       
511        if isinstance(filePath, basestring):
512            filePath = os.path.expandvars(filePath)
513            self._userX509Cert = X509CertRead(filePath)
514           
515        elif filePath is not None:
516            raise AttributeError("User X.509 cert. file path must be a valid "
517                                 "string")
518       
519        self._userX509CertFilePath = filePath
520               
521    userX509CertFilePath = property(fset=_setUserX509CertFilePath,
522                                    doc="File path to user X.509 cert.")
523   
524    def _setIssuingX509Cert(self, issuingX509Cert):
525        "Set property method for X.509 user cert."
526        self._issuingX509Cert = self._setX509Cert(issuingX509Cert)
527       
528    def _getIssuingX509Cert(self):
529        """Get user cert X509Cert instance"""
530        return self._issuingX509Cert
531
532    issuingX509Cert = property(fget=_getIssuingX509Cert,
533                               fset=_setIssuingX509Cert,
534                               doc="X.509 user certificate instance")
535 
536    def _setIssuerX509CertFilePath(self, filePath):
537        "Set user X.509 cert file path property method"
538       
539        if isinstance(filePath, basestring):
540            filePath = os.path.expandvars(filePath)
541            self._issuerX509Cert = X509CertRead(filePath)
542           
543        elif filePath is not None:
544            raise AttributeError("User X.509 cert. file path must be a valid "
545                                 "string")
546       
547        self._issuerX509CertFilePath = filePath
548               
549    issuerX509CertFilePath = property(fset=_setIssuerX509CertFilePath,
550                                      doc="File path to user X.509 cert. "
551                                          "issuing cert.")     
552
553    def _getUserPriKey(self):
554        "Get method for user private key"
555        return self._userPriKey
556   
557    def _setUserPriKey(self, userPriKey):
558        """Set method for user private key
559       
560        Nb. if input is a string, userPriKeyPwd will need to be set if
561        the key is password protected.
562       
563        @type userPriKey: M2Crypto.RSA.RSA / string
564        @param userPriKey: private key used to sign message"""
565       
566        if userPriKey is None:
567            log.warning("Setting user private key to None")
568            self._userPriKey = None
569        elif isinstance(userPriKey, basestring):
570            pwdCallback = lambda *ar, **kw: self._userPriKeyPwd
571            self._userPriKey = RSA.load_key_string(userPriKey,
572                                                   callback=pwdCallback)
573        elif isinstance(userPriKey, RSA.RSA):
574            self._userPriKey = userPriKey         
575        else:
576            raise AttributeError("user private key must be a valid "
577                                 "M2Crypto.RSA.RSA type or a string")
578               
579    userPriKey = property(fget=_getUserPriKey,
580                          fset=_setUserPriKey,
581                          doc="User private key if set, used to sign outbound "
582                              "messages to Attribute authority")
583
584    def _setUserPriKeyFilePath(self, filePath):
585        "Set user private key file path property method"
586       
587        if isinstance(filePath, basestring):
588            filePath = os.path.expandvars(filePath)
589            try:
590                # Read Private key to sign with   
591                priKeyFile = BIO.File(open(filePath)) 
592                pwdCallback = lambda *ar, **kw: self._userPriKeyPwd
593                self._userPriKey = RSA.load_key_bio(priKeyFile, 
594                                                    callback=pwdCallback)   
595            except Exception, e:
596                raise AttributeError("Setting user private key: %s" % e)
597       
598        elif filePath is not None:
599            raise AttributeError("Private key file path must be a valid "
600                                 "string or None")
601       
602        self._userPriKeyFilePath = filePath
603       
604    userPriKeyFilePath = property(fset=_setUserPriKeyFilePath,
605                                  doc="File path to user private key")
606 
607    def _setUserPriKeyPwd(self, userPriKeyPwd):
608        "Set method for user private key file password"
609        if userPriKeyPwd is not None and not isinstance(userPriKeyPwd, 
610                                                        basestring):
611            raise AttributeError("Signing private key password must be None "
612                                 "or a valid string")
613       
614        self._userPriKeyPwd = userPriKeyPwd
615
616    def _getUserPriKeyPwd(self):
617        "Get property method for user private key"
618        return self._userPriKeyPwd
619       
620    userPriKeyPwd = property(fset=_setUserPriKeyPwd,
621                             fget=_getUserPriKeyPwd,
622                             doc="Password protecting user private key file")
623       
624    def _getCredentials(self):
625        """Get Property method.  Credentials are read-only
626       
627        @rtype: dict
628        @return: cached ACs indesed by issuing organisation name"""
629        return self._credentials
630
631    # Publish attribute
632    credentials = property(fget=_getCredentials,
633                           doc="List of Attribute Certificates linked to "
634                               "issuing authorities")
635
636
637    def _getCACertFilePathList(self):
638        """Get CA cert or certs used to validate AC signatures and signatures
639        of peer SOAP messages.
640       
641        @rtype caCertFilePathList: basestring, list or tuple
642        @return caCertFilePathList: file path(s) to CA certificates."""
643        return self._caCertFilePathList
644   
645    def _setCACertFilePathList(self, caCertFilePathList):
646        """Set CA cert or certs to validate AC signatures, signatures
647        of Attribute Authority SOAP responses and SSL connections where
648        AA SOAP service is run over SSL.
649       
650        @type caCertFilePathList: basestring, list, tuple or None
651        @param caCertFilePathList: file path(s) to CA certificates.  If None
652        then the input is quietly ignored."""
653       
654        if isinstance(caCertFilePathList, basestring):
655           self._caCertFilePathList = [caCertFilePathList]
656           
657        elif isinstance(caCertFilePathList, list):
658           self._caCertFilePathList = caCertFilePathList
659           
660        elif isinstance(caCertFilePathList, tuple):
661           self._caCertFilePathList = list(caCertFilePathList)
662
663        elif caCertFilePathList is not None:
664            raise CredWalletError("Input CA Certificate file path is not a "
665                                  "valid string")     
666       
667    caCertFilePathList = property(fget=_getCACertFilePathList,
668                                  fset=_setCACertFilePathList,
669                                  doc="CA Certificates - used for "
670                                      "verification of AC and SOAP message "
671                                      "signatures")
672
673    def _getSSLCACertFilePathList(self):
674        """Get CA cert or certs used to validate AC signatures and signatures
675        of peer SOAP messages.
676       
677        @rtype sslCACertFilePathList: basestring, list or tuple
678        @return sslCACertFilePathList: file path(s) to CA certificates."""
679        return self._sslCACertFilePathList
680   
681    def _setSSLCACertFilePathList(self, sslCACertFilePathList):
682        """Set CA cert or certs to validate AC signatures, signatures
683        of Attribute Authority SOAP responses and SSL connections where
684        AA SOAP service is run over SSL.
685       
686        @type sslCACertFilePathList: basestring, list, tuple or None
687        @param sslCACertFilePathList: file path(s) to CA certificates.  If None
688        then the input is quietly ignored."""
689       
690        if isinstance(sslCACertFilePathList, basestring):
691           self._sslCACertFilePathList = [sslCACertFilePathList]
692           
693        elif isinstance(sslCACertFilePathList, list):
694           self._sslCACertFilePathList = sslCACertFilePathList
695           
696        elif isinstance(sslCACertFilePathList, tuple):
697           self._sslCACertFilePathList = list(sslCACertFilePathList)
698
699        elif sslCACertFilePathList is not None:
700            raise CredWalletError("Input CA Certificate file path is not a "
701                                  "valid string")     
702       
703    sslCACertFilePathList = property(fget=_getSSLCACertFilePathList,
704                                  fset=_setSSLCACertFilePathList,
705                                  doc="CA Certificates - used for "
706                                      "verification of peer certs in SSL "
707                                      "connections")
708
709    def _createAttributeAuthorityClnt(self, attributeAuthorityURI):
710        """Set up a client to an Attribute Authority with the given URI
711       
712        @type attributeAuthorityURI: string
713        @param attributeAuthorityURI: Attribute Authority Web Service URI.
714
715        @rtype: ndg.security.common.AttAuthorityClient
716        @return: new Attribute Authority client instance"""
717
718        log.debug('CredWallet._createAttributeAuthorityClnt for service: "%s"'%
719                  attributeAuthorityURI)
720
721        attributeAuthorityClnt = AttAuthorityClient(uri=attributeAuthorityURI,
722                                sslCACertFilePathList=self._sslCACertFilePathList,
723                                cfg=self.wssCfgFilePath or self._cfg,
724                                cfgFileSection=self.wssCfgSection,
725                                **(self.wssCfgKw or {}))
726       
727        # If a user certificate is set, use this to sign messages instead of
728        # the default settings in the WS-Security config. 
729        if attributeAuthorityClnt.signatureHandler is not None and \
730           self.userPriKey is not None:
731            if self.issuingX509Cert is not None:
732                # Pass a chain of certificates -
733                # Initialise WS-Security signature handling to pass
734                # BinarySecurityToken containing user cert and cert for user
735                # cert issuer
736                attributeAuthorityClnt.signatureHandler.reqBinSecTokValType = \
737                            SignatureHandler.binSecTokValType["X509PKIPathv1"]
738                attributeAuthorityClnt.signatureHandler.signingCertChain = \
739                                    (self._issuingX509Cert, self._userX509Cert)               
740
741                attributeAuthorityClnt.signatureHandler.signingPriKey = \
742                                                            self.userPriKey
743            elif self.userX509Cert is not None:
744                # Pass user cert only - no need to pass a cert chain. 
745                # This type of token is more likely to be supported by the
746                # various WS-Security toolkits
747                attributeAuthorityClnt.signatureHandler.reqBinSecTokValType = \
748                                    SignatureHandler.binSecTokValType["X509v3"]
749                attributeAuthorityClnt.signatureHandler.signingCert = \
750                                                            self._userX509Cert
751
752                attributeAuthorityClnt.signatureHandler.signingPriKey = \
753                                                            self.userPriKey
754
755        return attributeAuthorityClnt
756
757    def createAttributeAuthorityClnt(self, attributeAuthorityURI=None):
758        '''Convenience method to create an Attribute Authority Client based
759        on WS-Security config and user X.509 certificate settings if present
760       
761        Nb. a client is created implicitly when the attributeAuthorityURI
762        attribute property is set'''
763       
764        if attributeAuthorityURI is None:
765            attributeAuthorityURI = self.attributeAuthorityURI
766           
767        self._attributeAuthorityClnt = self._createAttributeAuthorityClnt(
768                                                        attributeAuthorityURI)
769       
770    def _getAttributeAuthorityURI(self):
771        """Get property method for Attribute Authority Web Service URI to
772        connect to."""
773        return self._attributeAuthorityURI
774           
775    def _setAttributeAuthorityURI(self, attributeAuthorityURI):
776        """Set property method for Attribute Authority Web Service URI to
777        connect to.  This method ALSO SETS UP THE CLIENT INTERFACE
778       
779        @type attributeAuthorityURI: string
780        @param attributeAuthorityURI: Attribute Authority Web Service URI.  Set
781        to None to initialise.  Set to a URI to instantiate a new Attribute
782        Authority client"""
783        if attributeAuthorityURI is None:
784            self._attributeAuthorityURI = self._attributeAuthorityClnt = None
785            return
786        else:
787            self._attributeAuthorityURI = attributeAuthorityURI
788            self._attributeAuthorityClnt = self._createAttributeAuthorityClnt(
789                                                        attributeAuthorityURI)
790           
791    attributeAuthorityURI = property(fget=_getAttributeAuthorityURI,
792                                     fset=_setAttributeAuthorityURI,
793                                     doc="Attribute Authority address - "
794                                         "setting also sets up "
795                                         "AttAuthorityClient instance!")
796
797
798    def _getAttributeAuthorityClnt(self):
799        """Get property method for Attribute Authority Web Service client
800        instance.  Use attributeAuthorityURI property to set up
801        attributeAuthorityClnt
802       
803        @type attributeAuthorityClnt: AttAuthorityClient
804        @param attributeAuthorityClnt: Attribute Authority Web Service client
805        instance"""
806        return self._attributeAuthorityClnt
807           
808    attributeAuthorityClnt = property(fget=_getAttributeAuthorityClnt, 
809                                      doc="Attribute Authority web service "
810                                          "client instance")
811
812
813    def _getAttributeAuthority(self):
814        """Get property method for Attribute Authority Web Service client
815        instance.  Use attributeAuthorityURI propert to set up
816        attributeAuthorityClnt
817       
818        @rtype attributeAuthority: ndg.security.server.AttAuthority.AttAuthority
819        @return attributeAuthority: Attribute Authority instance"""
820        return self._attributeAuthority
821
822    def _setAttributeAuthority(self, attributeAuthority):
823        """Set property method for Attribute Authority Web Service instance to
824        connect to.
825       
826        @type attributeAuthority: ndg.security.server.AttAuthority.AttAuthority
827        @param attributeAuthority: Attribute Authority instance."""
828        if attributeAuthority is not None and \
829           not isinstance(attributeAuthority, AttAuthority):
830            raise AttributeError("Expecting %r for attributeAuthority "
831                                 "attribute" % AttAuthority)
832           
833        self._attributeAuthority = attributeAuthority
834           
835    attributeAuthority = property(fget=_getAttributeAuthority,
836                                  fset=_setAttributeAuthority, 
837                                  doc="Attribute Authority instance")
838
839
840    def _getMapFromTrustedHosts(self):
841        """Get property method for boolean flag - if set to True it allows
842        role mapping to be attempted when connecting to an Attribute Authority
843       
844        @type mapFromTrustedHosts: bool
845        @param mapFromTrustedHosts: set to True to try role mapping in AC
846        requests to Attribute Authorities"""
847        return self._mapFromTrustedHosts
848
849    def _setMapFromTrustedHosts(self, mapFromTrustedHosts):
850        """Set property method for boolean flag - if set to True it allows
851        role mapping to be attempted when connecting to an Attribute Authority
852       
853        @type mapFromTrustedHosts: bool
854        @param mapFromTrustedHosts: Attribute Authority Web Service."""
855        if CredWallet.isBoolString(mapFromTrustedHosts):
856            mapFromTrustedHosts = bool(mapFromTrustedHosts)
857           
858        elif not isinstance(mapFromTrustedHosts, bool):
859            raise AttributeError("Expecting %r for mapFromTrustedHosts "
860                                 "attribute" % bool)
861           
862        self._mapFromTrustedHosts = mapFromTrustedHosts
863           
864    mapFromTrustedHosts = property(fget=_getMapFromTrustedHosts,
865                                   fset=_setMapFromTrustedHosts, 
866                                   doc="Set to True to enable mapped AC "
867                                       "requests")
868
869    def _getRtnExtAttCertList(self):
870        """Get property method for Attribute Authority Web Service client
871        instance.  Use rtnExtAttCertListURI propert to set up
872        rtnExtAttCertListClnt
873       
874        @type rtnExtAttCertList: bool
875        @param rtnExtAttCertList: """
876        return self._rtnExtAttCertList
877
878    def _setRtnExtAttCertList(self, rtnExtAttCertList):
879        """Set property method for boolean flag - when a AC request fails,
880        return a list of candidate ACs that could be used to re-try with in
881        order to get mapped AC.
882       
883        @type rtnExtAttCertList: bool
884        @param rtnExtAttCertList: set to True to configure getAttCert to return
885        a list of ACs that could be used in a re-try to get a mapped AC from
886        the target Attribute Authority."""
887        if CredWallet.isBoolString(rtnExtAttCertList):
888            rtnExtAttCertList = bool(rtnExtAttCertList)
889           
890        elif not isinstance(rtnExtAttCertList, bool):
891            raise AttributeError("Expecting %r for rtnExtAttCertList "
892                                 "attribute" % bool)
893           
894        self._rtnExtAttCertList = rtnExtAttCertList
895           
896    rtnExtAttCertList = property(fget=_getRtnExtAttCertList,
897                                 fset=_setRtnExtAttCertList, 
898                                 doc="Set to True to enable mapped AC "
899                                     "requests")
900
901    @staticmethod
902    def isBoolString(string):
903        '''Test for string set to equivalent of bool const values'''
904        return isinstance(string, basestring) and string in ('True', 'False')
905
906
907    def isValid(self, **x509CertKeys):
908        """Check wallet's user cert.  If expired return False
909       
910        @type **x509CertKeys: dict
911        @param **x509CertKeys: keywords applying to
912        ndg.security.common.X509.X509Cert.isValidTime method"""
913        return self._userX509Cert.isValidTime(**x509CertKeys)
914
915
916    def addCredential(self, attCert, bUpdateCredentialRepository=True):
917        """Add a new attribute certificate to the list of credentials held.
918
919        @type attCert:
920        @param attCert: new attribute Certificate to be added
921        @type bUpdateCredentialRepository: bool
922        @param bUpdateCredentialRepository: if set to True, and a repository
923        exists it will be updated with the new credentials also
924       
925        @rtype: bool
926        @return: True if certificate was added otherwise False.  - If an
927        existing certificate from the same issuer has a later expiry it will
928        take precence and the new input certificate is ignored."""
929
930        # Check input
931        if not isinstance(attCert, AttCert):
932            raise CredWalletError("Attribute Certificate must be an AttCert "
933                                  "type object")
934
935        # Check certificate validity
936        try:
937            attCert.isValid(raiseExcep=True)
938           
939        except AttCertError, e:
940            raise CredWalletError("Adding Credential: %s" % e)
941       
942
943        # Check to see if there is an existing Attribute Certificate held
944        # that was issued by the same host.  If so, compare the expiry time.
945        # The one with the latest expiry will be retained and the other
946        # ingored
947        bUpdateCred = True
948        issuerName = attCert['issuerName']
949       
950        if issuerName in self._credentials:
951            # There is an existing certificate held with the same issuing
952            # host name as the new certificate
953            attCertOld = self._credentials[issuerName]['attCert']
954
955            # Get expiry times in datetime format to allow comparison
956            dtAttCertOldNotAfter = attCertOld.getValidityNotAfter(\
957                                                            asDatetime=True)
958            dtAttCertNotAfter = attCert.getValidityNotAfter(asDatetime=True)
959
960            # If the new certificate has an earlier expiry time then ignore it
961            bUpdateCred = dtAttCertNotAfter > dtAttCertOldNotAfter
962
963               
964        if bUpdateCred:
965            # Update: Nb. -1 ID value flags item as new.  Items read in
966            # from the CredentialRepository during creation of the wallet will
967            # have +ve IDs previously allocated by the database
968            self._credentials[issuerName] = {'id': -1, 'attCert': attCert}
969
970            # Update the Credentials Repository - the permanent store of user
971            # authorisation credentials.  This allows credentials for previous
972            # sessions to be re-instated
973            if self._credentialRepository and bUpdateCredentialRepository:
974                self.updateCredentialRepository()
975
976        # Flag to caller to indicate whether the input certificate was added
977        # to the credentials or an exsiting certificate from the same issuer
978        # took precedence
979        return bUpdateCred
980           
981
982    def audit(self):
983        """Check the credentials held in the wallet removing any that have
984        expired or are otherwise invalid."""
985
986        log.debug("CredWallet.audit ...")
987       
988        # Nb. No signature check is carried out.  To do a check, access is
989        # needed to the cert of the CA that issued the Attribute Authority's
990        # cert
991        #
992        # P J Kershaw 12/09/05
993        for key, val in self._credentials.items():
994            if not val['attCert'].isValid(chkSig=False):
995                del self._credentials[key]
996
997
998    def updateCredentialRepository(self, auditCred=True):
999        """Copy over non-persistent credentials held by wallet into the
1000        perminent repository.
1001       
1002        @type auditCred: bool
1003        @param auditCred: filter existing credentials in the repository
1004        removing invalid ones"""
1005
1006        log.debug("CredWallet.updateCredentialRepository ...")
1007       
1008        if not self._credentialRepository:
1009            raise CredWalletError("No Credential Repository has been created "
1010                                  "for this wallet")
1011                           
1012        # Filter out invalid certs unless auditCred flag is explicitly set to
1013        # false
1014        if auditCred: self.audit()
1015
1016        # Update the database - only add new entries i.e. with an ID of -1
1017        attCertList = [i['attCert'] for i in self._credentials.values() \
1018                       if i['id'] == -1]
1019
1020        self._credentialRepository.addCredentials(self.userId, attCertList)
1021
1022
1023    def _getAttCert(self, attributeAuthorityClnt=None, extAttCert=None):       
1024        """Wrapper to Attribute Authority attribute certificate request.  See
1025        getAttCert for the classes' public interface.
1026
1027        To call the Attribute Authority as a Web Service, specify a URI
1028        otherwise set the properties file path.
1029       
1030        If successful, a new attribute certificate is issued to the user
1031        and added into the wallet
1032
1033        @type attributeAuthorityClnt: ndg.security.common.AttAuthorityClient
1034        @param attributeAuthorityClnt: client object to Attribute Authority to
1035        make a request to.  If omitted, it is set to
1036        self._attributeAuthorityClnt.  This attribute may itself be None.   
1037        In this case, a local AA will be expected to be set.
1038       
1039        @type extAttCert: ndg.security.common.AttCert.AttCert
1040        @param extAttCert: an existing Attribute Certificate which can
1041        be used to making a mapping should the user not be registered with the
1042        Attribute Authority"""
1043     
1044        log.debug("CredWallet._getAttCert ...")
1045       
1046        if attributeAuthorityClnt is None:
1047            attributeAuthorityClnt = self._attributeAuthorityClnt
1048       
1049        # If a user cert. is present, ignore the user ID setting.  The
1050        # Attribute Authority will set the userId field of the
1051        # Attribute Certificate based on the DN of the user certificate
1052        if self.userX509Cert:
1053            userId = None
1054        else:
1055            userId = self.userId
1056           
1057        if attributeAuthorityClnt is not None:
1058            try:
1059                log.debug("Calling attribute authority using supplied client")
1060                attCert = attributeAuthorityClnt.getAttCert(userId=userId,
1061                                                        userAttCert=extAttCert)
1062                               
1063                log.info('Granted Attribute Certificate from issuer DN = "%s"'
1064                         ' at "%s"' % (attCert.issuerDN, 
1065                                       attributeAuthorityClnt.uri))
1066               
1067            except AttributeRequestDenied, e:
1068                raise CredWalletAttributeRequestDenied, str(e)
1069                           
1070        elif self.attributeAuthority is not None:
1071
1072            # Call local based Attribute Authority with settings from the
1073            # configuration file attributeAuthority
1074            try:
1075                # Request a new attribute certificate from the Attribute
1076                # Authority
1077                log.debug("Calling Attribute Authority using info from "
1078                          "properties file: %s" % self.attributeAuthority)
1079                   
1080                attCert = self.attributeAuthority.getAttCert(userId=userId,
1081                                                        userAttCert=extAttCert)
1082               
1083                log.info('Granted Attribute Certificate from issuer DN = "%s"'%
1084                         attCert.issuerDN)
1085               
1086            except AttAuthorityAccessDenied, e:
1087                raise CredWalletAttributeRequestDenied, str(e)
1088                       
1089            except Exception, e:
1090                raise CredWalletError("Requesting attribute certificate: %s"%e)
1091
1092        else:
1093            raise CredWalletError("Error requesting attribute: certificate a "
1094                                  "URI or Attribute Authority configuration "
1095                                  "file must be specified")
1096       
1097
1098        # Update attribute Certificate instance with CA's certificate ready
1099        # for signature check in addCredential()
1100        if self._caCertFilePathList is None:
1101            raise CredWalletError("No CA certificate has been set")
1102       
1103        attCert.certFilePathList = self._caCertFilePathList
1104
1105       
1106        # Add credential into wallet
1107        #
1108        # Nb. if the certificates signature is invalid, it will be rejected
1109        log.debug("Adding credentials into wallet...")
1110        self.addCredential(attCert)
1111       
1112        return attCert
1113
1114
1115    def getAATrustedHostInfo(self, 
1116                             userRole=None,
1117                             attributeAuthority=None,
1118                             attributeAuthorityURI=None):
1119        """Wrapper to Attribute Authority getTrustedHostInfo
1120       
1121        getAATrustedHostInfo([userRole=r, ][attributeAuthority=f|attributeAuthorityURI=u])
1122                   
1123        @type userRole: string
1124        @param userRole: get hosts which have a mapping to this role
1125       
1126        @type attributeAuthorityURI: string
1127        @param attributeAuthorityURI: to call as a web service, specify the URI for the
1128        Attribute Authority.
1129       
1130        @type attributeAuthority: string
1131        @param attributeAuthority: Altenrative to attributeAuthorityURI - to run on the local
1132        machine, specify the local Attribute Authority configuration file.
1133        """
1134       
1135        log.debug('CredWallet.getAATrustedHostInfo for role "%s" and service: '
1136                  '"%s"' % (userRole, attributeAuthorityURI or attributeAuthority))
1137        if attributeAuthorityURI:
1138            self._setAttributeAuthorityURI(attributeAuthorityURI)
1139        elif attributeAuthority:
1140            self._setAAPropFilePath
1141
1142           
1143        if self._attributeAuthorityClnt is not None:
1144            # Call Attribute Authority WS
1145            try:
1146                return self._attributeAuthorityClnt.getTrustedHostInfo(role=userRole)               
1147                           
1148            except Exception, e:
1149                log.error("Requesting trusted host information: %s" % str(e))
1150                raise 
1151
1152        elif self.attributeAuthority is not None:
1153
1154            # Call local based Attribute Authority with settings from the
1155            # configuration file attributeAuthority
1156            try:
1157                # Request a new attribute certificate from the Attribute
1158                # Authority
1159                return self.attributeAuthority.getTrustedHostInfo(role=userRole)
1160               
1161            except Exception, e:
1162                log.error("Requesting trusted host info: %s" % e)
1163                raise
1164
1165        else:
1166            raise CredWalletError("Error requesting trusted hosts info: " 
1167                                  "a URI or Attribute Authority " 
1168                                  "configuration file must be specified")
1169
1170
1171    def getAttCert(self,
1172                   reqRole=None,
1173                   attributeAuthority=None,
1174                   attributeAuthorityURI=None,
1175                   mapFromTrustedHosts=None,
1176                   rtnExtAttCertList=None,
1177                   extAttCertList=None,
1178                   extTrustedHostList=None,
1179                   refreshAttCert=False,
1180                   attCertRefreshElapse=None):
1181       
1182        """Get an Attribute Certificate from an Attribute Authority.  If this
1183        fails try to make a mapped Attribute Certificate by using a certificate from another
1184        host which has a trust relationship to the Attribute Authority in
1185        question.
1186
1187        getAttCert([reqRole=r, ][attributeAuthority=a|attributeAuthorityURI=u,]
1188                   [mapFromTrustedHosts=m, ]
1189                   [rtnExtAttCertList=e, ][extAttCertList=el, ]
1190                   [extTrustedHostList=et, ][refreshAttCert=ra])
1191                 
1192        The procedure is:
1193
1194        1) Try attribute request using user certificate
1195        2) If the Attribute Authority (AA) doesn't recognise the certificate,
1196        find out any other hosts which have a trust relationship to the AA.
1197        3) Look for Attribute Certificates held in the wallet corresponding
1198        to these hosts.
1199        4) If no Attribute Certificates are available, call the relevant
1200        hosts' AAs to get certificates
1201        5) Finally, use these new certificates to try to obtain a mapped
1202        certificate from the original AA
1203        6) If this fails access is denied     
1204                   
1205        @type reqRole: string
1206        @param reqRole: the required role to get access for
1207       
1208        @type attributeAuthorityURI: string
1209        @param attributeAuthorityURI: to call as a web service, specify the URI for the
1210        Attribute Authority.
1211       
1212        @type attributeAuthority: string
1213        @param attributeAuthority: Altenrative to attributeAuthorityURI - to run on the local
1214        machine, specify the local Attribute Authority configuration file.
1215                               
1216        @type mapFromTrustedHosts: bool / None     
1217        @param mapFromTrustedHosts: if request fails via the user's cert
1218        ID, then it is possible to get a mapped certificate by using
1219        certificates from other AA's.  Set this flag to True, to allow this
1220        second stage of generating a mapped certificate from the certificate
1221        stored in the wallet credentials.
1222
1223        If set to False, it is possible to return the list of certificates
1224        available for mapping and then choose which one or ones to use for
1225        mapping by re-calling getAttCert with extAttCertList set to these
1226        certificates.
1227       
1228        Defaults to None in which case self._mapFromTrustedHosts is not
1229        altered
1230
1231        The list is returned via CredWalletAttributeRequestDenied exception
1232        If no value is set, the default value held in
1233        self.mapFromTrustedHosts is used
1234
1235        @type rtnExtAttCertList: bool / None
1236        @param rtnExtAttCertList: If request fails, make a list of
1237        candidate certificates from other Attribute Authorities which the user
1238        could use to retry and get a mapped certificate.
1239                               
1240        If mapFromTrustedHosts is set True this flags value is overriden and
1241        effectively set to True.
1242
1243        If no value is set, the default value held in self._rtnExtAttCertList
1244        is used.
1245                               
1246        The list is returned via a CredWalletAttributeRequestDenied exception
1247        object.
1248                               
1249        @type extAttCertList: list
1250        @param extAttCertList: Attribute Certificate or list of certificates
1251        from other Attribute Authorities.  These can be used to get a mapped
1252        certificate if access fails based on the user's certificate
1253        credentials.  They are tried out in turn until access is granted so
1254        the order of the list decides the order in which they will be tried
1255
1256        @type extTrustedHostList:
1257        @param extTrustedHostList: same as extAttCertList keyword, but
1258        instead of providing Attribute Certificates, give a list of Attribute
1259        Authority hosts.  These will be matched up to Attribute Certificates
1260        held in the wallet.  Matching certificates will then be used to try to
1261        get a mapped Attribute Certificate.
1262       
1263        @type refreshAttCert: bool
1264        @param refreshAttCert: if set to True, the attribute request
1265        will go ahead even if the wallet already contains an Attribute
1266        Certificate from the target Attribute Authority.  The existing AC in
1267        the wallet will be replaced by the new one obtained from this call.
1268                               
1269        If set to False, this method will check to see if an AC issued by the
1270        target AA already exists in the wallet.  If so, it will return this AC
1271        to the caller without proceeding to make a call to the AA.
1272       
1273        @type attCertRefreshElapse: float / int
1274        @param attCertRefreshElapse: determine whether to replace an
1275        existing AC in the cache with a fresh one.  If the existing one has
1276        less than attCertRefreshElapse time in seconds left before expiry then
1277        replace it.
1278       
1279        @rtype: ndg.security.common.AttCert.AttCert
1280        @return: Attribute Certificate retrieved from Attribute Authority"""
1281       
1282        log.debug("CredWallet.getAttCert ...")
1283       
1284        # Both these assignments are calling set property methods implicitly!
1285        if attributeAuthorityURI:
1286            self.attributeAuthorityURI = attributeAuthorityURI
1287        elif attributeAuthority:
1288            self.attributeAuthority = attributeAuthority
1289           
1290        if not refreshAttCert and self._credentials:
1291            # Refresh flag is not set so it's OK to check for any existing
1292            # Attribute Certificate in the wallet whose issuerName match the
1293            # target AA's name
1294           
1295            # Find out the site ID for the target AA by calling AA's host
1296            # info WS method
1297            log.debug("CredWallet.getAttCert - check AA site ID ...")
1298           
1299            try:
1300                hostInfo = self._attributeAuthorityClnt.getHostInfo()
1301                aaName = hostInfo.keys()[0]
1302            except Exception, e:
1303                raise CredWalletError("Getting host info: %s" % e)
1304           
1305            # Look in the wallet for an AC with the same issuer name
1306            if aaName in self._credentials:
1307                # Existing Attribute Certificate found in wallet - Check that
1308                # it will be valid for at least the next 2 hours
1309                if attCertRefreshElapse is not None:
1310                    self.attCertRefreshElapse = attCertRefreshElapse
1311                   
1312                dtNow = datetime.utcnow() + \
1313                        timedelta(seconds=self.attCertRefreshElapse)
1314               
1315                attCert = self._credentials[aaName]['attCert']
1316                if attCert.isValidTime(dtNow=dtNow):
1317                    log.info("Retrieved an existing %s AC from the wallet" % 
1318                             aaName)
1319                    return attCert
1320           
1321           
1322        # Check for settings from input, if not set use previous settings
1323        # made
1324        if mapFromTrustedHosts is not None:
1325            self.mapFromTrustedHosts = mapFromTrustedHosts
1326
1327        if rtnExtAttCertList is not None:
1328            self._rtnExtAttCertList = rtnExtAttCertList
1329
1330
1331        # Check for list of external trusted hosts (other trusted NDG data
1332        # centres)
1333        if extTrustedHostList:
1334            log.info("Checking for ACs in wallet matching list of trusted "
1335                     "hosts set: %s" % extTrustedHostList)
1336           
1337            if not self.mapFromTrustedHosts:
1338                raise CredWalletError("A list of trusted hosts has been " 
1339                                      "input but mapping from trusted hosts "
1340                                      "is set to disallowed")
1341           
1342            if isinstance(extTrustedHostList, basestring):
1343                extTrustedHostList = [extTrustedHostList]
1344
1345            # Nb. Any extAttCertList is overriden by extTrustedHostList being
1346            # set
1347            extAttCertList = [self._credentials[hostName]['attCert'] \
1348                              for hostName in extTrustedHostList \
1349                              if hostName in self._credentials]
1350
1351        # Set an empty list to trigger an AttributeError by initialising it to
1352        # None
1353        if extAttCertList == []:
1354            extAttCertList = None
1355           
1356        # Repeat authorisation attempts until succeed or means are exhausted
1357        while True:
1358           
1359            # Check for candidate certificates for mapping
1360            try:
1361                # If list is set get the next cert
1362                extAttCert = extAttCertList.pop()
1363
1364            except AttributeError:
1365                log.debug("No external Attribute Certificates - trying "
1366                          "request without mapping...")
1367                # No List set - attempt request without
1368                # using mapping from trusted hosts
1369                extAttCert = None
1370                           
1371            except IndexError:
1372               
1373                # List has been emptied without attribute request succeeding -
1374                # give up
1375                errMsg = "Attempting to obtained a mapped certificate: " + \
1376                         "no external attribute certificates are available"
1377                   
1378                # Add the exception form the last call to the Attribute
1379                # Authority if an error exists
1380                try:
1381                    errMsg += ": %s" % attributeRequestDenied
1382                except NameError:
1383                    pass
1384
1385                raise CredWalletAttributeRequestDenied, errMsg
1386                                                   
1387               
1388            # Request Attribute Certificate from Attribute Authority
1389            try:
1390                attCert = self._getAttCert(extAttCert=extAttCert)               
1391                # Access granted
1392                return attCert
1393           
1394            except CredWalletAttributeRequestDenied, attributeRequestDenied:
1395                if not self.mapFromTrustedHosts and not self.rtnExtAttCertList:
1396                    # Creating a mapped certificate is not allowed - raise
1397                    # authorisation denied exception saved from earlier
1398                    raise attributeRequestDenied
1399
1400                if isinstance(extAttCertList, list):
1401                    # An list of attribute certificates from trusted hosts
1402                    # is present continue cycling through this until one of
1403                    # them is accepted and a mapped certificate can be derived
1404                    log.debug("AC request denied - but external ACs available "
1405                              "to try mapped AC request ...")
1406                    continue
1407                             
1408                #  Use the input required role and the AA's trusted host list
1409                # to identify attribute certificates from other hosts which
1410                # could be used to make a mapped certificate
1411                log.debug("Getting a list of trusted hosts for mapped AC "
1412                          "request ...")
1413                try:
1414                    trustedHostInfo = self.getAATrustedHostInfo(reqRole,
1415                                            attributeAuthority=attributeAuthority)
1416                except NoMatchingRoleInTrustedHosts, e:
1417                    raise CredWalletAttributeRequestDenied(
1418                        'Can\'t get a mapped Attribute Certificate for '
1419                        'the "%s" role' % reqRole)
1420               
1421                except Exception, e:
1422                    raise CredWalletError, "Getting trusted hosts: %s" % e
1423
1424                if not trustedHostInfo:
1425                    raise CredWalletAttributeRequestDenied(
1426                        "Attribute Authority has no trusted hosts with "
1427                        "which to make a mapping")
1428
1429               
1430                # Initialise external certificate list here - if none are
1431                # found IndexError will be raised on the next iteration and
1432                # an access denied error will be raised
1433                extAttCertList = []
1434
1435                # Look for Attribute Certificates with matching issuer host
1436                # names
1437                log.debug("Checking wallet for ACs issued by one of the "
1438                          "trusted hosts...")
1439                for hostName in self._credentials:
1440
1441                    # Nb. Candidate certificates for mappings must have
1442                    # original provenance and contain at least one of the
1443                    # required roles
1444                    attCert = self._credentials[hostName]['attCert']
1445                   
1446                    if hostName in trustedHostInfo and attCert.isOriginal():                       
1447                        for role in attCert.roles:
1448                            if role in trustedHostInfo[hostName]['role']:                               
1449                                extAttCertList.append(attCert)
1450
1451
1452                if not extAttCertList:
1453                    log.debug("No wallet ACs matched any of the trusted " + \
1454                              "hosts.  - Try request for an AC from a " + \
1455                              "trusted host ...")
1456                   
1457                    # No certificates in the wallet matched the trusted host
1458                    # and required roles
1459                    #
1460                    # Try each host in turn in order to get a certificate with
1461                    # the required credentials in order to do a mapping
1462                    for host, info in trustedHostInfo.items():
1463                        try:
1464                            # Try request to trusted host
1465                            trustedAAClnt = self._createAttributeAuthorityClnt(info['attributeAuthorityURI'])
1466                            extAttCert=self._getAttCert(attributeAuthorityClnt=trustedAAClnt)
1467
1468                            # Check the certificate contains at least one of
1469                            # the required roles
1470                            if [True for r in extAttCert.roles \
1471                                if r in info['role']]:
1472                               extAttCertList.append(extAttCert)
1473
1474                               # For efficiency, stop once obtained a valid
1475                               # cert - but may want complete list for user to
1476                               # choose from
1477                               #break
1478                               
1479                        except Exception, e:
1480                            # ignore any errors and continue
1481                            log.warning('AC request to trusted host "%s"' % \
1482                                        info['aaURI'] + ' resulted in: %s' % e)
1483                           
1484                   
1485                if not extAttCertList:                       
1486                    raise CredWalletAttributeRequestDenied, \
1487                        "No certificates are available with which to " + \
1488                        "make a mapping to the Attribute Authority"
1489
1490
1491                if not self.mapFromTrustedHosts:
1492                   
1493                    # Exit here returning the list of candidate certificates
1494                    # that could be used to make a mapped certificate
1495                    msg = "User is not registered with Attribute " + \
1496                          "Authority - retry using one of the returned " + \
1497                          "Attribute Certificates obtained from other " + \
1498                          "trusted hosts"
1499                         
1500                    raise CredWalletAttributeRequestDenied(msg,
1501                                            extAttCertList=extAttCertList,
1502                                            trustedHostInfo=trustedHostInfo)           
1503
1504
1505class CredentialRepositoryError(_CredWalletException):   
1506    """Exception handling for NDG Credential Repository class."""
1507
1508
1509class CredentialRepository:
1510    """CredWallet's abstract interface class to a Credential Repository.  The
1511    Credential Repository is abstract store of user currently valid user
1512    credentials.  It enables retrieval of attribute certificates from a user's
1513    previous session(s)"""
1514       
1515    def __init__(self, propFilePath=None, dbPPhrase=None, **prop):
1516        """Initialise Credential Repository abstract base class.  Derive from
1517        this class to define Credentail Repository interface Credential
1518        Wallet
1519
1520        If the connection string or properties file is set a connection
1521        will be made
1522
1523        @type dbPPhrase: string
1524        @param dbPPhrase: pass-phrase to database if applicable
1525       
1526        @type propFilePath: string
1527        @param propFilePath: file path to a properties file.  This could
1528        contain configuration parameters for the repository e.g.  database
1529        connection parameters
1530       
1531        @type **prop: dict
1532        @param **prop: any other keywords required
1533        """
1534        raise NotImplementedError(
1535            self.__init__.__doc__.replace('\n       ',''))
1536
1537
1538    def addUser(self, userId, dn=None):
1539        """A new user to Credentials Repository
1540       
1541        @type userId: string
1542        @param userId: userId for new user
1543        @type dn: string
1544        @param dn: users Distinguished Name (optional)"""
1545        raise NotImplementedError(
1546            self.addUser.__doc__.replace('\n       ',''))
1547
1548                           
1549    def auditCredentials(self, userId=None, **attCertValidKeys):
1550        """Check the attribute certificates held in the repository and delete
1551        any that have expired
1552
1553        @type userId: basestring/list or tuple
1554        @param userId: audit credentials for the input user ID or list of IDs
1555        @type attCertValidKeys: dict
1556        @param **attCertValidKeys: keywords which set how to check the
1557        Attribute Certificate e.g. check validity time, XML signature, version
1558         etc.  Default is check validity time only - See AttCert class"""
1559        raise NotImplementedError(
1560            self.auditCredentials.__doc__.replace('\n       ',''))
1561
1562
1563    def getCredentials(self, userId):
1564        """Get the list of credentials for a given users DN
1565       
1566        @type userId: string
1567        @param userId: users userId, name or X.509 cert. distinguished name
1568        @rtype: list
1569        @return: list of Attribute Certificates"""
1570        raise NotImplementedError(
1571            self.getCredentials.__doc__.replace('\n       ',''))
1572
1573       
1574    def addCredentials(self, userId, attCertList):
1575        """Add new attribute certificates for a user.  The user must have
1576        been previously registered in the repository
1577
1578        @type userId: string
1579        @param userId: users userId, name or X.509 cert. distinguished name
1580        @type attCertList: list
1581        @param attCertList: list of attribute certificates"""
1582        raise NotImplementedError(
1583            self.addCredentials.__doc__.replace('\n       ',''))
1584
1585
1586
1587class NullCredentialRepository(CredentialRepository):
1588    """Implementation of Credential Repository interface with empty stubs. 
1589    Use this class in the case where no Credential Repository is required"""
1590   
1591    def __init__(self, propFilePath=None, dbPPhrase=None, **prop):
1592        pass
1593
1594    def addUser(self, userId):
1595        pass
1596                           
1597    def auditCredentials(self, **attCertValidKeys):
1598        pass
1599
1600    def getCredentials(self, userId):
1601        return []
1602       
1603    def addCredentials(self, userId, attCertList):
1604        pass
Note: See TracBrowser for help on using the repository browser.