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

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/NDGSecurity/python/ndg_security_common/ndg/security/common/credentialwallet.py@6570
Revision 6570, 87.7 KB checked in by pjkersha, 11 years ago (diff)
  • Refactored classfactory module as a more generic factory for importing any module object
  • Started unit tests with refactored SAML SOAP bindings.
  • 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.binding.soap.attributequery."
762        "AttributeQuerySslSOAPbinding client interface instead for retrieving "
763        "and caching user attributes.")
764   
765    def __init__(self, 
766                 cfg=None, 
767                 cfgFileSection='DEFAULT', 
768                 cfgPrefix='', 
769                 wssCfgKw={},
770                 **kw):
771        """Create store of user credentials for their current session
772
773        @type cfg: string / ConfigParser object
774        @param cfg: if a string type, this is interpreted as the file path to
775        a configuration file, otherwise it will be treated as a ConfigParser
776        object
777        @type cfgSection: string
778        @param cfgSection: sets the section name to retrieve config params
779        from
780        @type cfgPrefix: basestring
781        @param cfgPrefix: apply a prefix to all NDGCredentialWallet config
782        params so that if placed in a file with other parameters they can be
783        distinguished
784        @type cfgKw: dict
785        @param cfgKw: set parameters as key value pairs."""
786
787        warnings.warn(NDGCredentialWallet.FUTURE_DEPRECATION_MSG)
788        log.warning(NDGCredentialWallet.FUTURE_DEPRECATION_MSG)
789        log.debug("Calling NDGCredentialWallet.__init__ ...")
790
791        super(NDGCredentialWallet, self).__init__()
792       
793        # Initialise attributes
794        for k, v in NDGCredentialWallet.__slots__.items():
795            setattr(self, k, v)
796           
797        # Update attributes from a config file
798        if cfg:
799            self.parseConfig(cfg, section=cfgFileSection, prefix=cfgPrefix)
800
801        # Update attributes from keywords passed - set user private key
802        # password first if it's present.  This is to avoid an error setting
803        # the private key
804        self.userPriKeyPwd = kw.pop('userPriKeyPwd', None)
805        for k, v in kw.items():
806            setattr(self, k, v)
807
808        # Get the distinguished name from the user certificate
809        if self._userX509Cert:
810            self._dn = self._userX509Cert.dn.serialise()
811
812        # Make a connection to the Credentials Repository
813        if self._credentialRepository is None:
814            log.info('Applying default CredentialRepository %r for user '
815                     '"%s"' % (NullCredentialRepository, self.userId))
816            self._credentialRepository = NullCredentialRepository()
817        else:
818            log.info('Checking CredentialRepository for credentials for user '
819                     '"%s"' % self.userId)
820           
821            if not issubclass(self._credentialRepository, CredentialRepository):
822                raise CredentialWalletError("Input Credential Repository "
823                                            "instance must be of a class "
824                                            "derived from "
825                                            "\"CredentialRepository\"")
826   
827       
828            # Check for valid attribute certificates for the user
829            try:
830                self._credentialRepository.auditCredentials(self.userId)
831                userCred=self._credentialRepository.getCredentials(self.userId)
832   
833            except Exception, e:
834                log.error("Error updating wallet with credentials from "
835                          "repository: %s" % e)
836                raise
837   
838   
839            # Update wallet with attribute certificates stored in the
840            # repository.  Store ID and certificate instantiated as an AttCert
841            # type
842            try:
843                for cred in userCred: 
844                    attCert = AttCert.Parse(cred.attCert)
845                    issuerName = attCert['issuerName']
846                   
847                    self.credentials[issuerName] = {'id':cred.id, 
848                                                     'attCert':attCert}   
849            except Exception, e:
850                try:
851                    raise CredentialWalletError("Error parsing Attribute "
852                        "Certificate ID '%s' retrieved from the " 
853                        "Credentials Repository: %s" % (cred.id, e))           
854                except:
855                    raise CredentialWalletError("Error parsing Attribute "
856                                          "Certificate retrieved from the "
857                                          "Credentials Repository: %s:" % e)
858           
859            # Filter out expired or otherwise invalid certificates
860            self.audit()
861   
862    def __getstate__(self):
863        '''Enable pickling for use with beaker.session'''
864        _dict = super(NDGCredentialWallet, self).__getstate__()
865       
866        for attrName in NDGCredentialWallet.__slots__:
867            # Ugly hack to allow for derived classes setting private member
868            # variables
869            if attrName.startswith('__'):
870                attrName = "_NDGCredentialWallet" + attrName
871               
872            _dict[attrName] = getattr(self, attrName)
873           
874        return _dict
875       
876    def parseConfig(self, cfg, prefix='', section='DEFAULT'):
877        '''Extract parameters from cfg config object'''
878       
879        if isinstance(cfg, basestring):
880            cfgFilePath = os.path.expandvars(cfg)
881            self._cfg = None
882        else:
883            cfgFilePath = None
884            self._cfg = cfg
885           
886        # Configuration file properties are held together in a dictionary
887        readAndValidate = INIPropertyFileWithValidation()
888        prop = readAndValidate(cfgFilePath,
889                               cfg=self._cfg,
890                               validKeys=NDGCredentialWallet.propertyDefaults,
891                               prefix=prefix,
892                               sections=(section,))
893       
894        # Keep a copy of config for use by WS-Security SignatureHandler parser
895        if self._cfg is None:
896            self._cfg = readAndValidate.cfg
897       
898        # Copy prop dict into object attributes - __slots__ definition and
899        # property methods will ensure only the correct attributes are set
900        # Set user private key password first if it's present.  This is to
901        # avoid an error setting the private key
902        self.userPriKeyPwd = prop.pop('userPriKeyPwd', None)
903        for key, val in prop.items():
904            setattr(self, key, val)
905
906
907    def _getAttCertRefreshElapse(self):
908        """Get property method for Attribute Certificate wallet refresh time
909        @rtype: float or int
910        @return: "elapse time in seconds"""
911        return self._attCertRefreshElapse
912   
913    def _setAttCertRefreshElapse(self, val):
914        """Set property method for Attribute Certificate wallet refresh time
915        @type val: float or int
916        @param val: "elapse time in seconds"""
917        if isinstance(val, (float, int)):
918            self._attCertRefreshElapse = val
919           
920        elif isinstance(val, basestring):
921            self._attCertRefreshElapse = float(val)
922        else:
923            raise AttributeError("Expecting int, float or string type input "
924                                 "for attCertRefreshElapse")
925           
926    attCertRefreshElapse = property(fget=_getAttCertRefreshElapse, 
927                                    fset=_setAttCertRefreshElapse,
928                                    doc="If an existing one has AC less than "
929                                        "attCertRefreshElapse time in seconds "
930                                        "left before expiry then replace it")
931   
932    def _setX509Cert(self, cert):
933        """filter and convert input cert to signing verifying cert set
934        property methods.  For signingCert, set to None if it is not to be
935        included in the SOAP header.  For verifyingCert, set to None if this
936        cert can be expected to be retrieved from the SOAP header of the
937        message to be verified
938       
939        @type: ndg.security.common.X509.X509Cert / M2Crypto.X509.X509 /
940        string or None
941        @param cert: X.509 certificate. 
942       
943        @rtype ndg.security.common.X509.X509Cert
944        @return X.509 certificate object"""
945       
946        if cert is None or isinstance(cert, X509Cert):
947            # ndg.security.common.X509.X509Cert type / None
948            return cert
949           
950        elif isinstance(cert, X509.X509):
951            # M2Crypto.X509.X509 type
952            return X509Cert(m2CryptoX509=cert)
953           
954        elif isinstance(cert, basestring):
955            return X509Cert.Parse(cert)
956       
957        else:
958            raise AttributeError("X.509 Cert. must be type: "
959                                 "ndg.security.common.X509.X509Cert, "
960                                 "M2Crypto.X509.X509 or a base64 encoded "
961                                 "string")
962
963    def _setUserX509Cert(self, userX509Cert):
964        "Set property method for X.509 user cert."
965        self._userX509Cert = self._setX509Cert(userX509Cert)       
966
967    def _getUserX509Cert(self):
968        """Get user cert X509Cert instance"""
969        return self._userX509Cert
970
971    userX509Cert = property(fget=_getUserX509Cert,
972                            fset=_setUserX509Cert,
973                            doc="X.509 user certificate instance")
974 
975    def _setUserX509CertFilePath(self, filePath):
976        "Set user X.509 cert file path property method"
977       
978        if isinstance(filePath, basestring):
979            filePath = os.path.expandvars(filePath)
980            self._userX509Cert = X509Cert.Read(filePath)
981           
982        elif filePath is not None:
983            raise AttributeError("User X.509 cert. file path must be a valid "
984                                 "string")
985       
986        self._userX509CertFilePath = filePath
987               
988    userX509CertFilePath = property(fset=_setUserX509CertFilePath,
989                                    doc="File path to user X.509 cert.")
990   
991    def _setIssuingX509Cert(self, issuingX509Cert):
992        "Set property method for X.509 user cert."
993        self._issuingX509Cert = self._setX509Cert(issuingX509Cert)
994       
995    def _getIssuingX509Cert(self):
996        """Get user cert X509Cert instance"""
997        return self._issuingX509Cert
998
999    issuingX509Cert = property(fget=_getIssuingX509Cert,
1000                               fset=_setIssuingX509Cert,
1001                               doc="X.509 user certificate instance")
1002 
1003    def _setIssuerX509CertFilePath(self, filePath):
1004        "Set user X.509 cert file path property method"
1005       
1006        if isinstance(filePath, basestring):
1007            filePath = os.path.expandvars(filePath)
1008            self._issuerX509Cert = X509Cert.Read(filePath)
1009           
1010        elif filePath is not None:
1011            raise AttributeError("User X.509 cert. file path must be a valid "
1012                                 "string")
1013       
1014        self._issuerX509CertFilePath = filePath
1015               
1016    issuerX509CertFilePath = property(fset=_setIssuerX509CertFilePath,
1017                                      doc="File path to user X.509 cert. "
1018                                          "issuing cert.")     
1019
1020    def _getUserPriKey(self):
1021        "Get method for user private key"
1022        return self._userPriKey
1023   
1024    def _setUserPriKey(self, userPriKey):
1025        """Set method for user private key
1026       
1027        Nb. if input is a string, userPriKeyPwd will need to be set if
1028        the key is password protected.
1029       
1030        @type userPriKey: M2Crypto.RSA.RSA / string
1031        @param userPriKey: private key used to sign message"""
1032       
1033        if userPriKey is None:
1034            self._userPriKey = None
1035        elif isinstance(userPriKey, basestring):
1036            pwdCallback = lambda *ar, **kw: self._userPriKeyPwd
1037            self._userPriKey = RSA.load_key_string(userPriKey,
1038                                                   callback=pwdCallback)
1039        elif isinstance(userPriKey, RSA.RSA):
1040            self._userPriKey = userPriKey         
1041        else:
1042            raise AttributeError("user private key must be a valid "
1043                                 "M2Crypto.RSA.RSA type or a string")
1044               
1045    userPriKey = property(fget=_getUserPriKey,
1046                          fset=_setUserPriKey,
1047                          doc="User private key if set, used to sign outbound "
1048                              "messages to Attribute authority")
1049
1050    def _setUserPriKeyFilePath(self, filePath):
1051        "Set user private key file path property method"
1052       
1053        if isinstance(filePath, basestring):
1054            filePath = os.path.expandvars(filePath)
1055            try:
1056                # Read Private key to sign with   
1057                priKeyFile = BIO.File(open(filePath)) 
1058                pwdCallback = lambda *ar, **kw: self._userPriKeyPwd
1059                self._userPriKey = RSA.load_key_bio(priKeyFile, 
1060                                                    callback=pwdCallback)   
1061            except Exception, e:
1062                raise AttributeError("Setting user private key: %s" % e)
1063       
1064        elif filePath is not None:
1065            raise AttributeError("Private key file path must be a valid "
1066                                 "string or None")
1067       
1068        self._userPriKeyFilePath = filePath
1069       
1070    userPriKeyFilePath = property(fset=_setUserPriKeyFilePath,
1071                                  doc="File path to user private key")
1072 
1073    def _setUserPriKeyPwd(self, userPriKeyPwd):
1074        "Set method for user private key file password"
1075        if userPriKeyPwd is not None and not isinstance(userPriKeyPwd, 
1076                                                        basestring):
1077            raise AttributeError("Signing private key password must be None "
1078                                 "or a valid string")
1079       
1080        # Explicitly convert to string as M2Crypto OpenSSL wrapper fails with
1081        # unicode type
1082        self._userPriKeyPwd = str(userPriKeyPwd)
1083
1084    def _getUserPriKeyPwd(self):
1085        "Get property method for user private key"
1086        return self._userPriKeyPwd
1087       
1088    userPriKeyPwd = property(fset=_setUserPriKeyPwd,
1089                             fget=_getUserPriKeyPwd,
1090                             doc="Password protecting user private key file")
1091       
1092    def _getCACertFilePathList(self):
1093        """Get CA cert or certs used to validate AC signatures and signatures
1094        of peer SOAP messages.
1095       
1096        @rtype caCertFilePathList: basestring, list or tuple
1097        @return caCertFilePathList: file path(s) to CA certificates."""
1098        return self._caCertFilePathList
1099   
1100    def _setCACertFilePathList(self, caCertFilePathList):
1101        """Set CA cert or certs to validate AC signatures, signatures
1102        of Attribute Authority SOAP responses and SSL connections where
1103        AA SOAP service is run over SSL.
1104       
1105        @type caCertFilePathList: basestring, list, tuple or None
1106        @param caCertFilePathList: file path(s) to CA certificates.  If None
1107        then the input is quietly ignored."""
1108       
1109        if isinstance(caCertFilePathList, basestring):
1110           self._caCertFilePathList = [caCertFilePathList]
1111           
1112        elif isinstance(caCertFilePathList, list):
1113           self._caCertFilePathList = caCertFilePathList
1114           
1115        elif isinstance(caCertFilePathList, tuple):
1116           self._caCertFilePathList = list(caCertFilePathList)
1117
1118        elif caCertFilePathList is not None:
1119            raise TypeError('Expecting string/list/tuple or None type for '
1120                            '"caCertFilePathList"; got %r type' % 
1121                            type(caCertFilePathList))     
1122       
1123    caCertFilePathList = property(fget=_getCACertFilePathList,
1124                                  fset=_setCACertFilePathList,
1125                                  doc="CA Certificates - used for "
1126                                      "verification of AC and SOAP message "
1127                                      "signatures")
1128
1129    def _getAttributeAuthorityURI(self):
1130        return self._attributeAuthorityURI
1131
1132    def _setAttributeAuthorityURI(self, value):
1133        """String or None type are allowed - The URI may be set to None to
1134        flag that a local Attribute Authority instance is being invoked rather
1135        one hosted via a remote URI
1136        """
1137        if not isinstance(value, (basestring, type(None))):
1138            raise TypeError('Expecting string or None type for '
1139                            '"attributeAuthorityURI"; got %r instead' % 
1140                            type(value))
1141           
1142        self._attributeAuthorityURI = value
1143         
1144        if value is not None:     
1145            # Re-initialize local instance
1146            self._attributeAuthority = NDGCredentialWallet.propertyDefaults[
1147                                                        'attributeAuthority']
1148
1149    attributeAuthorityURI = property(fget=_getAttributeAuthorityURI,
1150                                     fset=_setAttributeAuthorityURI,
1151                                     doc="Attribute Authority address - "
1152                                         "setting also sets up "
1153                                         "AttributeAuthorityClient instance!")
1154
1155    def _getAttributeAuthority(self):
1156        """Get property method for Attribute Authority Web Service client
1157        instance.  Use attributeAuthorityURI propert to set up
1158        attributeAuthorityClnt
1159       
1160        @rtype attributeAuthority: ndg.security.server.attributeauthority.AttributeAuthority
1161        @return attributeAuthority: Attribute Authority instance"""
1162        return self._attributeAuthority
1163
1164    def _setAttributeAuthority(self, attributeAuthority):
1165        """Set property method for Attribute Authority Web Service instance to
1166        connect to.  This method ALSO RESETS attributeAuthorityURI - the
1167        address of a remote Attribute Authority - to None
1168       
1169        @type attributeAuthority: ndg.security.server.attributeauthority.AttributeAuthority
1170        @param attributeAuthority: Attribute Authority instance.
1171        """
1172        if not isinstance(attributeAuthority, (AttributeAuthority, type(None))):
1173            raise AttributeError("Expecting %r or None type for "
1174                                 "\"attributeAuthority\" attribute; got %r" % 
1175                                 (AttributeAuthority, type(attributeAuthority)))
1176           
1177        self._attributeAuthority = attributeAuthority
1178       
1179        # Re-initialize setting for remote service
1180        self.attributeAuthorityURI = None
1181           
1182    attributeAuthority = property(fget=_getAttributeAuthority,
1183                                  fset=_setAttributeAuthority, 
1184                                  doc="Attribute Authority instance")
1185
1186
1187    def _getMapFromTrustedHosts(self):
1188        """Get property method for boolean flag - if set to True it allows
1189        role mapping to be attempted when connecting to an Attribute Authority
1190       
1191        @type mapFromTrustedHosts: bool
1192        @param mapFromTrustedHosts: set to True to try role mapping in AC
1193        requests to Attribute Authorities"""
1194        return self._mapFromTrustedHosts
1195
1196    def _setMapFromTrustedHosts(self, mapFromTrustedHosts):
1197        """Set property method for boolean flag - if set to True it allows
1198        role mapping to be attempted when connecting to an Attribute Authority
1199       
1200        @type mapFromTrustedHosts: bool
1201        @param mapFromTrustedHosts: Attribute Authority Web Service."""
1202        if not isinstance(mapFromTrustedHosts, bool):
1203            raise AttributeError("Expecting %r for mapFromTrustedHosts "
1204                                 "attribute" % bool)
1205           
1206        self._mapFromTrustedHosts = mapFromTrustedHosts
1207           
1208    mapFromTrustedHosts = property(fget=_getMapFromTrustedHosts,
1209                                   fset=_setMapFromTrustedHosts, 
1210                                   doc="Set to True to enable mapped AC "
1211                                       "requests")
1212
1213    def _getRtnExtAttCertList(self):
1214        """Get property method for Attribute Authority Web Service client
1215        instance.  Use rtnExtAttCertListURI propert to set up
1216        rtnExtAttCertListClnt
1217       
1218        @type rtnExtAttCertList: bool
1219        @param rtnExtAttCertList: """
1220        return self._rtnExtAttCertList
1221
1222    def _setRtnExtAttCertList(self, rtnExtAttCertList):
1223        """Set property method for boolean flag - when a AC request fails,
1224        return a list of candidate ACs that could be used to re-try with in
1225        order to get mapped AC.
1226       
1227        @type rtnExtAttCertList: bool
1228        @param rtnExtAttCertList: set to True to configure getAttCert to return
1229        a list of ACs that could be used in a re-try to get a mapped AC from
1230        the target Attribute Authority."""
1231        if not isinstance(rtnExtAttCertList, bool):
1232            raise AttributeError("Expecting %r for rtnExtAttCertList "
1233                                 "attribute" % bool)
1234           
1235        self._rtnExtAttCertList = rtnExtAttCertList
1236           
1237    rtnExtAttCertList = property(fget=_getRtnExtAttCertList,
1238                                 fset=_setRtnExtAttCertList, 
1239                                 doc="Set to True to enable mapped AC "
1240                                     "requests")
1241
1242    def isValid(self, **x509CertKeys):
1243        """Check wallet's user cert.  If expired return False
1244       
1245        @type **x509CertKeys: dict
1246        @param **x509CertKeys: keywords applying to
1247        ndg.security.common.X509.X509Cert.isValidTime method"""
1248        if self._userX509Cert is not None:
1249            return self._userX509Cert.isValidTime(**x509CertKeys)
1250        else:
1251            log.warning("NDGCredentialWallet.isValid: no user certificate set in "
1252                        "wallet")
1253            return True
1254
1255    def addCredential(self, 
1256                      attCert, 
1257                      attributeAuthorityURI=None,
1258                      bUpdateCredentialRepository=True):
1259        """Add a new attribute certificate to the list of credentials held.
1260
1261        @type attCert:
1262        @param attCert: new attribute Certificate to be added
1263        @type attributeAuthorityURI: basestring
1264        @param attributeAuthorityURI: input the Attribute Authority URI from
1265        which attCert was retrieved.  This is added to a dict to enable access
1266        to a given Attribute Certificate keyed by Attribute Authority URI.
1267        See the getCredential method.
1268        @type bUpdateCredentialRepository: bool
1269        @param bUpdateCredentialRepository: if set to True, and a repository
1270        exists it will be updated with the new credentials also
1271       
1272        @rtype: bool
1273        @return: True if certificate was added otherwise False.  - If an
1274        existing certificate from the same issuer has a later expiry it will
1275        take precedence and the new input certificate is ignored."""
1276
1277        # Check input
1278        if not isinstance(attCert, AttCert):
1279            raise CredentialWalletError("Credential must be an %r type object" %
1280                                        AttCert)
1281           
1282        # Check certificate validity
1283        try:
1284            attCert.isValid(raiseExcep=True)
1285           
1286        except AttCertError, e:
1287            raise CredentialWalletError("Adding Credential: %s" % e)
1288
1289        # Check to see if there is an existing Attribute Certificate held
1290        # that was issued by the same host.  If so, compare the expiry time.
1291        # The one with the latest expiry will be retained and the other
1292        # ingored
1293        bUpdateCred = True
1294        issuerName = attCert['issuerName']
1295       
1296        if issuerName in self.credentials:
1297            # There is an existing certificate held with the same issuing
1298            # host name as the new certificate
1299            attCertOld = self.credentials[issuerName].credential
1300
1301            # Get expiry times in datetime format to allow comparison
1302            dtAttCertOldNotAfter = attCertOld.getValidityNotAfter(\
1303                                                            asDatetime=True)
1304            dtAttCertNotAfter = attCert.getValidityNotAfter(asDatetime=True)
1305
1306            # If the new certificate has an earlier expiry time then ignore it
1307            bUpdateCred = dtAttCertNotAfter > dtAttCertOldNotAfter
1308
1309               
1310        if bUpdateCred:
1311            thisCredential = CredentialContainer(AttCert)
1312            thisCredential.credential = attCert
1313            thisCredential.issuerName = issuerName
1314            thisCredential.attributeAuthorityURI = attributeAuthorityURI
1315           
1316            self.credentials[issuerName] = thisCredential
1317           
1318            if attributeAuthorityURI:
1319                self.credentialsKeyedByURI[
1320                    attributeAuthorityURI] = thisCredential
1321           
1322            # Update the Credentials Repository - the permanent store of user
1323            # authorisation credentials.  This allows credentials for previous
1324            # sessions to be re-instated
1325            if self._credentialRepository and bUpdateCredentialRepository:
1326                self.updateCredentialRepository()
1327
1328        # Flag to caller to indicate whether the input certificate was added
1329        # to the credentials or an exsiting certificate from the same issuer
1330        # took precedence
1331        return bUpdateCred
1332           
1333
1334    def audit(self):
1335        """Check the credentials held in the wallet removing any that have
1336        expired or are otherwise invalid."""
1337
1338        log.debug("NDGCredentialWallet.audit ...")
1339       
1340        # Nb. No signature check is carried out.  To do a check, access is
1341        # needed to the cert of the CA that issued the Attribute Authority's
1342        # cert
1343        #
1344        # P J Kershaw 12/09/05
1345        for key, val in self.credentials.items():
1346            if not val.credential.isValid(chkSig=False):
1347                del self.credentials[key]
1348
1349    def updateCredentialRepository(self, auditCred=True):
1350        """Copy over non-persistent credentials held by wallet into the
1351        perminent repository.
1352       
1353        @type auditCred: bool
1354        @param auditCred: filter existing credentials in the repository
1355        removing invalid ones"""
1356
1357        log.debug("NDGCredentialWallet.updateCredentialRepository ...")
1358       
1359        if not self._credentialRepository:
1360            raise CredentialWalletError("No Credential Repository has been "
1361                                        "created for this wallet")
1362                           
1363        # Filter out invalid certs unless auditCred flag is explicitly set to
1364        # false
1365        if auditCred: self.audit()
1366
1367        # Update the database - only add new entries i.e. with an ID of -1
1368        attCertList = [i.credential for i in self.credentials.values() 
1369                       if i.id == -1]
1370
1371        self._credentialRepository.addCredentials(self.userId, attCertList)
1372
1373    def _createAttributeAuthorityClnt(self, attributeAuthorityURI):
1374        """Set up a client to an Attribute Authority with the given URI
1375       
1376        @type attributeAuthorityURI: string
1377        @param attributeAuthorityURI: Attribute Authority Web Service URI.
1378
1379        @rtype: ndg.security.common.attributeauthority.AttributeAuthorityClient
1380        @return: new Attribute Authority client instance"""
1381
1382        log.debug('NDGCredentialWallet._createAttributeAuthorityClnt for '
1383                  'service: "%s"' % attributeAuthorityURI)
1384
1385        attributeAuthorityClnt = AttributeAuthorityClient(
1386                            uri=attributeAuthorityURI,
1387                            sslCACertFilePathList=self.sslCACertFilePathList,
1388                            cfg=self.wssCfgFilePath or self._cfg,
1389                            cfgFileSection=self.wssCfgSection,
1390                            cfgFilePrefix=self.wssCfgPrefix,
1391                            **(self.wssCfgKw or {}))
1392       
1393        # If a user certificate is set, use this to sign messages instead of
1394        # the default settings in the WS-Security config. 
1395        if attributeAuthorityClnt.signatureHandler is not None and \
1396           self.userPriKey is not None:
1397            if self.issuingX509Cert is not None:
1398                # Pass a chain of certificates -
1399                # Initialise WS-Security signature handling to pass
1400                # BinarySecurityToken containing user cert and cert for user
1401                # cert issuer
1402                attributeAuthorityClnt.signatureHandler.reqBinSecTokValType = \
1403                            SignatureHandler.binSecTokValType["X509PKIPathv1"]
1404                attributeAuthorityClnt.signatureHandler.signingCertChain = (
1405                                    self.issuingX509Cert, self.userX509Cert)               
1406
1407                attributeAuthorityClnt.signatureHandler.signingPriKey = \
1408                                                            self.userPriKey
1409            elif self.userX509Cert is not None:
1410                # Pass user cert only - no need to pass a cert chain. 
1411                # This type of token is more likely to be supported by the
1412                # various WS-Security toolkits
1413                attributeAuthorityClnt.signatureHandler.reqBinSecTokValType = \
1414                                    SignatureHandler.binSecTokValType["X509v3"]
1415                attributeAuthorityClnt.signatureHandler.signingCert = \
1416                                                            self.userX509Cert
1417
1418                attributeAuthorityClnt.signatureHandler.signingPriKey = \
1419                                                            self.userPriKey
1420
1421        return attributeAuthorityClnt
1422
1423
1424    def _getAttCert(self, 
1425                    attributeAuthorityURI=None, 
1426                    attributeAuthority=None,
1427                    extAttCert=None):       
1428        """Wrapper to Attribute Authority attribute certificate request.  See
1429        getAttCert for the classes' public interface.
1430       
1431        If successful, a new attribute certificate is issued to the user
1432        and added into the wallet
1433       
1434        @type attributeAuthorityURI: string
1435        @param attributeAuthorityURI: to call as a web service, specify the URI
1436        for the Attribute Authority.
1437       
1438        @type attributeAuthority: ndg.security.server.attributeauthority.AttributeAuthority
1439        @param attributeAuthority: Alternative to attributeAuthorityURI - to
1440        run on the local machine, specify a local Attribute Authority
1441        instance.
1442
1443        @type extAttCert: ndg.security.common.AttCert.AttCert
1444        @param extAttCert: an existing Attribute Certificate which can
1445        be used to making a mapping should the user not be registered with the
1446        Attribute Authority"""
1447     
1448        log.debug("NDGCredentialWallet._getAttCert ...")
1449       
1450       
1451        # If a user cert. is present, ignore the user ID setting.  The
1452        # Attribute Authority will set the userId field of the
1453        # Attribute Certificate based on the DN of the user certificate
1454        if self.userX509Cert:
1455            userId = str(self.userX509Cert.dn)
1456        else:
1457            userId = self.userId
1458           
1459        if attributeAuthority is not None and \
1460           attributeAuthorityURI is not None:
1461            raise KeyError("Both attributeAuthorityURI and attributeAuthority "
1462                           "keywords have been set")
1463       
1464        if attributeAuthority is None:
1465            attributeAuthority = self.attributeAuthority
1466           
1467        if attributeAuthorityURI is None:
1468            attributeAuthorityURI = self.attributeAuthorityURI
1469           
1470        # Set a client alias according to whether the Attribute Authority is
1471        # being called locally or as a remote service
1472        if attributeAuthorityURI is not None:
1473            # Call Remote Service at given URI
1474            aaInterface = self._createAttributeAuthorityClnt(
1475                                                        attributeAuthorityURI)                           
1476            log.debug('NDGCredentialWallet._getAttCert for remote Attribute '
1477                      'Authority service: "%s" ...' % attributeAuthorityURI)
1478               
1479        elif attributeAuthority is not None:
1480            # Call local based Attribute Authority with settings from the
1481            # configuration file attributeAuthority
1482            aaInterface = attributeAuthority
1483            log.debug('NDGCredentialWallet._getAttCert for local Attribute '
1484                      'Authority: "%r" ...' % attributeAuthority)
1485        else:
1486            raise CredentialWalletError("Error requesting attribute: "
1487                                        "certificate a URI or Attribute "
1488                                        "Authority instance must be specified")
1489       
1490        try:
1491            # Request a new attribute certificate from the Attribute
1492            # Authority
1493            attCert = aaInterface.getAttCert(userId=userId,
1494                                             userAttCert=extAttCert)
1495           
1496            log.info('Granted Attribute Certificate from issuer DN = "%s"'%
1497                     attCert.issuerDN)
1498           
1499        except (AttributeAuthorityAccessDenied, AttributeRequestDenied), e:
1500            # AttributeAuthorityAccessDenied is raised if
1501            # aaInterface is a local AA instance and
1502            # AttributeRequestDenied is raised for a client to a remote AA
1503            # service
1504            raise CredentialWalletAttributeRequestDenied(str(e))
1505                   
1506        except Exception, e:
1507            raise CredentialWalletError("Requesting attribute certificate: %s"%
1508                                        e)
1509
1510        # Update attribute Certificate instance with CA's certificate ready
1511        # for signature check in addCredential()
1512        if self._caCertFilePathList is None:
1513            raise CredentialWalletError("No CA certificate has been set")
1514       
1515        attCert.certFilePathList = self._caCertFilePathList
1516
1517       
1518        # Add credential into wallet
1519        #
1520        # Nb. if the certificates signature is invalid, it will be rejected
1521        log.debug("Adding credentials into wallet...")
1522        self.addCredential(attCert)
1523       
1524        return attCert
1525
1526    def _getAAHostInfo(self, 
1527                       attributeAuthority=None,
1528                       attributeAuthorityURI=None):
1529        """Wrapper to Attribute Authority getHostInfo
1530       
1531        _getAAHostInfo([attributeAuthority=f|attributeAuthorityURI=u])
1532                   
1533        @type userRole: string
1534        @param userRole: get hosts which have a mapping to this role
1535       
1536        @type attributeAuthorityURI: string
1537        @param attributeAuthorityURI: to call as a web service, specify the URI
1538        for the Attribute Authority.
1539       
1540        @type attributeAuthority: string
1541        @param attributeAuthority: Alternative to attributeAuthorityURI - to
1542        run on the local machine, specify the local Attribute Authority
1543        instance.
1544        """
1545
1546        if attributeAuthority is None:
1547            attributeAuthority = self.attributeAuthority
1548           
1549        if attributeAuthorityURI is None:
1550            attributeAuthorityURI = self.attributeAuthorityURI
1551       
1552        log.debug('NDGCredentialWallet._getAAHostInfo for service: "%s" ...' % 
1553                  attributeAuthorityURI or attributeAuthority)
1554           
1555        # Set a client alias according to whether the Attribute Authority is
1556        # being called locally or asa remote service
1557        if attributeAuthorityURI is not None:
1558            # Call Remote Service at given URI
1559            attributeAuthorityClnt = self._createAttributeAuthorityClnt(
1560                                                    attributeAuthorityURI)
1561
1562        elif attributeAuthority is not None:
1563            # Call local based Attribute Authority with settings from the
1564            # configuration file attributeAuthority
1565            attributeAuthorityClnt = attributeAuthority
1566           
1567        else:
1568            raise CredentialWalletError("Error requesting trusted hosts info: " 
1569                                        "a URI or Attribute Authority " 
1570                                        "configuration file must be specified")
1571           
1572        try:
1573            # Request a new attribute certificate from the Attribute
1574            # Authority
1575            return attributeAuthorityClnt.getHostInfo()
1576           
1577        except Exception, e:
1578            log.error("Requesting host info: %s" % e)
1579            raise
1580
1581    def _getAATrustedHostInfo(self, 
1582                              userRole=None,
1583                              attributeAuthority=None,
1584                              attributeAuthorityURI=None):
1585        """Wrapper to Attribute Authority getTrustedHostInfo
1586       
1587        _getAATrustedHostInfo([userRole=r, ][attributeAuthority=f|
1588                              attributeAuthorityURI=u])
1589                   
1590        @type userRole: string
1591        @param userRole: get hosts which have a mapping to this role
1592       
1593        @type attributeAuthorityURI: string
1594        @param attributeAuthorityURI: to call as a web service, specify the URI
1595        for the Attribute Authority.
1596       
1597        @type attributeAuthority: string
1598        @param attributeAuthority: Alternative to attributeAuthorityURI - to
1599        run on the local machine, specify the local Attribute Authority
1600        instance.
1601        """
1602
1603        if attributeAuthority is None:
1604            attributeAuthority = self.attributeAuthority
1605           
1606        if attributeAuthorityURI is None:
1607            attributeAuthorityURI = self.attributeAuthorityURI
1608       
1609        log.debug('NDGCredentialWallet._getAATrustedHostInfo for role "%s" and '
1610                  'service: "%s" ...' % (userRole, 
1611                                attributeAuthorityURI or attributeAuthority))
1612           
1613        # Set a client alias according to whether the Attribute Authority is
1614        # being called locally or asa remote service
1615        if attributeAuthorityURI is not None:
1616            # Call Remote Service at given URI
1617            attributeAuthorityClnt = self._createAttributeAuthorityClnt(
1618                                                    attributeAuthorityURI)
1619
1620        elif attributeAuthority is not None:
1621            # Call local based Attribute Authority with settings from the
1622            # configuration file attributeAuthority
1623            attributeAuthorityClnt = attributeAuthority
1624           
1625        else:
1626            raise CredentialWalletError("Error requesting trusted hosts info: " 
1627                                        "a URI or Attribute Authority " 
1628                                        "configuration file must be specified")
1629           
1630        try:
1631            # Request a new attribute certificate from the Attribute
1632            # Authority
1633            return attributeAuthorityClnt.getTrustedHostInfo(role=userRole)
1634           
1635        except Exception, e:
1636            log.error("Requesting trusted host info: %s" % e)
1637            raise
1638
1639    def getAttCert(self,
1640                   reqRole=None,
1641                   attributeAuthority=None,
1642                   attributeAuthorityURI=None,
1643                   mapFromTrustedHosts=None,
1644                   rtnExtAttCertList=None,
1645                   extAttCertList=None,
1646                   extTrustedHostList=None,
1647                   refreshAttCert=False,
1648                   attCertRefreshElapse=None):
1649       
1650        """Get an Attribute Certificate from an Attribute Authority.  If this
1651        fails try to make a mapped Attribute Certificate by using a certificate
1652        from another host which has a trust relationship to the Attribute
1653        Authority in question.
1654
1655        getAttCert([reqRole=r, ][attributeAuthority=a|attributeAuthorityURI=u,]
1656                   [mapFromTrustedHosts=m, ]
1657                   [rtnExtAttCertList=e, ][extAttCertList=el, ]
1658                   [extTrustedHostList=et, ][refreshAttCert=ra])
1659                 
1660        The procedure is:
1661
1662        1) Try attribute request using user certificate
1663        2) If the Attribute Authority (AA) doesn't recognise the certificate,
1664        find out any other hosts which have a trust relationship to the AA.
1665        3) Look for Attribute Certificates held in the wallet corresponding
1666        to these hosts.
1667        4) If no Attribute Certificates are available, call the relevant
1668        hosts' AAs to get certificates
1669        5) Finally, use these new certificates to try to obtain a mapped
1670        certificate from the original AA
1671        6) If this fails access is denied     
1672                   
1673        @type reqRole: string
1674        @param reqRole: the required role to get access for
1675       
1676        @type attributeAuthorityURI: string
1677        @param attributeAuthorityURI: to call as a web service, specify the URI
1678        for the Attribute Authority.
1679       
1680        @type attributeAuthority: string
1681        @param attributeAuthority: Altenrative to attributeAuthorityURI - to
1682        run on the local machine, specify a local Attribute Authority
1683        instance.
1684                               
1685        @type mapFromTrustedHosts: bool / None     
1686        @param mapFromTrustedHosts: if request fails via the user's cert
1687        ID, then it is possible to get a mapped certificate by using
1688        certificates from other AA's.  Set this flag to True, to allow this
1689        second stage of generating a mapped certificate from the certificate
1690        stored in the wallet credentials.
1691
1692        If set to False, it is possible to return the list of certificates
1693        available for mapping and then choose which one or ones to use for
1694        mapping by re-calling getAttCert with extAttCertList set to these
1695        certificates.
1696       
1697        Defaults to None in which case self._mapFromTrustedHosts is not
1698        altered
1699
1700        The list is returned via CredentialWalletAttributeRequestDenied
1701        exception.  If no value is set, the default value held in
1702        self.mapFromTrustedHosts is used
1703
1704        @type rtnExtAttCertList: bool / None
1705        @param rtnExtAttCertList: If request fails, make a list of
1706        candidate certificates from other Attribute Authorities which the user
1707        could use to retry and get a mapped certificate.
1708                               
1709        If mapFromTrustedHosts is set True this flags value is overriden and
1710        effectively set to True.
1711
1712        If no value is set, the default value held in self._rtnExtAttCertList
1713        is used.
1714                               
1715        The list is returned via a CredentialWalletAttributeRequestDenied
1716        exception object.
1717                               
1718        @type extAttCertList: list
1719        @param extAttCertList: Attribute Certificate or list of certificates
1720        from other Attribute Authorities.  These can be used to get a mapped
1721        certificate if access fails based on the user's certificate
1722        credentials.  They are tried out in turn until access is granted so
1723        the order of the list decides the order in which they will be tried
1724
1725        @type extTrustedHostList:
1726        @param extTrustedHostList: same as extAttCertList keyword, but
1727        instead of providing Attribute Certificates, give a list of Attribute
1728        Authority hosts.  These will be matched up to Attribute Certificates
1729        held in the wallet.  Matching certificates will then be used to try to
1730        get a mapped Attribute Certificate.
1731       
1732        @type refreshAttCert: bool
1733        @param refreshAttCert: if set to True, the attribute request
1734        will go ahead even if the wallet already contains an Attribute
1735        Certificate from the target Attribute Authority.  The existing AC in
1736        the wallet will be replaced by the new one obtained from this call.
1737                               
1738        If set to False, this method will check to see if an AC issued by the
1739        target AA already exists in the wallet.  If so, it will return this AC
1740        to the caller without proceeding to make a call to the AA.
1741       
1742        @type attCertRefreshElapse: float / int
1743        @param attCertRefreshElapse: determine whether to replace an
1744        existing AC in the cache with a fresh one.  If the existing one has
1745        less than attCertRefreshElapse time in seconds left before expiry then
1746        replace it.
1747       
1748        @rtype: ndg.security.common.AttCert.AttCert
1749        @return: Attribute Certificate retrieved from Attribute Authority"""
1750       
1751        log.debug("NDGCredentialWallet.getAttCert ...")
1752       
1753        # Both these assignments are calling set property methods implicitly!
1754        if attributeAuthorityURI:
1755            self.attributeAuthorityURI = attributeAuthorityURI
1756           
1757        if attributeAuthority is not None:
1758            self.attributeAuthority = attributeAuthority
1759           
1760        if not refreshAttCert and self.credentials:
1761            # Refresh flag is not set so it's OK to check for any existing
1762            # Attribute Certificate in the wallet whose issuerName match the
1763            # target AA's name
1764           
1765            # Find out the site ID for the target AA by calling AA's host
1766            # info WS method
1767            log.debug("NDGCredentialWallet.getAttCert - check AA site ID ...")
1768            try:
1769                hostInfo = self._getAAHostInfo()
1770                aaName = hostInfo.keys()[0]
1771            except Exception, e:
1772                raise CredentialWalletError("Getting host info: %s" % e)
1773           
1774            # Look in the wallet for an AC with the same issuer name
1775            if aaName in self.credentials:
1776                # Existing Attribute Certificate found in wallet - Check that
1777                # it will be valid for at least the next 2 hours
1778                if attCertRefreshElapse is not None:
1779                    self.attCertRefreshElapse = attCertRefreshElapse
1780                   
1781                dtNow = datetime.utcnow() + \
1782                        timedelta(seconds=self.attCertRefreshElapse)
1783               
1784                attCert = self.credentials[aaName]['attCert']
1785                if attCert.isValidTime(dtNow=dtNow):
1786                    log.info("Retrieved an existing %s AC from the wallet" % 
1787                             aaName)
1788                    return attCert
1789                     
1790        # Check for settings from input, if not set use previous settings
1791        # made
1792        if mapFromTrustedHosts is not None:
1793            self.mapFromTrustedHosts = mapFromTrustedHosts
1794
1795        if rtnExtAttCertList is not None:
1796            self.rtnExtAttCertList = rtnExtAttCertList
1797
1798        # Check for list of external trusted hosts (other trusted NDG data
1799        # centres)
1800        if extTrustedHostList:
1801            log.info("Checking for ACs in wallet matching list of trusted "
1802                     "hosts set: %s" % extTrustedHostList)
1803           
1804            if not self.mapFromTrustedHosts:
1805                raise CredentialWalletError("A list of trusted hosts has been " 
1806                                      "input but mapping from trusted hosts "
1807                                      "is set to disallowed")
1808           
1809            if isinstance(extTrustedHostList, basestring):
1810                extTrustedHostList = [extTrustedHostList]
1811
1812            # Nb. Any extAttCertList is overriden by extTrustedHostList being
1813            # set
1814            extAttCertList = [self.credentials[hostName]['attCert'] 
1815                              for hostName in extTrustedHostList
1816                              if hostName in self.credentials]
1817
1818        # Set an empty list to trigger an AttributeError by initialising it to
1819        # None
1820        if extAttCertList == []:
1821            extAttCertList = None
1822           
1823        # Repeat authorisation attempts until succeed or means are exhausted
1824        while True:
1825           
1826            # Check for candidate certificates for mapping
1827            try:
1828                # If list is set get the next cert
1829                extAttCert = extAttCertList.pop()
1830
1831            except AttributeError:
1832                log.debug("No external Attribute Certificates - trying "
1833                          "request without mapping...")
1834                # No List set - attempt request without
1835                # using mapping from trusted hosts
1836                extAttCert = None
1837                           
1838            except IndexError:
1839               
1840                # List has been emptied without attribute request succeeding -
1841                # give up
1842                errMsg = ("Attempting to obtained a mapped certificate: "
1843                          "no external attribute certificates are available")
1844                   
1845                # Add the exception form the last call to the Attribute
1846                # Authority if an error exists
1847                try:
1848                    errMsg += ": %s" % attributeRequestDenied
1849                except NameError:
1850                    pass
1851
1852                raise CredentialWalletAttributeRequestDenied(errMsg)
1853                                                   
1854               
1855            # Request Attribute Certificate from Attribute Authority
1856            try:
1857                attCert = self._getAttCert(extAttCert=extAttCert)               
1858                # Access granted
1859                return attCert
1860           
1861            except CredentialWalletAttributeRequestDenied, \
1862                   attributeRequestDenied:
1863                if not self.mapFromTrustedHosts and not self.rtnExtAttCertList:
1864                    log.debug("Creating a mapped certificate option is not "
1865                              "set - raising "
1866                              "CredentialWalletAttributeRequestDenied "
1867                              "exception saved from earlier")
1868                    raise attributeRequestDenied
1869
1870                if isinstance(extAttCertList, list):
1871                    # An list of attribute certificates from trusted hosts
1872                    # is present continue cycling through this until one of
1873                    # them is accepted and a mapped certificate can be derived
1874                    log.debug("AC request denied - but external ACs available "
1875                              "to try mapped AC request ...")
1876                    continue
1877                             
1878                #  Use the input required role and the AA's trusted host list
1879                # to identify attribute certificates from other hosts which
1880                # could be used to make a mapped certificate
1881                log.debug("Getting a list of trusted hosts for mapped AC "
1882                          "request ...")
1883                try:
1884                    trustedHostInfo = self._getAATrustedHostInfo(reqRole)
1885                   
1886                except NoMatchingRoleInTrustedHosts, e:
1887                    raise CredentialWalletAttributeRequestDenied(
1888                        'Can\'t get a mapped Attribute Certificate for '
1889                        'the "%s" role' % reqRole)
1890               
1891                except Exception, e:
1892                    raise CredentialWalletError("Getting trusted hosts: %s"%e)
1893
1894                if not trustedHostInfo:
1895                    raise CredentialWalletAttributeRequestDenied(
1896                        "Attribute Authority has no trusted hosts with "
1897                        "which to make a mapping")
1898
1899                # Initialise external certificate list here - if none are
1900                # found IndexError will be raised on the next iteration and
1901                # an access denied error will be raised
1902                extAttCertList = []
1903
1904                # Look for Attribute Certificates with matching issuer host
1905                # names
1906                log.debug("Checking wallet for ACs issued by one of the "
1907                          "trusted hosts...")
1908                for hostName in self.credentials:
1909
1910                    # Nb. Candidate certificates for mappings must have
1911                    # original provenance and contain at least one of the
1912                    # required roles
1913                    attCert = self.credentials[hostName].credential
1914                   
1915                    if hostName in trustedHostInfo and attCert.isOriginal():                       
1916                        for role in attCert.roles:
1917                            if role in trustedHostInfo[hostName]['role']:                               
1918                                extAttCertList.append(attCert)
1919
1920                if not extAttCertList:
1921                    log.debug("No wallet ACs matched any of the trusted "
1922                              "hosts.  - Try request for an AC from a "
1923                              "trusted host ...")
1924                   
1925                    # No certificates in the wallet matched the trusted host
1926                    # and required roles
1927                    #
1928                    # Try each host in turn in order to get a certificate with
1929                    # the required credentials in order to do a mapping
1930                    for host, info in trustedHostInfo.items():
1931                        try:
1932                            # Try request to trusted host
1933                            extAttCert = self._getAttCert(
1934                                        attributeAuthorityURI=info['aaURI'])
1935
1936                            # Check the certificate contains at least one of
1937                            # the required roles
1938                            if [True for r in extAttCert.roles
1939                                if r in info['role']]:
1940                               extAttCertList.append(extAttCert)
1941
1942                               # For efficiency, stop once obtained a valid
1943                               # cert - but may want complete list for user to
1944                               # choose from
1945                               #break
1946                               
1947                        except Exception, e:
1948                            # ignore any errors and continue
1949                            log.warning('AC request to trusted host "%s"' 
1950                                        ' resulted in: %s' % (info['aaURI'],e))
1951                           
1952                if not extAttCertList:                       
1953                    raise CredentialWalletAttributeRequestDenied(
1954                        "No certificates are available with which to "
1955                        "make a mapping to the Attribute Authority")
1956
1957
1958                if not self.mapFromTrustedHosts:
1959                   
1960                    # Exit here returning the list of candidate certificates
1961                    # that could be used to make a mapped certificate
1962                    msg = ("User is not registered with Attribute "
1963                           "Authority - retry using one of the returned "
1964                           "Attribute Certificates obtained from other " 
1965                           "trusted hosts")
1966                         
1967                    raise CredentialWalletAttributeRequestDenied(msg,
1968                                            extAttCertList=extAttCertList,
1969                                            trustedHostInfo=trustedHostInfo)
1970       
1971       
1972class CredentialRepositoryError(_CredentialWalletException):   
1973    """Exception handling for NDG Credential Repository class."""
1974
1975
1976class CredentialRepository(object):
1977    """CredentialWallet's abstract interface class to a Credential Repository.
1978    The Credential Repository is abstract store of user currently valid user
1979    credentials.  It enables retrieval of attribute certificates from a user's
1980    previous session(s)"""
1981       
1982    def __init__(self, propFilePath=None, dbPPhrase=None, **prop):
1983        """Initialise Credential Repository abstract base class.  Derive from
1984        this class to define Credentail Repository interface Credential
1985        Wallet
1986
1987        If the connection string or properties file is set a connection
1988        will be made
1989
1990        @type dbPPhrase: string
1991        @param dbPPhrase: pass-phrase to database if applicable
1992       
1993        @type propFilePath: string
1994        @param propFilePath: file path to a properties file.  This could
1995        contain configuration parameters for the repository e.g.  database
1996        connection parameters
1997       
1998        @type **prop: dict
1999        @param **prop: any other keywords required
2000        """
2001        raise NotImplementedError(
2002            self.__init__.__doc__.replace('\n       ',''))
2003
2004
2005    def addUser(self, userId, dn=None):
2006        """A new user to Credentials Repository
2007       
2008        @type userId: string
2009        @param userId: userId for new user
2010        @type dn: string
2011        @param dn: users Distinguished Name (optional)"""
2012        raise NotImplementedError(
2013            self.addUser.__doc__.replace('\n       ',''))
2014
2015                           
2016    def auditCredentials(self, userId=None, **attCertValidKeys):
2017        """Check the attribute certificates held in the repository and delete
2018        any that have expired
2019
2020        @type userId: basestring/list or tuple
2021        @param userId: audit credentials for the input user ID or list of IDs
2022        @type attCertValidKeys: dict
2023        @param **attCertValidKeys: keywords which set how to check the
2024        Attribute Certificate e.g. check validity time, XML signature, version
2025         etc.  Default is check validity time only - See AttCert class"""
2026        raise NotImplementedError(
2027            self.auditCredentials.__doc__.replace('\n       ',''))
2028
2029
2030    def getCredentials(self, userId):
2031        """Get the list of credentials for a given users DN
2032       
2033        @type userId: string
2034        @param userId: users userId, name or X.509 cert. distinguished name
2035        @rtype: list
2036        @return: list of Attribute Certificates"""
2037        raise NotImplementedError(
2038            self.getCredentials.__doc__.replace('\n       ',''))
2039
2040       
2041    def addCredentials(self, userId, attCertList):
2042        """Add new attribute certificates for a user.  The user must have
2043        been previously registered in the repository
2044
2045        @type userId: string
2046        @param userId: users userId, name or X.509 cert. distinguished name
2047        @type attCertList: list
2048        @param attCertList: list of attribute certificates"""
2049        raise NotImplementedError(
2050            self.addCredentials.__doc__.replace('\n       ',''))
2051
2052
2053class NullCredentialRepository(CredentialRepository):
2054    """Implementation of Credential Repository interface with empty stubs. 
2055    Use this class in the case where no Credential Repository is required"""
2056   
2057    def __init__(self, propFilePath=None, dbPPhrase=None, **prop):
2058        pass
2059
2060    def addUser(self, userId):
2061        pass
2062                           
2063    def auditCredentials(self, **attCertValidKeys):
2064        pass
2065
2066    def getCredentials(self, userId):
2067        return []
2068       
2069    def addCredentials(self, userId, attCertList):
2070        pass
Note: See TracBrowser for help on using the repository browser.