source: TI12-security/branches/ndg-security-1.5.x/ndg_security_common/ndg/security/common/credentialwallet.py @ 7632

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/branches/ndg-security-1.5.x/ndg_security_common/ndg/security/common/credentialwallet.py@7632
Revision 7632, 88.2 KB checked in by pjkersha, 10 years ago (diff)

Incomplete - task 15: NDG Security 1.5.8 Branch Release for Questionnaire

  • Committing 1.5.8 release. ZSI based SOAP functionality is disabled for Python 2.6 use but this does not affect currently used interfaces in production deployment.
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1"""Credential Wallet classes
2
3NERC DataGrid Project
4"""
5__author__ = "P J Kershaw"
6__date__ = "30/11/05"
7__copyright__ = "(C) 2009 Science and Technology Facilities Council"
8__license__ = "BSD - see LICENSE file in top-level directory"
9__contact__ = "Philip.Kershaw@stfc.ac.uk"
10__revision__ = '$Id:credentialwallet.py 4378 2008-10-29 10:30:14Z pjkersha $'
11
12import logging
13log = logging.getLogger(__name__)
14logging.basicConfig(level=logging.DEBUG)
15
16import os
17import warnings
18import traceback
19
20# Check Attribute Certificate validity times
21from datetime import datetime, timedelta
22
23from ConfigParser import ConfigParser
24
25from saml.utils import SAMLDateTime
26from saml.saml2.core import Assertion
27
28# Access Attribute Authority's web service using ZSI - allow pass if not
29# loaded since it's possible to make AttributeAuthority instance locally
30# without using the WS
31aaImportError = True
32try:
33    # AttributeAuthority client package resides with CredentialWallet module in
34    # ndg.security.common
35    from ndg.security.common.attributeauthority import (
36        AttributeAuthorityClient, AttributeAuthorityClientError, 
37        AttributeRequestDenied, NoMatchingRoleInTrustedHosts)
38    aaImportError = False
39except ImportError:
40    pass
41
42# Likewise - may not want to use WS and use AttributeAuthority locally in which
43# case no need to import it
44try:
45    from ndg.security.server.attributeauthority import (AttributeAuthority, 
46        AttributeAuthorityError, AttributeAuthorityAccessDenied)
47    aaImportError = False
48except ImportError:
49    pass
50
51if aaImportError:
52    raise ImportError("Either AttributeAuthority or AttributeAuthorityClient "
53                      "classes must be present to allow interoperation with "
54                      "Attribute Authorities: %s" % traceback.format_exc())
55
56# Authentication X.509 Certificate
57from ndg.security.common.X509 import X509Cert
58from M2Crypto import X509, BIO, RSA
59
60# Authorisation - attribute certificate
61from ndg.security.common.AttCert import AttCert, AttCertError
62
63_fourSuiteSignatureHandlerAvailable = True
64try:
65    from ndg.security.common.wssecurity.signaturehandler.foursuite import \
66                                                            SignatureHandler
67except ImportError, e:
68    warnings.warn("Four-suite based WS-Security Signature Handler not "
69                  "available, message is: %s" % e)
70    _fourSuiteSignatureHandlerAvailable = False
71
72# generic parser to read INI/XML properties file
73from ndg.security.common.utils.configfileparsers import \
74                                                INIPropertyFileWithValidation
75
76from ndg.security.common.utils.configfileparsers import (     
77                                                    CaseSensitiveConfigParser,)
78
79
80class _CredentialWalletException(Exception):   
81    """Generic Exception class for CredentialWallet module.  Overrides
82    Exception to enable writing to the log"""
83    def __init__(self, *arg, **kw):
84        if len(arg) > 0:
85            log.error(arg[0])
86           
87        Exception.__init__(self, *arg, **kw)
88
89
90class CredentialWalletError(_CredentialWalletException):   
91    """Exception handling for NDG Credential Wallet class.  Overrides Exception
92    to enable writing to the log"""
93
94
95class CredentialWalletAttributeRequestDenied(CredentialWalletError):   
96    """Handling exception where CredentialWallet is denied authorisation by an
97    Attribute Authority.
98 
99    @type __extAttCertList: list
100    @ivar __extAttCertList: list of candidate Attribute Certificates that
101    could be used to try to get a mapped certificate from the target
102    Attribute Authority
103   
104    @type __trustedHostInfo: dict
105    @ivar __trustedHostInfo: dictionary indexed by host name giving
106    details of Attribute Authority URI and roles for trusted hosts"""
107   
108    def __init__(self, *args, **kw):
109        """Raise exception for attribute request denied with option to give
110        caller hint to certificates that could used to try to obtain a
111        mapped certificate
112       
113        @type extAttCertList: list
114        @param extAttCertList: list of candidate Attribute Certificates that
115        could be used to try to get a mapped certificate from the target
116        Attribute Authority
117       
118        @type trustedHostInfo: dict
119        @param trustedHostInfo: dictionary indexed by host name giving
120        details of Attribute Authority URI and roles for trusted hosts"""
121       
122        self.__trustedHostInfo = kw.pop('trustedHostInfo', {})
123        self.__extAttCertList = kw.pop('extAttCertList', [])
124           
125        CredentialWalletError.__init__(self, *args, **kw)
126
127    def _getTrustedHostInfo(self):
128        """Get message text"""
129        return self.__trustedHostInfo
130
131    trustedHostInfo = property(fget=_getTrustedHostInfo, 
132                               doc="URI and roles details for trusted hosts")
133       
134    def _getExtAttCertList(self):
135        """Return list of candidate Attribute Certificates that could be used
136        to try to get a mapped certificate from the target Attribute Authority
137        """
138        return self.__extAttCertList
139
140    extAttCertList = property(fget=_getExtAttCertList,
141                              doc="list of candidate Attribute Certificates "
142                              "that could be used to try to get a mapped "
143                              "certificate from the target Attribute "
144                              "Authority")
145
146         
147class _MetaCredentialWallet(type):
148    """Enable CredentialWallet to have read only class variables e.g.
149   
150    print CredentialWallet.accessDenied
151   
152    ... is allowed but,
153   
154    CredentialWallet.accessDenied = None
155   
156    ... raises - AttributeError: can't set attribute"""
157   
158    def _getAccessDenied(cls):
159        '''accessDenied get method'''
160        return False
161   
162    accessDenied = property(fget=_getAccessDenied)
163   
164    def _getAccessGranted(cls):
165        '''accessGranted get method'''
166        return True
167   
168    accessGranted = property(fget=_getAccessGranted)
169
170class CredentialContainer(object):
171    """Container for cached credentials"""
172    ID_ATTRNAME = 'id'
173    ITEM_ATTRNAME = 'credential'
174    ISSUERNAME_ATTRNAME = 'issuerName'
175    ATTRIBUTE_AUTHORITY_URI_ATTRNAME = 'attributeAuthorityURI'
176    CREDENTIAL_TYPE_ATTRNAME = 'type'
177   
178    __ATTRIBUTE_NAMES = (
179        ID_ATTRNAME,
180        ITEM_ATTRNAME,
181        ISSUERNAME_ATTRNAME,
182        ATTRIBUTE_AUTHORITY_URI_ATTRNAME,
183        CREDENTIAL_TYPE_ATTRNAME
184    )
185    __slots__ = tuple(["__%s" % n for n in __ATTRIBUTE_NAMES])
186   
187    def __init__(self, type=None):
188        self.__type = None
189        self.type = type
190       
191        self.__id = -1
192        self.__credential = None
193        self.__issuerName = None
194        self.__attributeAuthorityURI = None
195
196    def _getType(self):
197        return self.__type
198
199    def _setType(self, value):
200        if not isinstance(value, type):
201            raise TypeError('Expecting %r for "type" attribute; got %r' %
202                            (type, type(value)))       
203        self.__type = value
204
205    type = property(_getType, _setType, 
206                    doc="Type for credential - set to None for any type")
207
208    def _getId(self):
209        return self.__id
210
211    def _setId(self, value):
212        if not isinstance(value, int):
213            raise TypeError('Expecting int type for "id" attribute; got %r' %
214                            type(value))
215        self.__id = value
216
217    id = property(_getId, 
218                  _setId, 
219                  doc="Numbered identifier for credential - "
220                      "set to -1 for new credentials")
221
222    def _getCredential(self):
223        return self.__credential
224
225    def _setCredential(self, value):
226        # Safeguard type attribute referencing for unpickling process - this
227        # method may be called before type attribute has been set
228        _type = getattr(self, 
229                        CredentialContainer.CREDENTIAL_TYPE_ATTRNAME, 
230                        None)
231       
232        if _type is not None and not isinstance(value, _type):
233            raise TypeError('Expecting %r type for "credential" attribute; '
234                            'got %r' % type(value))
235        self.__credential = value
236
237    credential = property(_getCredential, 
238                          _setCredential, 
239                          doc="Credential object")
240
241    def _getIssuerName(self):
242        return self.__issuerName
243
244    def _setIssuerName(self, value):
245        self.__issuerName = value
246
247    issuerName = property(_getIssuerName, 
248                          _setIssuerName, 
249                          doc="Name of issuer of the credential")
250
251    def _getAttributeAuthorityURI(self):
252        return self.__attributeAuthorityURI
253
254    def _setAttributeAuthorityURI(self, value):
255        """String or None type are allowed - The URI may be set to None if
256        a local Attribute Authority instance is being invoked rather
257        one hosted via a remote URI
258        """
259        if not isinstance(value, (basestring, type(None))):
260            raise TypeError('Expecting string or None type for '
261                            '"attributeAuthorityURI"; got %r instead' % 
262                            type(value))
263        self.__attributeAuthorityURI = value
264
265    attributeAuthorityURI = property(_getAttributeAuthorityURI,
266                                     _setAttributeAuthorityURI, 
267                                     doc="Attribute Authority Service URI")
268
269    def __getstate__(self):
270        '''Enable pickling'''
271        thisDict = dict([(attrName, getattr(self, attrName))
272                         for attrName in CredentialContainer.__ATTRIBUTE_NAMES])
273       
274        return thisDict
275       
276    def __setstate__(self, attrDict):
277        '''Enable pickling for use with beaker.session'''
278        try:
279            for attr, val in attrDict.items():
280                setattr(self, attr, val)
281        except Exception, e:
282            pass
283       
284
285class CredentialWalletBase(object):
286    """Abstract base class for NDG and SAML Credential Wallet implementations
287    """ 
288    CONFIG_FILE_OPTNAMES = ("userId", )
289    __slots__ = ("__credentials", "__credentialsKeyedByURI", "__userId")
290   
291    def __init__(self):
292        self.__userId = None
293        self.__credentials = {}
294        self.__credentialsKeyedByURI = {}
295
296    @classmethod
297    def fromConfig(cls, cfg, **kw):
298        '''Alternative constructor makes object from config file settings
299        @type cfg: basestring /ConfigParser derived type
300        @param cfg: configuration file path or ConfigParser type object
301        @rtype: ndg.security.common.credentialWallet.SAMLCredentialWallet
302        @return: new instance of this class
303        '''
304        credentialWallet = cls()
305        credentialWallet.parseConfig(cfg, **kw)
306       
307        return credentialWallet
308
309    def parseConfig(self, cfg, prefix='', section='DEFAULT'):
310        '''Virtual method defines interface to read config file settings
311        @type cfg: basestring /ConfigParser derived type
312        @param cfg: configuration file path or ConfigParser type object
313        @type prefix: basestring
314        @param prefix: prefix for option names e.g. "certExtApp."
315        @type section: baestring
316        @param section: configuration file section from which to extract
317        parameters.
318        '''
319        raise NotImplementedError(CredentialWalletBase.parseConfig.__doc__)
320
321    def addCredential(self, 
322                      credential, 
323                      attributeAuthorityURI=None,
324                      bUpdateCredentialRepository=True):
325        """Add a new attribute certificate to the list of credentials held.
326
327        @type credential: determined by derived class implementation e.g.
328        SAML assertion
329        @param credential: new attribute Certificate to be added
330        @type attributeAuthorityURI: basestring
331        @param attributeAuthorityURI: input the Attribute Authority URI from
332        which credential was retrieved.  This is added to a dict to enable
333        access to a given Attribute Certificate keyed by Attribute Authority
334        URI. See the getCredential method.
335        @type bUpdateCredentialRepository: bool
336        @param bUpdateCredentialRepository: if set to True, and a repository
337        exists it will be updated with the new credentials also
338       
339        @rtype: bool
340        @return: True if certificate was added otherwise False.  - If an
341        existing certificate from the same issuer has a later expiry it will
342        take precedence and the new input certificate is ignored."""
343        raise NotImplementedError(CredentialWalletBase.addCredential.__doc__)
344           
345    def audit(self):
346        """Check the credentials held in the wallet removing any that have
347        expired or are otherwise invalid."""
348        raise NotImplementedError(CredentialWalletBase.audit.__doc__)
349
350    def updateCredentialRepository(self, auditCred=True):
351        """Copy over non-persistent credentials held by wallet into the
352        perminent repository.
353       
354        @type auditCred: bool
355        @param auditCred: filter existing credentials in the repository
356        removing invalid ones"""
357        raise NotImplementedError(
358                    CredentialWalletBase.updateCredentialRepository.__doc__)
359       
360    def _getCredentials(self):
361        """Get Property method.  Credentials dict is read-only but also see
362        addCredential method
363       
364        @rtype: dict
365        @return: cached ACs indesed by issuing organisation name"""
366        return self.__credentials
367
368    # Publish attribute
369    credentials = property(fget=_getCredentials,
370                           doc="List of credentials linked to issuing "
371                               "authorities")
372
373    def _getCredentialsKeyedByURI(self):
374        """Get Property method for credentials keyed by Attribute Authority URI
375        Credentials dict is read-only but also see addCredential method
376       
377        @rtype: dict
378        @return: cached ACs indexed by issuing Attribute Authority"""
379        return self.__credentialsKeyedByURI
380   
381    # Publish attribute
382    credentialsKeyedByURI = property(fget=_getCredentialsKeyedByURI,
383                                     doc="List of Attribute Certificates "
384                                         "linked to attribute authority URI")
385       
386    def _getUserId(self):
387        return self.__userId
388
389    def _setUserId(self, value):
390        if not isinstance(value, basestring):
391            raise TypeError('Expecting string type for "userId"; got %r '
392                            'instead' % type(value))
393        self.__userId = value
394
395    userId = property(_getUserId, _setUserId, 
396                      doc="User Identity for this wallet")
397
398    def __getstate__(self):
399        '''Enable pickling for use with beaker.session'''
400        _dict = {}
401        for attrName in CredentialWalletBase.__slots__:
402            # Ugly hack to allow for derived classes setting private member
403            # variables
404            if attrName.startswith('__'):
405                attrName = "_CredentialWalletBase" + attrName
406               
407            _dict[attrName] = getattr(self, attrName)
408           
409        return _dict
410 
411    def __setstate__(self, attrDict):
412        '''Enable pickling for use with beaker.session'''
413        for attrName, val in attrDict.items():
414            setattr(self, attrName, val)
415
416
417class SAMLCredentialWallet(CredentialWalletBase):
418    """CredentialWallet for Earth System Grid supporting caching of SAML
419    Attribute Assertions
420    """
421    CONFIG_FILE_OPTNAMES = CredentialWalletBase.CONFIG_FILE_OPTNAMES + (
422                           "clockSkewTolerance", )
423    __slots__ = ("__clockSkewTolerance",)
424   
425    CREDENTIAL_REPOSITORY_NOT_SUPPORTED_MSG = ("SAMLCredentialWallet doesn't "
426                                               "support the "
427                                               "CredentialRepository "
428                                               "interface")
429
430    def __init__(self):
431        super(SAMLCredentialWallet, self).__init__()
432        self.__clockSkewTolerance = timedelta(seconds=0.)
433
434    def _getClockSkewTolerance(self):
435        return self.__clockSkewTolerance
436
437    def _setClockSkewTolerance(self, value):
438        if isinstance(value, (float, int, long)):
439            self.__clockSkewTolerance = timedelta(seconds=value)
440           
441        elif isinstance(value, basestring):
442            self.__clockSkewTolerance = timedelta(seconds=float(value))
443        else:
444            raise TypeError('Expecting float, int, long or string type for '
445                            '"clockSkewTolerance"; got %r' % type(value))
446
447    clockSkewTolerance = property(_getClockSkewTolerance, 
448                                  _setClockSkewTolerance, 
449                                  doc="Allow a tolerance (seconds) for "
450                                      "checking timestamps of the form: "
451                                      "notBeforeTime - tolerance < now < "
452                                      "notAfterTime + tolerance")
453
454    def parseConfig(self, cfg, prefix='', section='DEFAULT'):
455        '''Read config file settings
456        @type cfg: basestring /ConfigParser derived type
457        @param cfg: configuration file path or ConfigParser type object
458        @type prefix: basestring
459        @param prefix: prefix for option names e.g. "certExtApp."
460        @type section: baestring
461        @param section: configuration file section from which to extract
462        parameters.
463        ''' 
464        if isinstance(cfg, basestring):
465            cfgFilePath = os.path.expandvars(cfg)
466            _cfg = CaseSensitiveConfigParser()
467            _cfg.read(cfgFilePath)
468           
469        elif isinstance(cfg, ConfigParser):
470            _cfg = cfg   
471        else:
472            raise AttributeError('Expecting basestring or ConfigParser type '
473                                 'for "cfg" attribute; got %r type' % type(cfg))
474       
475        prefixLen = len(prefix)
476        for optName, val in _cfg.items(section):
477            if prefix and optName.startswith(prefix):
478                optName = optName[prefixLen:]
479               
480            setattr(self, optName, val)
481
482    def addCredential(self, 
483                      credential, 
484                      attributeAuthorityURI=None,
485                      bUpdateCredentialRepository=False,
486                      verifyCredential=True):
487        """Add a new assertion to the list of assertion credentials held.
488
489        @type credential: SAML assertion
490        @param credential: new assertion to be added
491        @type attributeAuthorityURI: basestring
492        @param attributeAuthorityURI: input the Attribute Authority URI from
493        which credential was retrieved.  This is added to a dict to enable
494        access to a given Attribute Certificate keyed by Attribute Authority
495        URI. See the getCredential method.
496        @type bUpdateCredentialRepository: bool
497        @param bUpdateCredentialRepository: if set to True, and a repository
498        exists it will be updated with the new credentials also. Nb. a derived
499        class will need to be implemented to enable this capability - see
500        the updateCredentialRepository method.
501        @type verifyCredential: bool
502        @param verifyCredential: if set to True, test validity of credential
503        by calling isValidCredential method.
504       
505        @rtype: bool
506        @return: True if credential was added otherwise False.  - If an
507        existing certificate from the same issuer has a later expiry it will
508        take precedence and the new input certificate is ignored."""
509       
510        # Check input
511        if not isinstance(credential, Assertion):
512            raise CredentialWalletError("Input credential must be an "
513                                        "%r type object" % Assertion)       
514
515        if verifyCredential and not self.isValidCredential(credential):
516            raise CredentialWalletError("Validity time error with assertion %r"
517                                        % credential)
518       
519        # Check to see if there is an existing Attribute Certificate held
520        # that was issued by the same host.  If so, compare the expiry time.
521        # The one with the latest expiry will be retained and the other
522        # ignored
523        bUpdateCred = True
524        if credential.issuer is None:
525            raise AttributeError("Adding SAML assertion to wallet: no issuer "
526                                 "set")
527           
528        issuerName = credential.issuer.value
529       
530        if issuerName in self.credentials:
531            # There is an existing certificate held with the same issuing
532            # host name as the new certificate
533            credentialOld = self.credentials[issuerName].credential
534
535            # If the new certificate has an earlier expiry time then ignore it
536            bUpdateCred = (credential.conditions.notOnOrAfter > 
537                           credentialOld.conditions.notOnOrAfter)
538
539        if bUpdateCred:
540            thisCredential = CredentialContainer(Assertion)
541            thisCredential.credential = credential
542            thisCredential.issuerName = issuerName
543            thisCredential.attributeAuthorityURI = attributeAuthorityURI
544           
545            self.credentials[issuerName] = thisCredential
546           
547            if attributeAuthorityURI:
548                self.credentialsKeyedByURI[
549                    attributeAuthorityURI] = thisCredential
550
551            # Update the Credentials Repository - the permanent store of user
552            # authorisation credentials.  This allows credentials for previous
553            # sessions to be re-instated
554            if bUpdateCredentialRepository:
555                self.updateCredentialRepository()
556
557        # Flag to caller to indicate whether the input certificate was added
558        # to the credentials or an exsiting certificate from the same issuer
559        # took precedence
560        return bUpdateCred
561                       
562    def audit(self):
563        """Check the credentials held in the wallet removing any that have
564        expired or are otherwise invalid."""
565
566        log.debug("SAMLCredentialWallet.audit ...")
567       
568        for issuerName, issuerEntry in self.credentials.items():
569            if not self.isValidCredential(issuerEntry.credential):
570                self.credentialsKeyedByURI.pop(
571                    issuerEntry.attributeAuthorityURI,
572                    None)
573                   
574                del self.credentials[issuerName]
575
576    def updateCredentialRepository(self, auditCred=True):
577        """No Credential Repository support is required"""
578        msg = SAMLCredentialWallet.CREDENTIAL_REPOSITORY_NOT_SUPPORTED_MSG
579        log.warning(msg)
580        warnings.warn(msg)
581
582    def isValidCredential(self, assertion):
583        """Validate SAML assertion time validity"""
584        utcNow = datetime.utcnow()
585        if utcNow < assertion.conditions.notBefore - self.clockSkewTolerance:
586            msg = ('The current clock time [%s] is before the SAML Attribute '
587                   'Response assertion conditions not before time [%s] ' 
588                   '(with clock skew tolerance = %s)' % 
589                   (SAMLDateTime.toString(utcNow),
590                    assertion.conditions.notBefore,
591                    self.clockSkewTolerance))
592            log.warning(msg)
593            return False
594           
595        if (utcNow >= 
596            assertion.conditions.notOnOrAfter + self.clockSkewTolerance):
597            msg = ('The current clock time [%s] is on or after the SAML '
598                   'Attribute Response assertion conditions not on or after '
599                   'time [%s] (with clock skew tolerance = %s)' % 
600                   (SAMLDateTime.toString(utcNow),
601                    assertion.conditions.notOnOrAfter,
602                    self.clockSkewTolerance))
603            log.warning(msg)
604            return False
605           
606        return True
607   
608    def __getstate__(self):
609        '''Enable pickling for use with beaker.session'''
610        _dict = super(SAMLCredentialWallet, self).__getstate__()
611       
612        for attrName in SAMLCredentialWallet.__slots__:
613            # Ugly hack to allow for derived classes setting private member
614            # variables
615            if attrName.startswith('__'):
616                attrName = "_SAMLCredentialWallet" + attrName
617               
618            _dict[attrName] = getattr(self, attrName)
619           
620        return _dict
621
622   
623class NDGCredentialWallet(CredentialWalletBase):
624    """Volatile store of user credentials associated with a user session
625   
626    @type userX509Cert: string / M2Crypto.X509.X509 /
627    ndg.security.common.X509.X509Cert
628    @ivar userX509Cert: X.509 certificate for user (property attribute)
629   
630    @type userPriKey: string / M2Crypto.RSA.RSA
631    @ivar userPriKey: private key for user cert (property attribute)
632   
633    @type issuingX509Cert: string / ndg.security.common.X509.X509Cert
634    @ivar issuingX509Cert: X.509 cert for issuer of user cert (property
635    attribute)
636   
637    @type attributeAuthorityURI: string
638    @ivar attributeAuthorityURI: URI of Attribute Authority to make
639    requests to.  Setting this ALSO creates an AttributeAuthorityClient
640    instance _attributeAuthorityClnt.  - See attributeAuthorityURI property for
641    details. (property attribute)
642   
643    @type attributeAuthority: ndg.security.server.attributeauthority.AttributeAuthority
644    @ivar attributeAuthority: Attribute Authority to make requests to. 
645    attributeAuthorityURI takes precedence over this keyword i.e. if an
646    attributeAuthorityURI has been set, then calls are made to the AA web
647    service at this location rather to any self.attributeAuthority running
648    locally. (property attribute)
649   
650    @type caCertFilePathList: string (for single file), list or tuple
651    @ivar caCertFilePathList: Certificate Authority's certificates - used
652    in validation of signed Attribute Certificates and WS-Security
653    signatures of incoming messages.  If not set here, it must
654    be input in call to getAttCert. (property attribute)
655           
656    @type credentialRepository: instance of CredentialRepository derived
657    class
658    @ivar credentialRepository: Credential Repository instance.   (property
659    attribute).  If not set, defaults to NullCredentialRepository type - see
660    class below...
661
662   
663    @type mapFromTrustedHosts: bool
664    @ivar mapFromTrustedHosts sets behaviour for getAttCert().  If
665    set True and authorisation fails with the given Attribute Authority,
666    attempt to get authorisation using Attribute Certificates issued by
667    other trusted AAs. (property attribute)
668   
669    @type rtnExtAttCertList: bool
670    @ivar rtnExtAttCertList: behaviour for getAttCert().  If True, and
671    authorisation fails with the given Attribute Authority, return a list
672    of Attribute Certificates from other trusted AAs which could be used
673    to obtain a mapped Attribute Certificate on a subsequent authorisation
674    attempt. (property attribute)
675   
676    @type attCertRefreshElapse: float / int
677    @ivar attCertRefreshElapse: used by getAttCert to determine
678    whether to replace an existing AC in the cache with a fresh one.  If
679    the existing one has less than attCertRefreshElapse time in seconds
680    left before expiry then replace it. (property attribute)
681   
682    @type wssCfgKw: dict
683    @ivar wssCfgKw: keywords to WS-Security SignatureHandler
684    used for Credential Wallet's SOAP interface to Attribute Authorities.
685    (property attribute)
686           
687    @type _credentialRepository: ndg.security.common.CredentialRepository or
688    derivative
689    @ivar _credentialRepository: reference to Credential Repository object. 
690    An optional non-volatile cache for storage of wallet info which can be
691    later restored. (Don't reference directly - see equivalent property
692    attribute)
693
694    @type _mapFromTrustedHosts: bool
695    @ivar _mapFromTrustedHosts: if true, allow a mapped attribute certificate
696    to obtained in a getAttCert call.  Set false to prevent mappings.
697    (Don't reference directly - see equivalent property attribute)
698
699    @type _rtnExtAttCertList: bool
700    @ivar _rtnExtAttCertList: if true, return a list of external attribute
701    certificates from getAttCert call. (Don't reference directly - see
702    equivalent property attribute)
703
704    @type __dn: ndg.security.common.X509.X500DN
705    @ivar __dn: distinguished name from user certificate.  (Don't reference
706    directly - see equivalent property attribute)
707
708    @type _credentials: dict       
709    @ivar _credentials: Credentials are stored as a dictionary one element per
710    attribute certificate held and indexed by certificate issuer name.
711    (Don't reference directly - see equivalent property attribute)
712
713    @type _caCertFilePathList: basestring, list, tuple or None
714    @ivar _caCertFilePathList: file path(s) to CA certificates.  If None
715    then the input is quietly ignored.  See caCertFilePathList property.
716    (Don't reference directly - see equivalent property attribute)
717
718    @type _userX509Cert: ndg.security.common.X509.X509Cert
719    @ivar _userX509Cert: X.509 user certificate instance.
720    (Don't reference directly - see equivalent property attribute)
721
722    @type _issuingX509Cert: ndg.security.common.X509.X509Cert
723    @ivar _issuingX509Cert: X.509 user certificate instance.
724    (Don't reference directly - see equivalent property attribute)
725 
726    @type _userPriKey: M2Crypto.RSA.RSA
727    @ivar _userPriKey: Private key used to sign outbound message.
728    (Don't reference directly - see equivalent property attribute)
729    """
730
731    __metaclass__ = _MetaCredentialWallet
732
733    # Names that may be set in a properties file
734    propertyDefaults = dict(
735        userX509Cert=None,
736        userX509CertFilePath=None,
737        userPriKey=None,
738        userPriKeyFilePath=None,
739        issuingX509Cert=None,
740        issuingX509CertFilePath=None,
741        caCertFilePathList=[],
742        sslCACertFilePathList=[],
743        attributeAuthority=None,
744        credentialRepository=None,
745        mapFromTrustedHosts=False,
746        rtnExtAttCertList=True,
747        attCertRefreshElapse=7200
748    )
749   
750    __slots__ = dict(
751        _cfg=None,
752        _dn=None,
753        _userPriKeyPwd=None,
754        _attributeAuthorityClnt=None,
755        _attributeAuthorityURI=None,
756        sslCACertFilePathList=[],
757        wssCfgFilePath=None,
758        wssCfgSection='DEFAULT',
759        wssCfgPrefix='',
760        wssCfgKw={}
761    )
762    __slots__.update(dict([("_" + n, v) for n, v in propertyDefaults.items()]))
763    del n
764   
765    FUTURE_DEPRECATION_MSG = (
766        "This class will be deprecated in future releases.  Use "
767        "SAMLCredentialWallet and "
768        "ndg.security.common.saml_utils.bindings.AttributeQuerySslSOAPbinding "
769        "client interface instead for retrieving and caching user attributes.")
770   
771    def __init__(self, 
772                 cfg=None, 
773                 cfgFileSection='DEFAULT', 
774                 cfgPrefix='', 
775                 wssCfgKw={},
776                 **kw):
777        """Create store of user credentials for their current session
778
779        @type cfg: string / ConfigParser object
780        @param cfg: if a string type, this is interpreted as the file path to
781        a configuration file, otherwise it will be treated as a ConfigParser
782        object
783        @type cfgSection: string
784        @param cfgSection: sets the section name to retrieve config params
785        from
786        @type cfgPrefix: basestring
787        @param cfgPrefix: apply a prefix to all NDGCredentialWallet config
788        params so that if placed in a file with other parameters they can be
789        distinguished
790        @type cfgKw: dict
791        @param cfgKw: set parameters as key value pairs."""
792
793        warnings.warn(NDGCredentialWallet.FUTURE_DEPRECATION_MSG)
794        log.warning(NDGCredentialWallet.FUTURE_DEPRECATION_MSG)
795        log.debug("Calling NDGCredentialWallet.__init__ ...")
796
797        if not _fourSuiteSignatureHandlerAvailable:
798            raise ImportError("4Suite-XML based WS-Security Signature handler "
799                              "is not installed.  Install it to enable this "
800                              "class")
801           
802        super(NDGCredentialWallet, self).__init__()
803       
804        # Initialise attributes
805        for k, v in NDGCredentialWallet.__slots__.items():
806            setattr(self, k, v)
807           
808        # Update attributes from a config file
809        if cfg:
810            self.parseConfig(cfg, section=cfgFileSection, prefix=cfgPrefix)
811
812        # Update attributes from keywords passed - set user private key
813        # password first if it's present.  This is to avoid an error setting
814        # the private key
815        self.userPriKeyPwd = kw.pop('userPriKeyPwd', None)
816        for k, v in kw.items():
817            setattr(self, k, v)
818
819        # Get the distinguished name from the user certificate
820        if self._userX509Cert:
821            self._dn = self._userX509Cert.dn.serialise()
822
823        # Make a connection to the Credentials Repository
824        if self._credentialRepository is None:
825            log.info('Applying default CredentialRepository %r for user '
826                     '"%s"' % (NullCredentialRepository, self.userId))
827            self._credentialRepository = NullCredentialRepository()
828        else:
829            log.info('Checking CredentialRepository for credentials for user '
830                     '"%s"' % self.userId)
831           
832            if not issubclass(self._credentialRepository, CredentialRepository):
833                raise CredentialWalletError("Input Credential Repository "
834                                            "instance must be of a class "
835                                            "derived from "
836                                            "\"CredentialRepository\"")
837   
838       
839            # Check for valid attribute certificates for the user
840            try:
841                self._credentialRepository.auditCredentials(self.userId)
842                userCred=self._credentialRepository.getCredentials(self.userId)
843   
844            except Exception, e:
845                log.error("Error updating wallet with credentials from "
846                          "repository: %s" % e)
847                raise
848   
849   
850            # Update wallet with attribute certificates stored in the
851            # repository.  Store ID and certificate instantiated as an AttCert
852            # type
853            try:
854                for cred in userCred: 
855                    attCert = AttCert.Parse(cred.attCert)
856                    issuerName = attCert['issuerName']
857                   
858                    self.credentials[issuerName] = {'id':cred.id, 
859                                                     'attCert':attCert}   
860            except Exception, e:
861                try:
862                    raise CredentialWalletError("Error parsing Attribute "
863                        "Certificate ID '%s' retrieved from the " 
864                        "Credentials Repository: %s" % (cred.id, e))           
865                except:
866                    raise CredentialWalletError("Error parsing Attribute "
867                                          "Certificate retrieved from the "
868                                          "Credentials Repository: %s:" % e)
869           
870            # Filter out expired or otherwise invalid certificates
871            self.audit()
872   
873    def __getstate__(self):
874        '''Enable pickling for use with beaker.session'''
875        _dict = super(NDGCredentialWallet, self).__getstate__()
876       
877        for attrName in NDGCredentialWallet.__slots__:
878            # Ugly hack to allow for derived classes setting private member
879            # variables
880            if attrName.startswith('__'):
881                attrName = "_NDGCredentialWallet" + attrName
882               
883            _dict[attrName] = getattr(self, attrName)
884           
885        return _dict
886       
887    def parseConfig(self, cfg, prefix='', section='DEFAULT'):
888        '''Extract parameters from cfg config object'''
889       
890        if isinstance(cfg, basestring):
891            cfgFilePath = os.path.expandvars(cfg)
892            self._cfg = None
893        else:
894            cfgFilePath = None
895            self._cfg = cfg
896           
897        # Configuration file properties are held together in a dictionary
898        readAndValidate = INIPropertyFileWithValidation()
899        prop = readAndValidate(cfgFilePath,
900                               cfg=self._cfg,
901                               validKeys=NDGCredentialWallet.propertyDefaults,
902                               prefix=prefix,
903                               sections=(section,))
904       
905        # Keep a copy of config for use by WS-Security SignatureHandler parser
906        if self._cfg is None:
907            self._cfg = readAndValidate.cfg
908       
909        # Copy prop dict into object attributes - __slots__ definition and
910        # property methods will ensure only the correct attributes are set
911        # Set user private key password first if it's present.  This is to
912        # avoid an error setting the private key
913        self.userPriKeyPwd = prop.pop('userPriKeyPwd', None)
914        for key, val in prop.items():
915            setattr(self, key, val)
916
917
918    def _getAttCertRefreshElapse(self):
919        """Get property method for Attribute Certificate wallet refresh time
920        @rtype: float or int
921        @return: "elapse time in seconds"""
922        return self._attCertRefreshElapse
923   
924    def _setAttCertRefreshElapse(self, val):
925        """Set property method for Attribute Certificate wallet refresh time
926        @type val: float or int
927        @param val: "elapse time in seconds"""
928        if isinstance(val, (float, int)):
929            self._attCertRefreshElapse = val
930           
931        elif isinstance(val, basestring):
932            self._attCertRefreshElapse = float(val)
933        else:
934            raise AttributeError("Expecting int, float or string type input "
935                                 "for attCertRefreshElapse")
936           
937    attCertRefreshElapse = property(fget=_getAttCertRefreshElapse, 
938                                    fset=_setAttCertRefreshElapse,
939                                    doc="If an existing one has AC less than "
940                                        "attCertRefreshElapse time in seconds "
941                                        "left before expiry then replace it")
942   
943    def _setX509Cert(self, cert):
944        """filter and convert input cert to signing verifying cert set
945        property methods.  For signingCert, set to None if it is not to be
946        included in the SOAP header.  For verifyingCert, set to None if this
947        cert can be expected to be retrieved from the SOAP header of the
948        message to be verified
949       
950        @type: ndg.security.common.X509.X509Cert / M2Crypto.X509.X509 /
951        string or None
952        @param cert: X.509 certificate. 
953       
954        @rtype ndg.security.common.X509.X509Cert
955        @return X.509 certificate object"""
956       
957        if cert is None or isinstance(cert, X509Cert):
958            # ndg.security.common.X509.X509Cert type / None
959            return cert
960           
961        elif isinstance(cert, X509.X509):
962            # M2Crypto.X509.X509 type
963            return X509Cert(m2CryptoX509=cert)
964           
965        elif isinstance(cert, basestring):
966            return X509Cert.Parse(cert)
967       
968        else:
969            raise AttributeError("X.509 Cert. must be type: "
970                                 "ndg.security.common.X509.X509Cert, "
971                                 "M2Crypto.X509.X509 or a base64 encoded "
972                                 "string")
973
974    def _setUserX509Cert(self, userX509Cert):
975        "Set property method for X.509 user cert."
976        self._userX509Cert = self._setX509Cert(userX509Cert)       
977
978    def _getUserX509Cert(self):
979        """Get user cert X509Cert instance"""
980        return self._userX509Cert
981
982    userX509Cert = property(fget=_getUserX509Cert,
983                            fset=_setUserX509Cert,
984                            doc="X.509 user certificate instance")
985 
986    def _setUserX509CertFilePath(self, filePath):
987        "Set user X.509 cert file path property method"
988       
989        if isinstance(filePath, basestring):
990            filePath = os.path.expandvars(filePath)
991            self._userX509Cert = X509Cert.Read(filePath)
992           
993        elif filePath is not None:
994            raise AttributeError("User X.509 cert. file path must be a valid "
995                                 "string")
996       
997        self._userX509CertFilePath = filePath
998               
999    userX509CertFilePath = property(fset=_setUserX509CertFilePath,
1000                                    doc="File path to user X.509 cert.")
1001   
1002    def _setIssuingX509Cert(self, issuingX509Cert):
1003        "Set property method for X.509 user cert."
1004        self._issuingX509Cert = self._setX509Cert(issuingX509Cert)
1005       
1006    def _getIssuingX509Cert(self):
1007        """Get user cert X509Cert instance"""
1008        return self._issuingX509Cert
1009
1010    issuingX509Cert = property(fget=_getIssuingX509Cert,
1011                               fset=_setIssuingX509Cert,
1012                               doc="X.509 user certificate instance")
1013 
1014    def _setIssuerX509CertFilePath(self, filePath):
1015        "Set user X.509 cert file path property method"
1016       
1017        if isinstance(filePath, basestring):
1018            filePath = os.path.expandvars(filePath)
1019            self._issuerX509Cert = X509Cert.Read(filePath)
1020           
1021        elif filePath is not None:
1022            raise AttributeError("User X.509 cert. file path must be a valid "
1023                                 "string")
1024       
1025        self._issuerX509CertFilePath = filePath
1026               
1027    issuerX509CertFilePath = property(fset=_setIssuerX509CertFilePath,
1028                                      doc="File path to user X.509 cert. "
1029                                          "issuing cert.")     
1030
1031    def _getUserPriKey(self):
1032        "Get method for user private key"
1033        return self._userPriKey
1034   
1035    def _setUserPriKey(self, userPriKey):
1036        """Set method for user private key
1037       
1038        Nb. if input is a string, userPriKeyPwd will need to be set if
1039        the key is password protected.
1040       
1041        @type userPriKey: M2Crypto.RSA.RSA / string
1042        @param userPriKey: private key used to sign message"""
1043       
1044        if userPriKey is None:
1045            self._userPriKey = None
1046        elif isinstance(userPriKey, basestring):
1047            pwdCallback = lambda *ar, **kw: self._userPriKeyPwd
1048            self._userPriKey = RSA.load_key_string(userPriKey,
1049                                                   callback=pwdCallback)
1050        elif isinstance(userPriKey, RSA.RSA):
1051            self._userPriKey = userPriKey         
1052        else:
1053            raise AttributeError("user private key must be a valid "
1054                                 "M2Crypto.RSA.RSA type or a string")
1055               
1056    userPriKey = property(fget=_getUserPriKey,
1057                          fset=_setUserPriKey,
1058                          doc="User private key if set, used to sign outbound "
1059                              "messages to Attribute authority")
1060
1061    def _setUserPriKeyFilePath(self, filePath):
1062        "Set user private key file path property method"
1063       
1064        if isinstance(filePath, basestring):
1065            filePath = os.path.expandvars(filePath)
1066            try:
1067                # Read Private key to sign with   
1068                priKeyFile = BIO.File(open(filePath)) 
1069                pwdCallback = lambda *ar, **kw: self._userPriKeyPwd
1070                self._userPriKey = RSA.load_key_bio(priKeyFile, 
1071                                                    callback=pwdCallback)   
1072            except Exception, e:
1073                raise AttributeError("Setting user private key: %s" % e)
1074       
1075        elif filePath is not None:
1076            raise AttributeError("Private key file path must be a valid "
1077                                 "string or None")
1078       
1079        self._userPriKeyFilePath = filePath
1080       
1081    userPriKeyFilePath = property(fset=_setUserPriKeyFilePath,
1082                                  doc="File path to user private key")
1083 
1084    def _setUserPriKeyPwd(self, userPriKeyPwd):
1085        "Set method for user private key file password"
1086        if userPriKeyPwd is not None and not isinstance(userPriKeyPwd, 
1087                                                        basestring):
1088            raise AttributeError("Signing private key password must be None "
1089                                 "or a valid string")
1090       
1091        # Explicitly convert to string as M2Crypto OpenSSL wrapper fails with
1092        # unicode type
1093        self._userPriKeyPwd = str(userPriKeyPwd)
1094
1095    def _getUserPriKeyPwd(self):
1096        "Get property method for user private key"
1097        return self._userPriKeyPwd
1098       
1099    userPriKeyPwd = property(fset=_setUserPriKeyPwd,
1100                             fget=_getUserPriKeyPwd,
1101                             doc="Password protecting user private key file")
1102       
1103    def _getCACertFilePathList(self):
1104        """Get CA cert or certs used to validate AC signatures and signatures
1105        of peer SOAP messages.
1106       
1107        @rtype caCertFilePathList: basestring, list or tuple
1108        @return caCertFilePathList: file path(s) to CA certificates."""
1109        return self._caCertFilePathList
1110   
1111    def _setCACertFilePathList(self, caCertFilePathList):
1112        """Set CA cert or certs to validate AC signatures, signatures
1113        of Attribute Authority SOAP responses and SSL connections where
1114        AA SOAP service is run over SSL.
1115       
1116        @type caCertFilePathList: basestring, list, tuple or None
1117        @param caCertFilePathList: file path(s) to CA certificates.  If None
1118        then the input is quietly ignored."""
1119       
1120        if isinstance(caCertFilePathList, basestring):
1121           self._caCertFilePathList = [caCertFilePathList]
1122           
1123        elif isinstance(caCertFilePathList, list):
1124           self._caCertFilePathList = caCertFilePathList
1125           
1126        elif isinstance(caCertFilePathList, tuple):
1127           self._caCertFilePathList = list(caCertFilePathList)
1128
1129        elif caCertFilePathList is not None:
1130            raise TypeError('Expecting string/list/tuple or None type for '
1131                            '"caCertFilePathList"; got %r type' % 
1132                            type(caCertFilePathList))     
1133       
1134    caCertFilePathList = property(fget=_getCACertFilePathList,
1135                                  fset=_setCACertFilePathList,
1136                                  doc="CA Certificates - used for "
1137                                      "verification of AC and SOAP message "
1138                                      "signatures")
1139
1140    def _getAttributeAuthorityURI(self):
1141        return self._attributeAuthorityURI
1142
1143    def _setAttributeAuthorityURI(self, value):
1144        """String or None type are allowed - The URI may be set to None to
1145        flag that a local Attribute Authority instance is being invoked rather
1146        one hosted via a remote URI
1147        """
1148        if not isinstance(value, (basestring, type(None))):
1149            raise TypeError('Expecting string or None type for '
1150                            '"attributeAuthorityURI"; got %r instead' % 
1151                            type(value))
1152           
1153        self._attributeAuthorityURI = value
1154         
1155        if value is not None:     
1156            # Re-initialize local instance
1157            self._attributeAuthority = NDGCredentialWallet.propertyDefaults[
1158                                                        'attributeAuthority']
1159
1160    attributeAuthorityURI = property(fget=_getAttributeAuthorityURI,
1161                                     fset=_setAttributeAuthorityURI,
1162                                     doc="Attribute Authority address - "
1163                                         "setting also sets up "
1164                                         "AttributeAuthorityClient instance!")
1165
1166    def _getAttributeAuthority(self):
1167        """Get property method for Attribute Authority Web Service client
1168        instance.  Use attributeAuthorityURI propert to set up
1169        attributeAuthorityClnt
1170       
1171        @rtype attributeAuthority: ndg.security.server.attributeauthority.AttributeAuthority
1172        @return attributeAuthority: Attribute Authority instance"""
1173        return self._attributeAuthority
1174
1175    def _setAttributeAuthority(self, attributeAuthority):
1176        """Set property method for Attribute Authority Web Service instance to
1177        connect to.  This method ALSO RESETS attributeAuthorityURI - the
1178        address of a remote Attribute Authority - to None
1179       
1180        @type attributeAuthority: ndg.security.server.attributeauthority.AttributeAuthority
1181        @param attributeAuthority: Attribute Authority instance.
1182        """
1183        if not isinstance(attributeAuthority, (AttributeAuthority, type(None))):
1184            raise AttributeError("Expecting %r or None type for "
1185                                 "\"attributeAuthority\" attribute; got %r" % 
1186                                 (AttributeAuthority, type(attributeAuthority)))
1187           
1188        self._attributeAuthority = attributeAuthority
1189       
1190        # Re-initialize setting for remote service
1191        self.attributeAuthorityURI = None
1192           
1193    attributeAuthority = property(fget=_getAttributeAuthority,
1194                                  fset=_setAttributeAuthority, 
1195                                  doc="Attribute Authority instance")
1196
1197
1198    def _getMapFromTrustedHosts(self):
1199        """Get property method for boolean flag - if set to True it allows
1200        role mapping to be attempted when connecting to an Attribute Authority
1201       
1202        @type mapFromTrustedHosts: bool
1203        @param mapFromTrustedHosts: set to True to try role mapping in AC
1204        requests to Attribute Authorities"""
1205        return self._mapFromTrustedHosts
1206
1207    def _setMapFromTrustedHosts(self, mapFromTrustedHosts):
1208        """Set property method for boolean flag - if set to True it allows
1209        role mapping to be attempted when connecting to an Attribute Authority
1210       
1211        @type mapFromTrustedHosts: bool
1212        @param mapFromTrustedHosts: Attribute Authority Web Service."""
1213        if not isinstance(mapFromTrustedHosts, bool):
1214            raise AttributeError("Expecting %r for mapFromTrustedHosts "
1215                                 "attribute" % bool)
1216           
1217        self._mapFromTrustedHosts = mapFromTrustedHosts
1218           
1219    mapFromTrustedHosts = property(fget=_getMapFromTrustedHosts,
1220                                   fset=_setMapFromTrustedHosts, 
1221                                   doc="Set to True to enable mapped AC "
1222                                       "requests")
1223
1224    def _getRtnExtAttCertList(self):
1225        """Get property method for Attribute Authority Web Service client
1226        instance.  Use rtnExtAttCertListURI propert to set up
1227        rtnExtAttCertListClnt
1228       
1229        @type rtnExtAttCertList: bool
1230        @param rtnExtAttCertList: """
1231        return self._rtnExtAttCertList
1232
1233    def _setRtnExtAttCertList(self, rtnExtAttCertList):
1234        """Set property method for boolean flag - when a AC request fails,
1235        return a list of candidate ACs that could be used to re-try with in
1236        order to get mapped AC.
1237       
1238        @type rtnExtAttCertList: bool
1239        @param rtnExtAttCertList: set to True to configure getAttCert to return
1240        a list of ACs that could be used in a re-try to get a mapped AC from
1241        the target Attribute Authority."""
1242        if not isinstance(rtnExtAttCertList, bool):
1243            raise AttributeError("Expecting %r for rtnExtAttCertList "
1244                                 "attribute" % bool)
1245           
1246        self._rtnExtAttCertList = rtnExtAttCertList
1247           
1248    rtnExtAttCertList = property(fget=_getRtnExtAttCertList,
1249                                 fset=_setRtnExtAttCertList, 
1250                                 doc="Set to True to enable mapped AC "
1251                                     "requests")
1252
1253    def isValid(self, **x509CertKeys):
1254        """Check wallet's user cert.  If expired return False
1255       
1256        @type **x509CertKeys: dict
1257        @param **x509CertKeys: keywords applying to
1258        ndg.security.common.X509.X509Cert.isValidTime method"""
1259        if self._userX509Cert is not None:
1260            return self._userX509Cert.isValidTime(**x509CertKeys)
1261        else:
1262            log.warning("NDGCredentialWallet.isValid: no user certificate set in "
1263                        "wallet")
1264            return True
1265
1266    def addCredential(self, 
1267                      attCert, 
1268                      attributeAuthorityURI=None,
1269                      bUpdateCredentialRepository=True):
1270        """Add a new attribute certificate to the list of credentials held.
1271
1272        @type attCert:
1273        @param attCert: new attribute Certificate to be added
1274        @type attributeAuthorityURI: basestring
1275        @param attributeAuthorityURI: input the Attribute Authority URI from
1276        which attCert was retrieved.  This is added to a dict to enable access
1277        to a given Attribute Certificate keyed by Attribute Authority URI.
1278        See the getCredential method.
1279        @type bUpdateCredentialRepository: bool
1280        @param bUpdateCredentialRepository: if set to True, and a repository
1281        exists it will be updated with the new credentials also
1282       
1283        @rtype: bool
1284        @return: True if certificate was added otherwise False.  - If an
1285        existing certificate from the same issuer has a later expiry it will
1286        take precedence and the new input certificate is ignored."""
1287
1288        # Check input
1289        if not isinstance(attCert, AttCert):
1290            raise CredentialWalletError("Credential must be an %r type object" %
1291                                        AttCert)
1292           
1293        # Check certificate validity
1294        try:
1295            attCert.isValid(raiseExcep=True)
1296           
1297        except AttCertError, e:
1298            raise CredentialWalletError("Adding Credential: %s" % e)
1299
1300        # Check to see if there is an existing Attribute Certificate held
1301        # that was issued by the same host.  If so, compare the expiry time.
1302        # The one with the latest expiry will be retained and the other
1303        # ingored
1304        bUpdateCred = True
1305        issuerName = attCert['issuerName']
1306       
1307        if issuerName in self.credentials:
1308            # There is an existing certificate held with the same issuing
1309            # host name as the new certificate
1310            attCertOld = self.credentials[issuerName].credential
1311
1312            # Get expiry times in datetime format to allow comparison
1313            dtAttCertOldNotAfter = attCertOld.getValidityNotAfter(\
1314                                                            asDatetime=True)
1315            dtAttCertNotAfter = attCert.getValidityNotAfter(asDatetime=True)
1316
1317            # If the new certificate has an earlier expiry time then ignore it
1318            bUpdateCred = dtAttCertNotAfter > dtAttCertOldNotAfter
1319
1320               
1321        if bUpdateCred:
1322            thisCredential = CredentialContainer(AttCert)
1323            thisCredential.credential = attCert
1324            thisCredential.issuerName = issuerName
1325            thisCredential.attributeAuthorityURI = attributeAuthorityURI
1326           
1327            self.credentials[issuerName] = thisCredential
1328           
1329            if attributeAuthorityURI:
1330                self.credentialsKeyedByURI[
1331                    attributeAuthorityURI] = thisCredential
1332           
1333            # Update the Credentials Repository - the permanent store of user
1334            # authorisation credentials.  This allows credentials for previous
1335            # sessions to be re-instated
1336            if self._credentialRepository and bUpdateCredentialRepository:
1337                self.updateCredentialRepository()
1338
1339        # Flag to caller to indicate whether the input certificate was added
1340        # to the credentials or an exsiting certificate from the same issuer
1341        # took precedence
1342        return bUpdateCred
1343           
1344
1345    def audit(self):
1346        """Check the credentials held in the wallet removing any that have
1347        expired or are otherwise invalid."""
1348
1349        log.debug("NDGCredentialWallet.audit ...")
1350       
1351        # Nb. No signature check is carried out.  To do a check, access is
1352        # needed to the cert of the CA that issued the Attribute Authority's
1353        # cert
1354        #
1355        # P J Kershaw 12/09/05
1356        for key, val in self.credentials.items():
1357            if not val.credential.isValid(chkSig=False):
1358                del self.credentials[key]
1359
1360    def updateCredentialRepository(self, auditCred=True):
1361        """Copy over non-persistent credentials held by wallet into the
1362        perminent repository.
1363       
1364        @type auditCred: bool
1365        @param auditCred: filter existing credentials in the repository
1366        removing invalid ones"""
1367
1368        log.debug("NDGCredentialWallet.updateCredentialRepository ...")
1369       
1370        if not self._credentialRepository:
1371            raise CredentialWalletError("No Credential Repository has been "
1372                                        "created for this wallet")
1373                           
1374        # Filter out invalid certs unless auditCred flag is explicitly set to
1375        # false
1376        if auditCred: self.audit()
1377
1378        # Update the database - only add new entries i.e. with an ID of -1
1379        attCertList = [i.credential for i in self.credentials.values() 
1380                       if i.id == -1]
1381
1382        self._credentialRepository.addCredentials(self.userId, attCertList)
1383
1384    def _createAttributeAuthorityClnt(self, attributeAuthorityURI):
1385        """Set up a client to an Attribute Authority with the given URI
1386       
1387        @type attributeAuthorityURI: string
1388        @param attributeAuthorityURI: Attribute Authority Web Service URI.
1389
1390        @rtype: ndg.security.common.attributeauthority.AttributeAuthorityClient
1391        @return: new Attribute Authority client instance"""
1392
1393        log.debug('NDGCredentialWallet._createAttributeAuthorityClnt for '
1394                  'service: "%s"' % attributeAuthorityURI)
1395
1396        attributeAuthorityClnt = AttributeAuthorityClient(
1397                            uri=attributeAuthorityURI,
1398                            sslCACertFilePathList=self.sslCACertFilePathList,
1399                            cfg=self.wssCfgFilePath or self._cfg,
1400                            cfgFileSection=self.wssCfgSection,
1401                            cfgFilePrefix=self.wssCfgPrefix,
1402                            **(self.wssCfgKw or {}))
1403       
1404        # If a user certificate is set, use this to sign messages instead of
1405        # the default settings in the WS-Security config. 
1406        if attributeAuthorityClnt.signatureHandler is not None and \
1407           self.userPriKey is not None:
1408            if self.issuingX509Cert is not None:
1409                # Pass a chain of certificates -
1410                # Initialise WS-Security signature handling to pass
1411                # BinarySecurityToken containing user cert and cert for user
1412                # cert issuer
1413                attributeAuthorityClnt.signatureHandler.reqBinSecTokValType = \
1414                            SignatureHandler.binSecTokValType["X509PKIPathv1"]
1415                attributeAuthorityClnt.signatureHandler.signingCertChain = (
1416                                    self.issuingX509Cert, self.userX509Cert)               
1417
1418                attributeAuthorityClnt.signatureHandler.signingPriKey = \
1419                                                            self.userPriKey
1420            elif self.userX509Cert is not None:
1421                # Pass user cert only - no need to pass a cert chain. 
1422                # This type of token is more likely to be supported by the
1423                # various WS-Security toolkits
1424                attributeAuthorityClnt.signatureHandler.reqBinSecTokValType = \
1425                                    SignatureHandler.binSecTokValType["X509v3"]
1426                attributeAuthorityClnt.signatureHandler.signingCert = \
1427                                                            self.userX509Cert
1428
1429                attributeAuthorityClnt.signatureHandler.signingPriKey = \
1430                                                            self.userPriKey
1431
1432        return attributeAuthorityClnt
1433
1434
1435    def _getAttCert(self, 
1436                    attributeAuthorityURI=None, 
1437                    attributeAuthority=None,
1438                    extAttCert=None):       
1439        """Wrapper to Attribute Authority attribute certificate request.  See
1440        getAttCert for the classes' public interface.
1441       
1442        If successful, a new attribute certificate is issued to the user
1443        and added into the wallet
1444       
1445        @type attributeAuthorityURI: string
1446        @param attributeAuthorityURI: to call as a web service, specify the URI
1447        for the Attribute Authority.
1448       
1449        @type attributeAuthority: ndg.security.server.attributeauthority.AttributeAuthority
1450        @param attributeAuthority: Alternative to attributeAuthorityURI - to
1451        run on the local machine, specify a local Attribute Authority
1452        instance.
1453
1454        @type extAttCert: ndg.security.common.AttCert.AttCert
1455        @param extAttCert: an existing Attribute Certificate which can
1456        be used to making a mapping should the user not be registered with the
1457        Attribute Authority"""
1458     
1459        log.debug("NDGCredentialWallet._getAttCert ...")
1460       
1461       
1462        # If a user cert. is present, ignore the user ID setting.  The
1463        # Attribute Authority will set the userId field of the
1464        # Attribute Certificate based on the DN of the user certificate
1465        if self.userX509Cert:
1466            userId = str(self.userX509Cert.dn)
1467        else:
1468            userId = self.userId
1469           
1470        if attributeAuthority is not None and \
1471           attributeAuthorityURI is not None:
1472            raise KeyError("Both attributeAuthorityURI and attributeAuthority "
1473                           "keywords have been set")
1474       
1475        if attributeAuthority is None:
1476            attributeAuthority = self.attributeAuthority
1477           
1478        if attributeAuthorityURI is None:
1479            attributeAuthorityURI = self.attributeAuthorityURI
1480           
1481        # Set a client alias according to whether the Attribute Authority is
1482        # being called locally or as a remote service
1483        if attributeAuthorityURI is not None:
1484            # Call Remote Service at given URI
1485            aaInterface = self._createAttributeAuthorityClnt(
1486                                                        attributeAuthorityURI)                           
1487            log.debug('NDGCredentialWallet._getAttCert for remote Attribute '
1488                      'Authority service: "%s" ...' % attributeAuthorityURI)
1489               
1490        elif attributeAuthority is not None:
1491            # Call local based Attribute Authority with settings from the
1492            # configuration file attributeAuthority
1493            aaInterface = attributeAuthority
1494            log.debug('NDGCredentialWallet._getAttCert for local Attribute '
1495                      'Authority: "%r" ...' % attributeAuthority)
1496        else:
1497            raise CredentialWalletError("Error requesting attribute: "
1498                                        "certificate a URI or Attribute "
1499                                        "Authority instance must be specified")
1500       
1501        try:
1502            # Request a new attribute certificate from the Attribute
1503            # Authority
1504            attCert = aaInterface.getAttCert(userId=userId,
1505                                             userAttCert=extAttCert)
1506           
1507            log.info('Granted Attribute Certificate from issuer DN = "%s"'%
1508                     attCert.issuerDN)
1509           
1510        except (AttributeAuthorityAccessDenied, AttributeRequestDenied), e:
1511            # AttributeAuthorityAccessDenied is raised if
1512            # aaInterface is a local AA instance and
1513            # AttributeRequestDenied is raised for a client to a remote AA
1514            # service
1515            raise CredentialWalletAttributeRequestDenied(str(e))
1516                   
1517        except Exception, e:
1518            raise CredentialWalletError("Requesting attribute certificate: %s"%
1519                                        e)
1520
1521        # Update attribute Certificate instance with CA's certificate ready
1522        # for signature check in addCredential()
1523        if self._caCertFilePathList is None:
1524            raise CredentialWalletError("No CA certificate has been set")
1525       
1526        attCert.certFilePathList = self._caCertFilePathList
1527
1528       
1529        # Add credential into wallet
1530        #
1531        # Nb. if the certificates signature is invalid, it will be rejected
1532        log.debug("Adding credentials into wallet...")
1533        self.addCredential(attCert)
1534       
1535        return attCert
1536
1537    def _getAAHostInfo(self, 
1538                       attributeAuthority=None,
1539                       attributeAuthorityURI=None):
1540        """Wrapper to Attribute Authority getHostInfo
1541       
1542        _getAAHostInfo([attributeAuthority=f|attributeAuthorityURI=u])
1543                   
1544        @type userRole: string
1545        @param userRole: get hosts which have a mapping to this role
1546       
1547        @type attributeAuthorityURI: string
1548        @param attributeAuthorityURI: to call as a web service, specify the URI
1549        for the Attribute Authority.
1550       
1551        @type attributeAuthority: string
1552        @param attributeAuthority: Alternative to attributeAuthorityURI - to
1553        run on the local machine, specify the local Attribute Authority
1554        instance.
1555        """
1556
1557        if attributeAuthority is None:
1558            attributeAuthority = self.attributeAuthority
1559           
1560        if attributeAuthorityURI is None:
1561            attributeAuthorityURI = self.attributeAuthorityURI
1562       
1563        log.debug('NDGCredentialWallet._getAAHostInfo for service: "%s" ...' % 
1564                  attributeAuthorityURI or attributeAuthority)
1565           
1566        # Set a client alias according to whether the Attribute Authority is
1567        # being called locally or asa remote service
1568        if attributeAuthorityURI is not None:
1569            # Call Remote Service at given URI
1570            attributeAuthorityClnt = self._createAttributeAuthorityClnt(
1571                                                    attributeAuthorityURI)
1572
1573        elif attributeAuthority is not None:
1574            # Call local based Attribute Authority with settings from the
1575            # configuration file attributeAuthority
1576            attributeAuthorityClnt = attributeAuthority
1577           
1578        else:
1579            raise CredentialWalletError("Error requesting trusted hosts info: " 
1580                                        "a URI or Attribute Authority " 
1581                                        "configuration file must be specified")
1582           
1583        try:
1584            # Request a new attribute certificate from the Attribute
1585            # Authority
1586            return attributeAuthorityClnt.getHostInfo()
1587           
1588        except Exception, e:
1589            log.error("Requesting host info: %s" % e)
1590            raise
1591
1592    def _getAATrustedHostInfo(self, 
1593                              userRole=None,
1594                              attributeAuthority=None,
1595                              attributeAuthorityURI=None):
1596        """Wrapper to Attribute Authority getTrustedHostInfo
1597       
1598        _getAATrustedHostInfo([userRole=r, ][attributeAuthority=f|
1599                              attributeAuthorityURI=u])
1600                   
1601        @type userRole: string
1602        @param userRole: get hosts which have a mapping to this role
1603       
1604        @type attributeAuthorityURI: string
1605        @param attributeAuthorityURI: to call as a web service, specify the URI
1606        for the Attribute Authority.
1607       
1608        @type attributeAuthority: string
1609        @param attributeAuthority: Alternative to attributeAuthorityURI - to
1610        run on the local machine, specify the local Attribute Authority
1611        instance.
1612        """
1613
1614        if attributeAuthority is None:
1615            attributeAuthority = self.attributeAuthority
1616           
1617        if attributeAuthorityURI is None:
1618            attributeAuthorityURI = self.attributeAuthorityURI
1619       
1620        log.debug('NDGCredentialWallet._getAATrustedHostInfo for role "%s" and '
1621                  'service: "%s" ...' % (userRole, 
1622                                attributeAuthorityURI or attributeAuthority))
1623           
1624        # Set a client alias according to whether the Attribute Authority is
1625        # being called locally or asa remote service
1626        if attributeAuthorityURI is not None:
1627            # Call Remote Service at given URI
1628            attributeAuthorityClnt = self._createAttributeAuthorityClnt(
1629                                                    attributeAuthorityURI)
1630
1631        elif attributeAuthority is not None:
1632            # Call local based Attribute Authority with settings from the
1633            # configuration file attributeAuthority
1634            attributeAuthorityClnt = attributeAuthority
1635           
1636        else:
1637            raise CredentialWalletError("Error requesting trusted hosts info: " 
1638                                        "a URI or Attribute Authority " 
1639                                        "configuration file must be specified")
1640           
1641        try:
1642            # Request a new attribute certificate from the Attribute
1643            # Authority
1644            return attributeAuthorityClnt.getTrustedHostInfo(role=userRole)
1645           
1646        except Exception, e:
1647            log.error("Requesting trusted host info: %s" % e)
1648            raise
1649
1650    def getAttCert(self,
1651                   reqRole=None,
1652                   attributeAuthority=None,
1653                   attributeAuthorityURI=None,
1654                   mapFromTrustedHosts=None,
1655                   rtnExtAttCertList=None,
1656                   extAttCertList=None,
1657                   extTrustedHostList=None,
1658                   refreshAttCert=False,
1659                   attCertRefreshElapse=None):
1660       
1661        """Get an Attribute Certificate from an Attribute Authority.  If this
1662        fails try to make a mapped Attribute Certificate by using a certificate
1663        from another host which has a trust relationship to the Attribute
1664        Authority in question.
1665
1666        getAttCert([reqRole=r, ][attributeAuthority=a|attributeAuthorityURI=u,]
1667                   [mapFromTrustedHosts=m, ]
1668                   [rtnExtAttCertList=e, ][extAttCertList=el, ]
1669                   [extTrustedHostList=et, ][refreshAttCert=ra])
1670                 
1671        The procedure is:
1672
1673        1) Try attribute request using user certificate
1674        2) If the Attribute Authority (AA) doesn't recognise the certificate,
1675        find out any other hosts which have a trust relationship to the AA.
1676        3) Look for Attribute Certificates held in the wallet corresponding
1677        to these hosts.
1678        4) If no Attribute Certificates are available, call the relevant
1679        hosts' AAs to get certificates
1680        5) Finally, use these new certificates to try to obtain a mapped
1681        certificate from the original AA
1682        6) If this fails access is denied     
1683                   
1684        @type reqRole: string
1685        @param reqRole: the required role to get access for
1686       
1687        @type attributeAuthorityURI: string
1688        @param attributeAuthorityURI: to call as a web service, specify the URI
1689        for the Attribute Authority.
1690       
1691        @type attributeAuthority: string
1692        @param attributeAuthority: Altenrative to attributeAuthorityURI - to
1693        run on the local machine, specify a local Attribute Authority
1694        instance.
1695                               
1696        @type mapFromTrustedHosts: bool / None     
1697        @param mapFromTrustedHosts: if request fails via the user's cert
1698        ID, then it is possible to get a mapped certificate by using
1699        certificates from other AA's.  Set this flag to True, to allow this
1700        second stage of generating a mapped certificate from the certificate
1701        stored in the wallet credentials.
1702
1703        If set to False, it is possible to return the list of certificates
1704        available for mapping and then choose which one or ones to use for
1705        mapping by re-calling getAttCert with extAttCertList set to these
1706        certificates.
1707       
1708        Defaults to None in which case self._mapFromTrustedHosts is not
1709        altered
1710
1711        The list is returned via CredentialWalletAttributeRequestDenied
1712        exception.  If no value is set, the default value held in
1713        self.mapFromTrustedHosts is used
1714
1715        @type rtnExtAttCertList: bool / None
1716        @param rtnExtAttCertList: If request fails, make a list of
1717        candidate certificates from other Attribute Authorities which the user
1718        could use to retry and get a mapped certificate.
1719                               
1720        If mapFromTrustedHosts is set True this flags value is overriden and
1721        effectively set to True.
1722
1723        If no value is set, the default value held in self._rtnExtAttCertList
1724        is used.
1725                               
1726        The list is returned via a CredentialWalletAttributeRequestDenied
1727        exception object.
1728                               
1729        @type extAttCertList: list
1730        @param extAttCertList: Attribute Certificate or list of certificates
1731        from other Attribute Authorities.  These can be used to get a mapped
1732        certificate if access fails based on the user's certificate
1733        credentials.  They are tried out in turn until access is granted so
1734        the order of the list decides the order in which they will be tried
1735
1736        @type extTrustedHostList:
1737        @param extTrustedHostList: same as extAttCertList keyword, but
1738        instead of providing Attribute Certificates, give a list of Attribute
1739        Authority hosts.  These will be matched up to Attribute Certificates
1740        held in the wallet.  Matching certificates will then be used to try to
1741        get a mapped Attribute Certificate.
1742       
1743        @type refreshAttCert: bool
1744        @param refreshAttCert: if set to True, the attribute request
1745        will go ahead even if the wallet already contains an Attribute
1746        Certificate from the target Attribute Authority.  The existing AC in
1747        the wallet will be replaced by the new one obtained from this call.
1748                               
1749        If set to False, this method will check to see if an AC issued by the
1750        target AA already exists in the wallet.  If so, it will return this AC
1751        to the caller without proceeding to make a call to the AA.
1752       
1753        @type attCertRefreshElapse: float / int
1754        @param attCertRefreshElapse: determine whether to replace an
1755        existing AC in the cache with a fresh one.  If the existing one has
1756        less than attCertRefreshElapse time in seconds left before expiry then
1757        replace it.
1758       
1759        @rtype: ndg.security.common.AttCert.AttCert
1760        @return: Attribute Certificate retrieved from Attribute Authority"""
1761       
1762        log.debug("NDGCredentialWallet.getAttCert ...")
1763       
1764        # Both these assignments are calling set property methods implicitly!
1765        if attributeAuthorityURI:
1766            self.attributeAuthorityURI = attributeAuthorityURI
1767           
1768        if attributeAuthority is not None:
1769            self.attributeAuthority = attributeAuthority
1770           
1771        if not refreshAttCert and self.credentials:
1772            # Refresh flag is not set so it's OK to check for any existing
1773            # Attribute Certificate in the wallet whose issuerName match the
1774            # target AA's name
1775           
1776            # Find out the site ID for the target AA by calling AA's host
1777            # info WS method
1778            log.debug("NDGCredentialWallet.getAttCert - check AA site ID ...")
1779            try:
1780                hostInfo = self._getAAHostInfo()
1781                aaName = hostInfo.keys()[0]
1782            except Exception, e:
1783                raise CredentialWalletError("Getting host info: %s" % e)
1784           
1785            # Look in the wallet for an AC with the same issuer name
1786            if aaName in self.credentials:
1787                # Existing Attribute Certificate found in wallet - Check that
1788                # it will be valid for at least the next 2 hours
1789                if attCertRefreshElapse is not None:
1790                    self.attCertRefreshElapse = attCertRefreshElapse
1791                   
1792                dtNow = datetime.utcnow() + \
1793                        timedelta(seconds=self.attCertRefreshElapse)
1794               
1795                attCert = self.credentials[aaName]['attCert']
1796                if attCert.isValidTime(dtNow=dtNow):
1797                    log.info("Retrieved an existing %s AC from the wallet" % 
1798                             aaName)
1799                    return attCert
1800                     
1801        # Check for settings from input, if not set use previous settings
1802        # made
1803        if mapFromTrustedHosts is not None:
1804            self.mapFromTrustedHosts = mapFromTrustedHosts
1805
1806        if rtnExtAttCertList is not None:
1807            self.rtnExtAttCertList = rtnExtAttCertList
1808
1809        # Check for list of external trusted hosts (other trusted NDG data
1810        # centres)
1811        if extTrustedHostList:
1812            log.info("Checking for ACs in wallet matching list of trusted "
1813                     "hosts set: %s" % extTrustedHostList)
1814           
1815            if not self.mapFromTrustedHosts:
1816                raise CredentialWalletError("A list of trusted hosts has been " 
1817                                      "input but mapping from trusted hosts "
1818                                      "is set to disallowed")
1819           
1820            if isinstance(extTrustedHostList, basestring):
1821                extTrustedHostList = [extTrustedHostList]
1822
1823            # Nb. Any extAttCertList is overriden by extTrustedHostList being
1824            # set
1825            extAttCertList = [self.credentials[hostName]['attCert'] 
1826                              for hostName in extTrustedHostList
1827                              if hostName in self.credentials]
1828
1829        # Set an empty list to trigger an AttributeError by initialising it to
1830        # None
1831        if extAttCertList == []:
1832            extAttCertList = None
1833           
1834        # Repeat authorisation attempts until succeed or means are exhausted
1835        while True:
1836           
1837            # Check for candidate certificates for mapping
1838            try:
1839                # If list is set get the next cert
1840                extAttCert = extAttCertList.pop()
1841
1842            except AttributeError:
1843                log.debug("No external Attribute Certificates - trying "
1844                          "request without mapping...")
1845                # No List set - attempt request without
1846                # using mapping from trusted hosts
1847                extAttCert = None
1848                           
1849            except IndexError:
1850               
1851                # List has been emptied without attribute request succeeding -
1852                # give up
1853                errMsg = ("Attempting to obtained a mapped certificate: "
1854                          "no external attribute certificates are available")
1855                   
1856                # Add the exception form the last call to the Attribute
1857                # Authority if an error exists
1858                try:
1859                    errMsg += ": %s" % attributeRequestDenied
1860                except NameError:
1861                    pass
1862
1863                raise CredentialWalletAttributeRequestDenied(errMsg)
1864                                                   
1865               
1866            # Request Attribute Certificate from Attribute Authority
1867            try:
1868                attCert = self._getAttCert(extAttCert=extAttCert)
1869                # Access granted
1870                return attCert
1871           
1872            except CredentialWalletAttributeRequestDenied, \
1873                   attributeRequestDenied:
1874                if not self.mapFromTrustedHosts and not self.rtnExtAttCertList:
1875                    log.debug("Creating a mapped certificate option is not "
1876                              "set - raising "
1877                              "CredentialWalletAttributeRequestDenied "
1878                              "exception saved from earlier")
1879                    raise attributeRequestDenied
1880
1881                if isinstance(extAttCertList, list):
1882                    # An list of attribute certificates from trusted hosts
1883                    # is present continue cycling through this until one of
1884                    # them is accepted and a mapped certificate can be derived
1885                    log.debug("AC request denied - but external ACs available "
1886                              "to try mapped AC request ...")
1887                    continue
1888                             
1889                #  Use the input required role and the AA's trusted host list
1890                # to identify attribute certificates from other hosts which
1891                # could be used to make a mapped certificate
1892                log.debug("Getting a list of trusted hosts for mapped AC "
1893                          "request ...")
1894                try:
1895                    trustedHostInfo = self._getAATrustedHostInfo(reqRole)
1896                   
1897                except NoMatchingRoleInTrustedHosts, e:
1898                    raise CredentialWalletAttributeRequestDenied(
1899                        'Can\'t get a mapped Attribute Certificate for '
1900                        'the "%s" role' % reqRole)
1901               
1902                except Exception, e:
1903                    raise CredentialWalletError("Getting trusted hosts: %s"%e)
1904
1905                if not trustedHostInfo:
1906                    raise CredentialWalletAttributeRequestDenied(
1907                        "Attribute Authority has no trusted hosts with "
1908                        "which to make a mapping")
1909
1910                # Initialise external certificate list here - if none are
1911                # found IndexError will be raised on the next iteration and
1912                # an access denied error will be raised
1913                extAttCertList = []
1914
1915                # Look for Attribute Certificates with matching issuer host
1916                # names
1917                log.debug("Checking wallet for ACs issued by one of the "
1918                          "trusted hosts...")
1919                for hostName in self.credentials:
1920
1921                    # Nb. Candidate certificates for mappings must have
1922                    # original provenance and contain at least one of the
1923                    # required roles
1924                    attCert = self.credentials[hostName].credential
1925                   
1926                    if hostName in trustedHostInfo and attCert.isOriginal():                       
1927                        for role in attCert.roles:
1928                            if role in trustedHostInfo[hostName]['role']:                               
1929                                extAttCertList.append(attCert)
1930
1931                if not extAttCertList:
1932                    log.debug("No wallet ACs matched any of the trusted "
1933                              "hosts.  - Try request for an AC from a "
1934                              "trusted host ...")
1935                   
1936                    # No certificates in the wallet matched the trusted host
1937                    # and required roles
1938                    #
1939                    # Try each host in turn in order to get a certificate with
1940                    # the required credentials in order to do a mapping
1941                    for host, info in trustedHostInfo.items():
1942                        try:
1943                            # Try request to trusted host
1944                            extAttCert = self._getAttCert(
1945                                        attributeAuthorityURI=info['aaURI'])
1946
1947                            # Check the certificate contains at least one of
1948                            # the required roles
1949                            if [True for r in extAttCert.roles
1950                                if r in info['role']]:
1951                               extAttCertList.append(extAttCert)
1952
1953                               # For efficiency, stop once obtained a valid
1954                               # cert - but may want complete list for user to
1955                               # choose from
1956                               #break
1957                               
1958                        except Exception, e:
1959                            # ignore any errors and continue
1960                            log.warning('AC request to trusted host "%s"' 
1961                                        ' resulted in: %s' % (info['aaURI'],e))
1962                           
1963                if not extAttCertList:                       
1964                    raise CredentialWalletAttributeRequestDenied(
1965                        "No certificates are available with which to "
1966                        "make a mapping to the Attribute Authority")
1967
1968
1969                if not self.mapFromTrustedHosts:
1970                   
1971                    # Exit here returning the list of candidate certificates
1972                    # that could be used to make a mapped certificate
1973                    msg = ("User is not registered with Attribute "
1974                           "Authority - retry using one of the returned "
1975                           "Attribute Certificates obtained from other " 
1976                           "trusted hosts")
1977                         
1978                    raise CredentialWalletAttributeRequestDenied(msg,
1979                                            extAttCertList=extAttCertList,
1980                                            trustedHostInfo=trustedHostInfo)
1981       
1982       
1983class CredentialRepositoryError(_CredentialWalletException):   
1984    """Exception handling for NDG Credential Repository class."""
1985
1986
1987class CredentialRepository(object):
1988    """CredentialWallet's abstract interface class to a Credential Repository.
1989    The Credential Repository is abstract store of user currently valid user
1990    credentials.  It enables retrieval of attribute certificates from a user's
1991    previous session(s)"""
1992       
1993    def __init__(self, propFilePath=None, dbPPhrase=None, **prop):
1994        """Initialise Credential Repository abstract base class.  Derive from
1995        this class to define Credentail Repository interface Credential
1996        Wallet
1997
1998        If the connection string or properties file is set a connection
1999        will be made
2000
2001        @type dbPPhrase: string
2002        @param dbPPhrase: pass-phrase to database if applicable
2003       
2004        @type propFilePath: string
2005        @param propFilePath: file path to a properties file.  This could
2006        contain configuration parameters for the repository e.g.  database
2007        connection parameters
2008       
2009        @type **prop: dict
2010        @param **prop: any other keywords required
2011        """
2012        raise NotImplementedError(
2013            self.__init__.__doc__.replace('\n       ',''))
2014
2015
2016    def addUser(self, userId, dn=None):
2017        """A new user to Credentials Repository
2018       
2019        @type userId: string
2020        @param userId: userId for new user
2021        @type dn: string
2022        @param dn: users Distinguished Name (optional)"""
2023        raise NotImplementedError(
2024            self.addUser.__doc__.replace('\n       ',''))
2025
2026                           
2027    def auditCredentials(self, userId=None, **attCertValidKeys):
2028        """Check the attribute certificates held in the repository and delete
2029        any that have expired
2030
2031        @type userId: basestring/list or tuple
2032        @param userId: audit credentials for the input user ID or list of IDs
2033        @type attCertValidKeys: dict
2034        @param **attCertValidKeys: keywords which set how to check the
2035        Attribute Certificate e.g. check validity time, XML signature, version
2036         etc.  Default is check validity time only - See AttCert class"""
2037        raise NotImplementedError(
2038            self.auditCredentials.__doc__.replace('\n       ',''))
2039
2040
2041    def getCredentials(self, userId):
2042        """Get the list of credentials for a given users DN
2043       
2044        @type userId: string
2045        @param userId: users userId, name or X.509 cert. distinguished name
2046        @rtype: list
2047        @return: list of Attribute Certificates"""
2048        raise NotImplementedError(
2049            self.getCredentials.__doc__.replace('\n       ',''))
2050
2051       
2052    def addCredentials(self, userId, attCertList):
2053        """Add new attribute certificates for a user.  The user must have
2054        been previously registered in the repository
2055
2056        @type userId: string
2057        @param userId: users userId, name or X.509 cert. distinguished name
2058        @type attCertList: list
2059        @param attCertList: list of attribute certificates"""
2060        raise NotImplementedError(
2061            self.addCredentials.__doc__.replace('\n       ',''))
2062
2063
2064class NullCredentialRepository(CredentialRepository):
2065    """Implementation of Credential Repository interface with empty stubs. 
2066    Use this class in the case where no Credential Repository is required"""
2067   
2068    def __init__(self, propFilePath=None, dbPPhrase=None, **prop):
2069        pass
2070
2071    def addUser(self, userId):
2072        pass
2073                           
2074    def auditCredentials(self, **attCertValidKeys):
2075        pass
2076
2077    def getCredentials(self, userId):
2078        return []
2079       
2080    def addCredentials(self, userId, attCertList):
2081        pass
Note: See TracBrowser for help on using the repository browser.