source: TI12-security/trunk/NDGSecurity/python/ndg_security_common/ndg/security/common/credentialwallet.py @ 6512

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

Patches to CredentialWallet?, SAML interfaces and authz middleware for WPS testing.

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