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

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@6672
Revision 6672, 87.7 KB checked in by pjkersha, 10 years ago (diff)

Patched ndg.security.common.AttCert? so that it uses a proxy to ndg.security.common.XMLSec.XMLSecDoc for Python versions >= 2.5.5. This is to allow for PyXML incompatibility with later versions of Python. Disabling XMLSecDoc means that Attribute Certificates are not signed but the NDG Attribute Certificates are no longer used. SAML assertions take their place. NDG AC functionality will be deleted from the trunk.

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