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

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

Working authz lite integration tests with integrated SAML Attribute Authority interface to authz middleware: the old NDG Attribute Authority SOAP/WSDL interface is completely removed as a dependency.

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