source: TI12-security/trunk/python/ndg.security.common/ndg/security/common/credentialwallet.py @ 4379

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

Renamed CredWallet? module -> credentialwallet

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