source: TI12-security/trunk/python/ndg.security.common/ndg/security/common/CredWallet.py @ 4290

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg-security/TI12-security/trunk/python/ndg.security.common/ndg/security/common/CredWallet.py@4290
Revision 4290, 61.4 KB checked in by pjkersha, 12 years ago (diff)

Refactoring of CredWallet? - added unit tests for AA getAttCert call with a userId (as in DEWS) and with a personal X.509 cert.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1"""NDG Credentials Wallet
2
3NERC Data Grid Project
4"""
5__author__ = "P J Kershaw"
6__date__ = "30/11/05"
7__copyright__ = "(C) 2008 STFC & NERC"
8__license__ = \
9"""This software may be distributed under the terms of the Q Public
10License, version 1.0 or later."""
11__contact__ = "P.J.Kershaw@rl.ac.uk"
12__revision__ = '$Id$'
13
14import logging
15log = logging.getLogger(__name__)
16
17# Temporary store of certificates for use with CredWallet getAttCert()
18import tempfile
19
20# Check Attribute Certificate validity times
21from datetime import datetime
22from datetime import timedelta
23
24
25# Access Attribute Authority's web service using ZSI - allow pass if not
26# loaded since it's possible to make AttAuthority instance locally without
27# using the WS
28aaImportError = True
29try:
30    # AttAuthority client package resides with CredWallet module in
31    # ndg.security.common
32    from ndg.security.common.AttAuthority import AttAuthorityClient, \
33        AttAuthorityClientError, AttributeRequestDenied, \
34        NoMatchingRoleInTrustedHosts
35    aaImportError = False
36except ImportError:
37    log.warning('Loading CredWallet without SOAP interface imports')
38    pass
39
40# Likewise - may not want to use WS and use AttAuthority locally in which case
41# no need to import it
42try:
43    from ndg.security.server.AttAuthority import AttAuthority, \
44        AttAuthorityError, AttAuthorityAccessDenied
45    aaImportError = False
46except:
47    log.warning('Loading CredWallet without Attribute Authority interface '
48                'imports')
49    pass
50
51if aaImportError:
52    raise ImportError("Either AttAuthority or AttAuthorityClient classes must "
53                      "be present to allow interoperation with Attribute "
54                      "Authorities")
55
56# Authentication X.509 Certificate
57from ndg.security.common.X509 import *
58from M2Crypto import X509, BIO, RSA
59
60# Authorisation - attribute certificate
61from ndg.security.common.AttCert import *
62from ndg.security.common.wssecurity.dom import SignatureHandler
63
64# generic parser to read INI/XML properties file
65from ndg.security.common.utils.ConfigFileParsers import \
66                                                INIPropertyFileWithValidation
67
68
69class _CredWalletException(Exception):   
70    """Generic Exception class for CredWallet module.  Overrides Exception to
71    enable writing to the log"""
72    def __init__(self, msg):
73        log.error(msg)
74        Exception.__init__(self, msg)
75
76
77class CredWalletError(_CredWalletException):   
78    """Exception handling for NDG Credential Wallet class.  Overrides Exception
79    to enable writing to the log"""
80
81
82class CredWalletAttributeRequestDenied(CredWalletError):   
83    """Handling exception where CredWallet is denied authorisation by an
84    Attribute Authority.
85 
86    @type __extAttCertList: list
87    @ivar __extAttCertList: list of candidate Attribute Certificates that
88    could be used to try to get a mapped certificate from the target
89    Attribute Authority
90   
91    @type __trustedHostInfo: dict
92    @ivar __trustedHostInfo: dictionary indexed by host name giving
93    details of Attribute Authority URI and roles for trusted hosts"""
94   
95    def __init__(self, *args, **kw):
96        """Raise exception for attribute request denied with option to give
97        caller hint to certificates that could used to try to obtain a
98        mapped certificate
99       
100        @type extAttCertList: list
101        @param extAttCertList: list of candidate Attribute Certificates that
102        could be used to try to get a mapped certificate from the target
103        Attribute Authority
104       
105        @type trustedHostInfo: dict
106        @param trustedHostInfo: dictionary indexed by host name giving
107        details of Attribute Authority URI and roles for trusted hosts"""
108       
109        self.__trustedHostInfo = kw.pop('trustedHostInfo', {})
110        self.__extAttCertList = kw.pop('extAttCertList', [])
111           
112        CredWalletError.__init__(self, *args, **kw)
113
114    def _getTrustedHostInfo(self):
115        """Get message text"""
116        return self.__trustedHostInfo
117
118    trustedHostInfo = property(fget=_getTrustedHostInfo, 
119                               doc="URI and roles details for trusted hosts")
120       
121    def _getExtAttCertList(self):
122        """Return list of candidate Attribute Certificates that could be used
123        to try to get a mapped certificate from the target Attribute Authority
124        """
125        return self.__extAttCertList
126
127    extAttCertList = property(fget=_getExtAttCertList,
128                              doc="list of candidate Attribute Certificates "
129                              "that could be used to try to get a mapped "
130                              "certificate from the target Attribute "
131                              "Authority")
132
133         
134class _MetaCredWallet(type):
135    """Enable CredWallet to have read only class variables e.g.
136   
137    print CredWallet.accessDenied
138   
139    ... is allowed but,
140   
141    CredWallet.accessDenied = None
142   
143    ... raises - AttributeError: can't set attribute"""
144   
145    def _getAccessDenied(cls):
146        '''accessDenied get method'''
147        return False
148   
149    accessDenied = property(fget=_getAccessDenied)
150   
151    def _getAccessGranted(cls):
152        '''accessGranted get method'''
153        return True
154   
155    accessGranted = property(fget=_getAccessGranted)
156
157
158# CredWallet is a 'new-style' class inheriting from "object" and making use
159# of new Get/Set methods for hiding of attributes
160class CredWallet(object):
161    """Volatile store of user credentials associated with a user session
162   
163    @type userX509Cert: string / M2Crypto.X509.X509 /
164    ndg.security.common.X509.X509Cert
165    @ivar userX509Cert: X.509 certificate for user (property attribute)
166   
167    @type userPriKey: string / M2Crypto.RSA.RSA
168    @ivar userPriKey: private key for user cert (property attribute)
169   
170    @type issuingX509Cert: string / ndg.security.common.X509.X509Cert
171    @ivar issuingX509Cert: X.509 cert for issuer of user cert (property
172    attribute)
173   
174    @type attributeAuthorityURI: string
175    @ivar attributeAuthorityURI: URI of Attribute Authority to make
176    requests to.  Setting this ALSO creates an AttAuthorityClient instance
177    self._attributeAuthorityClnt.  - See attributeAuthorityURI property for
178    details. (property attribute)
179   
180    @type attributeAuthority: ndg.security.server.AttAuthority.AttAuthority
181    @ivar attributeAuthority: Attribute Authority to make requests to. 
182    attributeAuthorityURI takes precedence over this keyword i.e. if an
183    attributeAuthorityURI has been set, then calls are made to the AA web
184    service at this location rather to any self.attributeAuthority running
185    locally. (property attribute)
186   
187    @type caCertFilePathList: string (for single file), list or tuple
188    @ivar caCertFilePathList: Certificate Authority's certificates - used
189    in validation of signed Attribute Certificates and WS-Security
190    signatures of incoming messages.  If not set here, it must
191    be input in call to getAttCert. (property attribute)
192           
193    @type credentialRepository: instance of CredentialRepository derived
194    class
195    @ivar credentialRepository: Credential Repository instance.   (property
196    attribute).  If not set, defaults to NullCredentialRepository type - see
197    class below...
198
199   
200    @type mapFromTrustedHosts: bool
201    @ivar mapFromTrustedHosts sets behaviour for getAttCert().  If
202    set True and authorisation fails with the given Attribute Authority,
203    attempt to get authorisation using Attribute Certificates issued by
204    other trusted AAs. (property attribute)
205   
206    @type rtnExtAttCertList: bool
207    @ivar rtnExtAttCertList: behaviour for getAttCert().  If True, and
208    authorisation fails with the given Attribute Authority, return a list
209    of Attribute Certificates from other trusted AAs which could be used
210    to obtain a mapped Attribute Certificate on a subsequent authorisation
211    attempt. (property attribute)
212   
213    @type attCertRefreshElapse: float / int
214    @ivar attCertRefreshElapse: used by getAttCert to determine
215    whether to replace an existing AC in the cache with a fresh one.  If
216    the existing one has less than attCertRefreshElapse time in seconds
217    left before expiry then replace it. (property attribute)
218   
219    @type wssCfgKw: dict
220    @ivar wssCfgKw: keywords to WS-Security SignatureHandler
221    used for Credential Wallet's SOAP interface to Attribute Authorities.
222    (property attribute)
223           
224    @type _credentialRepository: ndg.security.common.CredentialRepository or
225    derivative
226    @ivar _credentialRepository: reference to Credential Repository object. 
227    An optional non-volatile cache for storage of wallet info which can be
228    later restored. (Don't reference directly - see equivalent property
229    attribute)
230
231    @type _mapFromTrustedHosts: bool
232    @ivar _mapFromTrustedHosts: if true, allow a mapped attribute certificate
233    to obtained in a getAttCert call.  Set false to prevent mappings.
234    (Don't reference directly - see equivalent property attribute)
235
236    @type _rtnExtAttCertList: bool
237    @ivar _rtnExtAttCertList: if true, return a list of external attribute
238    certificates from getAttCert call. (Don't reference directly - see
239    equivalent property attribute)
240
241    @type __dn: ndg.security.common.X509.X500DN
242    @ivar __dn: distinguished name from user certificate.  (Don't reference
243    directly - see equivalent property attribute)
244
245    @type _credentials: dict       
246    @ivar _credentials: Credentials are stored as a dictionary one element per
247    attribute certificate held and indexed by certificate issuer name.
248    (Don't reference directly - see equivalent property attribute)
249
250    @type _caCertFilePathList: basestring, list, tuple or None
251    @ivar _caCertFilePathList: file path(s) to CA certificates.  If None
252    then the input is quietly ignored.  See caCertFilePathList property.
253    (Don't reference directly - see equivalent property attribute)
254
255    @type _userX509Cert: ndg.security.common.X509.X509Cert
256    @ivar _userX509Cert: X.509 user certificate instance.
257    (Don't reference directly - see equivalent property attribute)
258
259    @type _issuingX509Cert: ndg.security.common.X509.X509Cert
260    @ivar _issuingX509Cert: X.509 user certificate instance.
261    (Don't reference directly - see equivalent property attribute)
262 
263    @type _userPriKey: M2Crypto.RSA.RSA
264    @ivar _userPriKey: Private key used to sign outbound message.
265    (Don't reference directly - see equivalent property attribute)
266    """
267
268    __metaclass__ = _MetaCredWallet
269
270    _defParam = dict(userId=None,
271                     userX509Cert=None,
272                     userPriKey=None,
273                     issuingX509Cert=None,
274                     caCertFilePathList=None,
275                     sslCACertFilePathList=None,
276                     attributeAuthorityURI=None,
277                     attributeAuthority=None,
278                     credentialRepository=None,
279                     mapFromTrustedHosts=False,
280                     rtnExtAttCertList=True,
281                     attCertRefreshElapse=7200,
282                     wssCfgFilePath=None,
283                     wssCfgSection='DEFAULT',
284                     wssCfgKw={})
285   
286    __slots__ = _defParam.keys() + [
287        '_userX509Cert',
288        '_userPriKey',
289        '_issuingX509Cert',
290        '_attributeAuthorityClnt',
291        '_attributeAuthority',
292        '_caCertFilePathList',
293        '_sslCACertFilePathList',
294        '_credentialRepository',
295        '_attCertRefreshElapse',
296        '_cfg',
297        '_credentials',
298        '_dn',
299        '_attributeAuthorityURI'
300    ]
301   
302    def __init__(self, 
303                 cfg=None, 
304                 cfgFileSection='DEFAULT', 
305                 cfgPrefix='', 
306                 wssCfgKw={},
307                 **kw):
308        """Create store of user credentials for their current session
309
310        @type cfg: string / ConfigParser object
311        @param cfg: if a string type, this is interpreted as the file path to
312        a configuration file, otherwise it will be treated as a ConfigParser
313        object
314        @type cfgSection: string
315        @param cfgSection: sets the section name to retrieve config params
316        from
317        @type cfgPrefix: basestring
318        @param cfgPrefix: apply a prefix to all CredWallet config params so
319        that if placed in a file with other parameters they can be
320        distinguished
321        @type cfgKw: dict
322        @param cfgKw: set parameters as key value pairs."""
323
324        log.debug("Calling CredWallet.__init__ ...")
325
326        # Initialise attributes
327        attr = {}.fromkeys(CredWallet.__slots__)
328        attr.update(CredWallet._defParam)
329        for k, v in attr.items():
330            try:
331                setattr(self, k, v)
332            except AttributeError, e:
333                # FIXME: remove this test exception handling code
334                pass
335           
336        # Update attributes from a config file
337        if cfg:
338            self.parseConfig(cfg, section=cfgFileSection, prefix=cfgPrefix)
339
340        # Update attributes from keywords passed
341        for k,v in kw.items():
342            setattr(self, k, v)
343
344        # Get the distinguished name from the user certificate
345        if self._userX509Cert:
346            self._dn = self._userX509Cert.dn.serialise()
347       
348       
349        # Credentials are stored as a dictionary one element per attribute
350        # certicate held and indexed by certificate issuer name
351        self._credentials = {}
352
353
354        # Make a connection to the Credentials Repository
355        if self._credentialRepository is None:
356            log.info('Applying default CredentialRepository %r for user '
357                     '"%s"' % (NullCredentialRepository, self.userId))
358            self._credentialRepository = NullCredentialRepository()
359        else:
360            log.info('Checking CredentialRepository for credentials for user '
361                     '"%s"' % self.userId)
362           
363            if not isinstance(self._credentialRepository,CredentialRepository):
364                raise CredWalletError("Input Credential Repository instance "
365                                      "must be of a class derived from "
366                                      "\"CredentialRepository\"")
367   
368       
369            # Check for valid attribute certificates for the user
370            try:
371                self._credentialRepository.auditCredentials(self.userId)
372                userCred = self._credentialRepository.getCredentials(self.userId)
373   
374            except Exception, e:
375                log.error("Error updating wallet with credentials from "
376                          "repository: %s" % e)
377                raise
378   
379   
380            # Update wallet with attribute certificates stored in the
381            # repository.  Store ID and certificate instantiated as an AttCert
382            # type
383            try:
384                for cred in userCred: 
385                    attCert = AttCertParse(cred.attCert)
386                    issuerName = attCert['issuerName']
387                   
388                    self._credentials[issuerName] = {'id':cred.id, 
389                                                     'attCert':attCert}   
390            except Exception, e:
391                try:
392                    raise CredWalletError("Error parsing Attribute Certificate"
393                                          " ID '%s' retrieved from the " 
394                                          "Credentials Repository: %s" % 
395                                          (cred.id, e))           
396                except:
397                    raise CredWalletError("Error parsing Attribute "
398                                          "Certificate retrieved from the "
399                                          "Credentials Repository: %s:" % e)
400           
401            # Filter out expired or otherwise invalid certificates
402            self.audit()
403
404    def parseConfig(self, cfg, prefix='', section='DEFAULT'):
405        '''Extract parameters from _cfg config object'''
406       
407        if isinstance(cfg, basestring):
408            cfgFilePath = os.path.expandvars(cfg)
409            self._cfg = None
410        else:
411            cfgFilePath = None
412            self._cfg = cfg
413           
414        # Configuration file properties are held together in a dictionary
415        readAndValidate = INIPropertyFileWithValidation()
416        prop = readAndValidate(cfgFilePath,
417                               cfg=self._cfg,
418                               validKeys=CredWallet._defParam,
419                               prefix=prefix,
420                               sections=(section,))
421       
422        # Keep a copy of config for use by WS-Security SignatureHandler parser
423        if self._cfg is None:
424            self._cfg = readAndValidate.cfg
425       
426        # Copy prop dict into object attributes - __slots__ definition and
427        # property methods will ensure only the correct attributes are set
428        for key, val in prop.items():
429            setattr(self, key, val)
430
431
432    def _getAttCertRefreshElapse(self):
433        return self._attCertRefreshElapse
434   
435    def _setAttCertRefreshElapse(self, val):
436        if isinstance(val, (float, int)):
437            self._attCertRefreshElapse = val
438           
439        elif isinstance(val, basestring):
440            self._attCertRefreshElapse = float(val)
441        else:
442            raise AttributeError("Expecting int, float or string type input "
443                                 "for attCertRefreshElapse")
444           
445    attCertRefreshElapse = property(fget=_getAttCertRefreshElapse, 
446                                    fset=_setAttCertRefreshElapse,
447                                    doc="If an existing one has AC less than "
448                                        "attCertRefreshElapse time in seconds "
449                                        "left before expiry then replace it")
450   
451    def _setX509Cert(self, cert):
452        """filter and convert input cert to signing verifying cert set
453        property methods.  For signingCert, set to None if it is not to be
454        included in the SOAP header.  For verifyingCert, set to None if this
455        cert can be expected to be retrieved from the SOAP header of the
456        message to be verified
457       
458        @type: ndg.security.common.X509.X509Cert / M2Crypto.X509.X509 /
459        string or None
460        @param cert: X.509 certificate. 
461       
462        @rtype ndg.security.common.X509.X509Cert
463        @return X.509 certificate object"""
464       
465        if cert is None or isinstance(cert, X509Cert):
466            # ndg.security.common.X509.X509Cert type / None
467            return cert
468           
469        elif isinstance(cert, X509.X509):
470            # M2Crypto.X509.X509 type
471            return X509Cert(m2CryptoX509=cert)
472           
473        elif isinstance(cert, basestring):
474            return X509CertParse(cert)
475       
476        else:
477            raise AttributeError("X.509 Cert. must be type: "
478                                 "ndg.security.common.X509.X509Cert, "
479                                 "M2Crypto.X509.X509 or a base64 encoded "
480                                 "string")
481
482    def _setUserX509Cert(self, userX509Cert):
483        "Set property method for X.509 user cert."
484        self._userX509Cert = self._setX509Cert(userX509Cert)
485       
486
487    def _getUserX509Cert(self):
488        """Get user cert X509Cert instance"""
489        return self._userX509Cert
490
491    userX509Cert = property(fget=_getUserX509Cert,
492                            fset=_setUserX509Cert,
493                            doc="X.509 user certificate instance")
494
495
496    def _setIssuingX509Cert(self, issuingX509Cert):
497        "Set property method for X.509 user cert."
498        self._issuingX509Cert = self._setX509Cert(issuingX509Cert)
499       
500
501    def _getIssuingX509Cert(self):
502        """Get user cert X509Cert instance"""
503        return self._issuingX509Cert
504
505    issuingX509Cert = property(fget=_getIssuingX509Cert,
506                               fset=_setIssuingX509Cert,
507                               doc="X.509 user certificate instance")
508     
509
510    def _getUserPriKey(self):
511        "Get method for user private key"
512        return self._userPriKey
513   
514    def _setUserPriKey(self, userPriKey):
515        """Set method for user private key
516       
517        Nb. if input is a string, userPriKeyPwd will need to be set if
518        the key is password protected.
519       
520        @type userPriKey: M2Crypto.RSA.RSA / string
521        @param userPriKey: private key used to sign message"""
522       
523        if userPriKey is None:
524            log.warning("Setting user private key to None")
525            self._userPriKey = None
526        elif isinstance(userPriKey, basestring):
527            self._userPriKey = RSA.load_key_string(userPriKey,
528                                             callback=lambda *ar, **kw: None)
529        elif isinstance(userPriKey, RSA.RSA):
530            self._userPriKey = userPriKey         
531        else:
532            raise AttributeError("user private key must be a valid "
533                                 "M2Crypto.RSA.RSA type or a string")
534               
535    userPriKey = property(fget=_getUserPriKey,
536                          fset=_setUserPriKey,
537                          doc="User private key if set, used to sign outbound "
538                              "messages to Attribute authority")
539
540   
541    def _getCredentials(self):
542        """Get Property method.  Credentials are read-only
543       
544        @rtype: dict
545        @return: cached ACs indesed by issuing organisation name"""
546        return self._credentials
547
548    # Publish attribute
549    credentials = property(fget=_getCredentials,
550                           doc="List of Attribute Certificates")   
551
552
553    def _getCACertFilePathList(self):
554        """Get CA cert or certs used to validate AC signatures and signatures
555        of peer SOAP messages.
556       
557        @rtype caCertFilePathList: basestring, list or tuple
558        @return caCertFilePathList: file path(s) to CA certificates."""
559        return self._caCertFilePathList
560   
561    def _setCACertFilePathList(self, caCertFilePathList):
562        """Set CA cert or certs to validate AC signatures, signatures
563        of Attribute Authority SOAP responses and SSL connections where
564        AA SOAP service is run over SSL.
565       
566        @type caCertFilePathList: basestring, list, tuple or None
567        @param caCertFilePathList: file path(s) to CA certificates.  If None
568        then the input is quietly ignored."""
569       
570        if isinstance(caCertFilePathList, basestring):
571           self._caCertFilePathList = [caCertFilePathList]
572           
573        elif isinstance(caCertFilePathList, list):
574           self._caCertFilePathList = caCertFilePathList
575           
576        elif isinstance(caCertFilePathList, tuple):
577           self._caCertFilePathList = list(caCertFilePathList)
578
579        elif caCertFilePathList is not None:
580            raise CredWalletError("Input CA Certificate file path is not a "
581                                  "valid string")     
582       
583    caCertFilePathList = property(fget=_getCACertFilePathList,
584                                  fset=_setCACertFilePathList,
585                                  doc="CA Certificates - used for "
586                                      "verification of AC and SOAP message "
587                                      "signatures")
588
589    def _getSSLCACertFilePathList(self):
590        """Get CA cert or certs used to validate AC signatures and signatures
591        of peer SOAP messages.
592       
593        @rtype sslCACertFilePathList: basestring, list or tuple
594        @return sslCACertFilePathList: file path(s) to CA certificates."""
595        return self._sslCACertFilePathList
596   
597    def _setSSLCACertFilePathList(self, sslCACertFilePathList):
598        """Set CA cert or certs to validate AC signatures, signatures
599        of Attribute Authority SOAP responses and SSL connections where
600        AA SOAP service is run over SSL.
601       
602        @type sslCACertFilePathList: basestring, list, tuple or None
603        @param sslCACertFilePathList: file path(s) to CA certificates.  If None
604        then the input is quietly ignored."""
605       
606        if isinstance(sslCACertFilePathList, basestring):
607           self._sslCACertFilePathList = [sslCACertFilePathList]
608           
609        elif isinstance(sslCACertFilePathList, list):
610           self._sslCACertFilePathList = sslCACertFilePathList
611           
612        elif isinstance(sslCACertFilePathList, tuple):
613           self._sslCACertFilePathList = list(sslCACertFilePathList)
614
615        elif sslCACertFilePathList is not None:
616            raise CredWalletError("Input CA Certificate file path is not a "
617                                  "valid string")     
618       
619    sslCACertFilePathList = property(fget=_getSSLCACertFilePathList,
620                                  fset=_setSSLCACertFilePathList,
621                                  doc="CA Certificates - used for "
622                                      "verification of peer certs in SSL "
623                                      "connections")
624
625    def _createAttributeAuthorityClnt(self, attributeAuthorityURI):
626        """Set up a client to an Attribute Authority with the given URI
627       
628        @type attributeAuthorityURI: string
629        @param attributeAuthorityURI: Attribute Authority Web Service URI.
630
631        @rtype: ndg.security.common.AttAuthorityClient
632        @return: new Attribute Authority client instance"""
633
634        log.debug('CredWallet._createAttributeAuthorityClnt for service: "%s"'%
635                  attributeAuthorityURI)
636
637        attributeAuthorityClnt = AttAuthorityClient(uri=attributeAuthorityURI,
638                                sslCACertFilePathList=self._sslCACertFilePathList,
639                                cfg=self.wssCfgFilePath or self._cfg,
640                                cfgFileSection=self.wssCfgSection,
641                                **(self.wssCfgKw or {}))
642       
643        # If a user certificate is set, use this to sign messages instead of
644        # the default settings in the WS-Security config. 
645        if attributeAuthorityClnt.signatureHandler is not None and \
646           self.userPriKey is not None:
647            if self.issuingX509Cert is not None:
648                # Pass a chain of certificates -
649                # Initialise WS-Security signature handling to pass
650                # BinarySecurityToken containing user cert and cert for user
651                # cert issuer
652                attributeAuthorityClnt.signatureHandler.reqBinSecTokValType = \
653                            SignatureHandler.binSecTokValType["X509PKIPathv1"]
654                attributeAuthorityClnt.signatureHandler.signingCertChain = \
655                                    (self._issuingX509Cert, self._userX509Cert)               
656
657                attributeAuthorityClnt.signatureHandler.signingPriKey = \
658                                                            self.userPriKey
659            elif self.userX509Cert is not None:
660                # Pass user cert only - no need to pass a cert chain. 
661                # This type of token is more likely to be supported by the
662                # various WS-Security toolkits
663                attributeAuthorityClnt.signatureHandler.reqBinSecTokValType = \
664                                    SignatureHandler.binSecTokValType["X509v3"]
665                attributeAuthorityClnt.signatureHandler.signingCert = \
666                                                            self._userX509Cert
667
668                attributeAuthorityClnt.signatureHandler.signingPriKey = \
669                                                            self.userPriKey
670
671        return attributeAuthorityClnt
672
673    def createAttributeAuthorityClnt(self, attributeAuthorityURI=None):
674        '''Convenience method to create an Attribute Authority Client based
675        on WS-Security config and user X.509 certificate settings if present
676       
677        Nb. a client is created implicitly when the attributeAuthorityURI
678        attribute property is set'''
679       
680        if attributeAuthorityURI is None:
681            attributeAuthorityURI = self.attributeAuthorityURI
682           
683        self._attributeAuthorityClnt = self._createAttributeAuthorityClnt(
684                                                        attributeAuthorityURI)
685       
686    def _getAttributeAuthorityURI(self):
687        """Get property method for Attribute Authority Web Service URI to
688        connect to."""
689        return self._attributeAuthorityURI
690           
691    def _setAttributeAuthorityURI(self, attributeAuthorityURI):
692        """Set property method for Attribute Authority Web Service URI to
693        connect to.  This method ALSO SETS UP THE CLIENT INTERFACE
694       
695        @type attributeAuthorityURI: string
696        @param attributeAuthorityURI: Attribute Authority Web Service URI.  Set
697        to None to initialise.  Set to a URI to instantiate a new Attribute
698        Authority client"""
699        if attributeAuthorityURI is None:
700            self._attributeAuthorityURI = self._attributeAuthorityClnt = None
701            return
702        else:
703            self._attributeAuthorityURI = attributeAuthorityURI
704            self._attributeAuthorityClnt = self._createAttributeAuthorityClnt(
705                                                        attributeAuthorityURI)
706           
707    attributeAuthorityURI = property(fget=_getAttributeAuthorityURI,
708                                     fset=_setAttributeAuthorityURI,
709                                     doc="Attribute Authority address - "
710                                         "setting also sets up "
711                                         "AttAuthorityClient instance!")
712
713
714    def _getAttributeAuthorityClnt(self):
715        """Get property method for Attribute Authority Web Service client
716        instance.  Use attributeAuthorityURI property to set up
717        attributeAuthorityClnt
718       
719        @type attributeAuthorityClnt: AttAuthorityClient
720        @param attributeAuthorityClnt: Attribute Authority Web Service client
721        instance"""
722        return self._attributeAuthorityClnt
723           
724    attributeAuthorityClnt = property(fget=_getAttributeAuthorityClnt, 
725                      doc="Attribute Authority web service client instance")
726
727
728    def _getAttributeAuthority(self):
729        """Get property method for Attribute Authority Web Service client
730        instance.  Use attributeAuthorityURI propert to set up
731        attributeAuthorityClnt
732       
733        @type attributeAuthorityClnt: AttAuthorityClient
734        @param attributeAuthorityClnt: Attribute Authority Web Service client
735        instance"""
736        return self._attributeAuthorityClnt
737
738
739    def _setAttributeAuthority(self, attributeAuthority):
740        """Set property method for Attribute Authority Web Service instance to
741        connect to.
742       
743        @type attributeAuthority: ndg.security.server.AttAuthority.AttAuthority
744        @param attributeAuthority: Attribute Authority Web Service."""
745        if attributeAuthority is not None and \
746           not isinstance(attributeAuthority, AttAuthority):
747            raise TypeError("Expecting %r type for attributeAuthority "
748                            "attribute" % AttAuthority)
749           
750        self._attributeAuthority = self.attributeAuthority
751           
752    attributeAuthority = property(fget=_getAttributeAuthority,
753                                  fset=_setAttributeAuthority, 
754                                  doc="Attribute Authority instance")
755
756
757    def isValid(self, **x509CertKeys):
758        """Check wallet's user cert.  If expired return False
759       
760        @type **x509CertKeys: dict
761        @param **x509CertKeys: keywords applying to
762        ndg.security.common.X509.X509Cert.isValidTime method"""
763        return self._userX509Cert.isValidTime(**x509CertKeys)
764
765
766    def addCredential(self, attCert, bUpdateCredentialRepository=True):
767        """Add a new attribute certificate to the list of credentials held.
768
769        @type attCert:
770        @param attCert: new attribute Certificate to be added
771        @type bUpdateCredentialRepository: bool
772        @param bUpdateCredentialRepository: if set to True, and a repository
773        exists it will be updated with the new credentials also
774       
775        @rtype: bool
776        @return: True if certificate was added otherwise False.  - If an
777        existing certificate from the same issuer has a later expiry it will
778        take precence and the new input certificate is ignored."""
779
780        # Check input
781        if not isinstance(attCert, AttCert):
782            raise CredWalletError("Attribute Certificate must be an AttCert "
783                                  "type object")
784
785        # Check certificate validity
786        try:
787            attCert.isValid(raiseExcep=True)
788           
789        except AttCertError, e:
790            raise CredWalletError("Adding Credential: %s" % e)
791       
792
793        # Check to see if there is an existing Attribute Certificate held
794        # that was issued by the same host.  If so, compare the expiry time.
795        # The one with the latest expiry will be retained and the other
796        # ingored
797        bUpdateCred = True
798        issuerName = attCert['issuerName']
799       
800        if issuerName in self._credentials:
801            # There is an existing certificate held with the same issuing
802            # host name as the new certificate
803            attCertOld = self._credentials[issuerName]['attCert']
804
805            # Get expiry times in datetime format to allow comparison
806            dtAttCertOldNotAfter = attCertOld.getValidityNotAfter(\
807                                                            asDatetime=True)
808            dtAttCertNotAfter = attCert.getValidityNotAfter(asDatetime=True)
809
810            # If the new certificate has an earlier expiry time then ignore it
811            bUpdateCred = dtAttCertNotAfter > dtAttCertOldNotAfter
812
813               
814        if bUpdateCred:
815            # Update: Nb. -1 ID value flags item as new.  Items read in
816            # from the CredentialRepository during creation of the wallet will
817            # have +ve IDs previously allocated by the database
818            self._credentials[issuerName] = {'id': -1, 'attCert': attCert}
819
820            # Update the Credentials Repository - the permanent store of user
821            # authorisation credentials.  This allows credentials for previous
822            # sessions to be re-instated
823            if self._credentialRepository and bUpdateCredentialRepository:
824                self.updateCredentialRepository()
825
826        # Flag to caller to indicate whether the input certificate was added
827        # to the credentials or an exsiting certificate from the same issuer
828        # took precedence
829        return bUpdateCred
830           
831
832    def audit(self):
833        """Check the credentials held in the wallet removing any that have
834        expired or are otherwise invalid."""
835
836        log.debug("CredWallet.audit ...")
837       
838        # Nb. No signature check is carried out.  To do a check, access is
839        # needed to the cert of the CA that issued the Attribute Authority's
840        # cert
841        #
842        # P J Kershaw 12/09/05
843        for key, val in self._credentials.items():
844            if not val['attCert'].isValid(chkSig=False):
845                del self._credentials[key]
846
847
848    def updateCredentialRepository(self, auditCred=True):
849        """Copy over non-persistent credentials held by wallet into the
850        perminent repository.
851       
852        @type auditCred: bool
853        @param auditCred: filter existing credentials in the repository
854        removing invalid ones"""
855
856        log.debug("CredWallet.updateCredentialRepository ...")
857       
858        if not self._credentialRepository:
859            raise CredWalletError("No Credential Repository has been created "
860                                  "for this wallet")
861                           
862        # Filter out invalid certs unless auditCred flag is explicitly set to
863        # false
864        if auditCred: self.audit()
865
866        # Update the database - only add new entries i.e. with an ID of -1
867        attCertList = [i['attCert'] for i in self._credentials.values() \
868                       if i['id'] == -1]
869
870        self._credentialRepository.addCredentials(self.userId, attCertList)
871
872
873    def _getAttCert(self, attributeAuthorityClnt=None, extAttCert=None):       
874        """Wrapper to Attribute Authority attribute certificate request.  See
875        getAttCert for the classes' public interface.
876
877        To call the Attribute Authority as a Web Service, specify a URI
878        otherwise set the properties file path.
879       
880        If successful, a new attribute certificate is issued to the user
881        and added into the wallet
882
883        @type attributeAuthorityClnt: ndg.security.common.AttAuthorityClient
884        @param attributeAuthorityClnt: client object to Attribute Authority to
885        make a request to.  If omitted, it is set to
886        self._attributeAuthorityClnt.  This attribute may itself be None.   
887        In this case, a local AA will be expected to be set.
888       
889        @type extAttCert: ndg.security.common.AttCert.AttCert
890        @param extAttCert: an existing Attribute Certificate which can
891        be used to making a mapping should the user not be registered with the
892        Attribute Authority"""
893     
894        log.debug("CredWallet._getAttCert ...")
895       
896        if attributeAuthorityClnt is None:
897            attributeAuthorityClnt = self._attributeAuthorityClnt
898       
899        # If a user cert. is present, ignore the user ID setting.  The
900        # Attribute Authority will set the userId field of the
901        # Attribute Certificate based on the DN of the user certificate
902        if self.userX509Cert:
903            userId = None
904        else:
905            userId = self.userId
906           
907        if attributeAuthorityClnt is not None:
908            try:
909                log.debug("Calling attribute authority using supplied client")
910                attCert = attributeAuthorityClnt.getAttCert(userId=userId,
911                                                        userAttCert=extAttCert)
912                               
913                log.info('Granted Attribute Certificate from issuer DN = "%s"'
914                         ' at "%s"' % (attCert.issuerDN, 
915                                       attributeAuthorityClnt.uri))
916               
917            except AttributeRequestDenied, e:
918                raise CredWalletAttributeRequestDenied, str(e)
919                           
920        elif self.attributeAuthority is not None:
921
922            # Call local based Attribute Authority with settings from the
923            # configuration file attributeAuthority
924            try:
925                # Request a new attribute certificate from the Attribute
926                # Authority
927                log.debug("Calling Attribute Authority using info from "
928                          "properties file: %s" % self.attributeAuthority)
929                   
930                attCert = self.attributeAuthority.getAttCert(userId=userId,
931                                                        userAttCert=extAttCert)
932               
933                log.info('Granted Attribute Certificate from issuer DN = "%s"'%
934                         attCert.issuerDN)
935               
936            except AttAuthorityAccessDenied, e:
937                raise CredWalletAttributeRequestDenied, str(e)
938                       
939            except Exception, e:
940                raise CredWalletError("Requesting attribute certificate: %s"%e)
941
942        else:
943            raise CredWalletError("Error requesting attribute: certificate a "
944                                  "URI or Attribute Authority configuration "
945                                  "file must be specified")
946       
947
948        # Update attribute Certificate instance with CA's certificate ready
949        # for signature check in addCredential()
950        if self._caCertFilePathList is None:
951            raise CredWalletError("No CA certificate has been set")
952       
953        attCert.certFilePathList = self._caCertFilePathList
954
955       
956        # Add credential into wallet
957        #
958        # Nb. if the certificates signature is invalid, it will be rejected
959        log.debug("Adding credentials into wallet...")
960        self.addCredential(attCert)
961       
962        return attCert
963
964
965    def getAATrustedHostInfo(self, 
966                             userRole=None,
967                             attributeAuthority=None,
968                             attributeAuthorityURI=None):
969        """Wrapper to Attribute Authority getTrustedHostInfo
970       
971        getAATrustedHostInfo([userRole=r, ][attributeAuthority=f|attributeAuthorityURI=u])
972                   
973        @type userRole: string
974        @param userRole: get hosts which have a mapping to this role
975       
976        @type attributeAuthorityURI: string
977        @param attributeAuthorityURI: to call as a web service, specify the URI for the
978        Attribute Authority.
979       
980        @type attributeAuthority: string
981        @param attributeAuthority: Altenrative to attributeAuthorityURI - to run on the local
982        machine, specify the local Attribute Authority configuration file.
983        """
984       
985        log.debug('CredWallet.getAATrustedHostInfo for role "%s" and service: '
986                  '"%s"' % (userRole, attributeAuthorityURI or attributeAuthority))
987        if attributeAuthorityURI:
988            self._setAttributeAuthorityURI(attributeAuthorityURI)
989        elif attributeAuthority:
990            self._setAAPropFilePath
991
992           
993        if self._attributeAuthorityClnt is not None:
994            # Call Attribute Authority WS
995            try:
996                return self._attributeAuthorityClnt.getTrustedHostInfo(role=userRole)               
997                           
998            except Exception, e:
999                log.error("Requesting trusted host information: %s" % str(e))
1000                raise 
1001
1002        elif self.attributeAuthority is not None:
1003
1004            # Call local based Attribute Authority with settings from the
1005            # configuration file attributeAuthority
1006            try:
1007                # Request a new attribute certificate from the Attribute
1008                # Authority
1009                return self.attributeAuthority.getTrustedHostInfo(role=userRole)
1010               
1011            except Exception, e:
1012                log.error("Requesting trusted host info: %s" % e)
1013                raise
1014
1015        else:
1016            raise CredWalletError("Error requesting trusted hosts info: " 
1017                                  "a URI or Attribute Authority " 
1018                                  "configuration file must be specified")
1019
1020
1021    def getAttCert(self,
1022                   reqRole=None,
1023                   attributeAuthority=None,
1024                   attributeAuthorityURI=None,
1025                   mapFromTrustedHosts=None,
1026                   rtnExtAttCertList=None,
1027                   extAttCertList=None,
1028                   extTrustedHostList=None,
1029                   refreshAttCert=False,
1030                   attCertRefreshElapse=None):
1031       
1032        """Get an Attribute Certificate from an Attribute Authority.  If this
1033        fails try to make a mapped Attribute Certificate by using a certificate from another
1034        host which has a trust relationship to the Attribute Authority in
1035        question.
1036
1037        getAttCert([reqRole=r, ][attributeAuthority=a|attributeAuthorityURI=u,]
1038                   [mapFromTrustedHosts=m, ]
1039                   [rtnExtAttCertList=e, ][extAttCertList=el, ]
1040                   [extTrustedHostList=et, ][refreshAttCert=ra])
1041                 
1042        The procedure is:
1043
1044        1) Try attribute request using user certificate
1045        2) If the Attribute Authority (AA) doesn't recognise the certificate,
1046        find out any other hosts which have a trust relationship to the AA.
1047        3) Look for Attribute Certificates held in the wallet corresponding
1048        to these hosts.
1049        4) If no Attribute Certificates are available, call the relevant
1050        hosts' AAs to get certificates
1051        5) Finally, use these new certificates to try to obtain a mapped
1052        certificate from the original AA
1053        6) If this fails access is denied     
1054                   
1055        @type reqRole: string
1056        @param reqRole: the required role to get access for
1057       
1058        @type attributeAuthorityURI: string
1059        @param attributeAuthorityURI: to call as a web service, specify the URI for the
1060        Attribute Authority.
1061       
1062        @type attributeAuthority: string
1063        @param attributeAuthority: Altenrative to attributeAuthorityURI - to run on the local
1064        machine, specify the local Attribute Authority configuration file.
1065                               
1066        @type mapFromTrustedHosts: bool / None     
1067        @param mapFromTrustedHosts: if request fails via the user's cert
1068        ID, then it is possible to get a mapped certificate by using
1069        certificates from other AA's.  Set this flag to True, to allow this
1070        second stage of generating a mapped certificate from the certificate
1071        stored in the wallet credentials.
1072
1073        If set to False, it is possible to return the list of certificates
1074        available for mapping and then choose which one or ones to use for
1075        mapping by re-calling getAttCert with extAttCertList set to these
1076        certificates.
1077       
1078        Defaults to None in which case self._mapFromTrustedHosts is not
1079        altered
1080
1081        The list is returned via CredWalletAttributeRequestDenied exception
1082        If no value is set, the default value held in
1083        self._mapFromTrustedHosts is used
1084
1085        @type rtnExtAttCertList: bool / None
1086        @param rtnExtAttCertList: If request fails, make a list of
1087        candidate certificates from other Attribute Authorities which the user
1088        could use to retry and get a mapped certificate.
1089                               
1090        If mapFromTrustedHosts is set True this flags value is overriden and
1091        effectively set to True.
1092
1093        If no value is set, the default value held in self._rtnExtAttCertList
1094        is used.
1095                               
1096        The list is returned via a CredWalletAttributeRequestDenied exception
1097        object.
1098                               
1099        @type extAttCertList: list
1100        @param extAttCertList: Attribute Certificate or list of certificates
1101        from other Attribute Authorities.  These can be used to get a mapped
1102        certificate if access fails based on the user's certificate
1103        credentials.  They are tried out in turn until access is granted so
1104        the order of the list decides the order in which they will be tried
1105
1106        @type extTrustedHostList:
1107        @param extTrustedHostList: same as extAttCertList keyword, but
1108        instead of providing Attribute Certificates, give a list of Attribute
1109        Authority hosts.  These will be matched up to Attribute Certificates
1110        held in the wallet.  Matching certificates will then be used to try to
1111        get a mapped Attribute Certificate.
1112       
1113        @type refreshAttCert: bool
1114        @param refreshAttCert: if set to True, the attribute request
1115        will go ahead even if the wallet already contains an Attribute
1116        Certificate from the target Attribute Authority.  The existing AC in
1117        the wallet will be replaced by the new one obtained from this call.
1118                               
1119        If set to False, this method will check to see if an AC issued by the
1120        target AA already exists in the wallet.  If so, it will return this AC
1121        to the caller without proceeding to make a call to the AA.
1122       
1123        @type attCertRefreshElapse: float / int
1124        @param attCertRefreshElapse: determine whether to replace an
1125        existing AC in the cache with a fresh one.  If the existing one has
1126        less than attCertRefreshElapse time in seconds left before expiry then
1127        replace it.
1128       
1129        @rtype: ndg.security.common.AttCert.AttCert
1130        @return: Attribute Certificate retrieved from Attribute Authority"""
1131       
1132        log.debug("CredWallet.getAttCert ...")
1133        if attributeAuthorityURI:
1134            self.attributeAuthorityURI = attributeAuthorityURI
1135        elif attributeAuthority:
1136            self._setAAPropFilePath
1137           
1138        if not refreshAttCert and self._credentials:
1139            # Refresh flag is not set so it's OK to check for any existing
1140            # Attribute Certificate in the wallet whose issuerName match the
1141            # target AA's name
1142           
1143            # Find out the site ID for the target AA by calling AA's host
1144            # info WS method
1145            log.debug("CredWallet.getAttCert - check AA site ID ...")
1146           
1147            try:
1148                hostInfo = self._attributeAuthorityClnt.getHostInfo()
1149                aaName = hostInfo.keys()[0]
1150            except Exception, e:
1151                raise CredWalletError("Getting host info: %s" % e)
1152           
1153            # Look in the wallet for an AC with the same issuer name
1154            if aaName in self._credentials:
1155                # Existing Attribute Certificate found in wallet - Check that
1156                # it will be valid for at least the next 2 hours
1157                if attCertRefreshElapse is not None:
1158                    self.attCertRefreshElapse = attCertRefreshElapse
1159                   
1160                dtNow = datetime.utcnow() + \
1161                        timedelta(seconds=self.attCertRefreshElapse)
1162               
1163                attCert = self._credentials[aaName]['attCert']
1164                if attCert.isValidTime(dtNow=dtNow):
1165                    log.info("Retrieved an existing %s AC from the wallet" % 
1166                             aaName)
1167                    return attCert
1168           
1169           
1170        # Check for settings from input, if not set use previous settings
1171        # made
1172        if mapFromTrustedHosts is not None:
1173            self._mapFromTrustedHosts = mapFromTrustedHosts
1174
1175        if rtnExtAttCertList is not None:
1176            self._rtnExtAttCertList = rtnExtAttCertList
1177
1178
1179        # Check for list of external trusted hosts (other trusted NDG data
1180        # centres)
1181        if extTrustedHostList:
1182            log.info("Checking for ACs in wallet matching list of trusted "
1183                     "hosts set: %s" % extTrustedHostList)
1184           
1185            if not self._mapFromTrustedHosts:
1186                raise CredWalletError("A list of trusted hosts has been " 
1187                                      "input but mapping from trusted hosts "
1188                                      "is set to disallowed")
1189           
1190            if isinstance(extTrustedHostList, basestring):
1191                extTrustedHostList = [extTrustedHostList]
1192
1193            # Nb. Any extAttCertList is overriden by extTrustedHostList being
1194            # set
1195            extAttCertList = [self._credentials[hostName]['attCert'] \
1196                              for hostName in extTrustedHostList \
1197                              if hostName in self._credentials]
1198
1199        # Set an empty list to trigger an AttributeError by initialising it to
1200        # None
1201        if extAttCertList == []:
1202            extAttCertList = None
1203           
1204        # Repeat authorisation attempts until succeed or means are exhausted
1205        while True:
1206           
1207            # Check for candidate certificates for mapping
1208            try:
1209                # If list is set get the next cert
1210                extAttCert = extAttCertList.pop()
1211
1212            except AttributeError:
1213                log.debug("No external Attribute Certificates - trying "
1214                          "request without mapping...")
1215                # No List set - attempt request without
1216                # using mapping from trusted hosts
1217                extAttCert = None
1218                           
1219            except IndexError:
1220               
1221                # List has been emptied without attribute request succeeding -
1222                # give up
1223                errMsg = "Attempting to obtained a mapped certificate: " + \
1224                         "no external attribute certificates are available"
1225                   
1226                # Add the exception form the last call to the Attribute
1227                # Authority if an error exists
1228                try:
1229                    errMsg += ": %s" % attributeRequestDenied
1230                except NameError:
1231                    pass
1232
1233                raise CredWalletAttributeRequestDenied, errMsg
1234                                                   
1235               
1236            # Request Attribute Certificate from Attribute Authority
1237            try:
1238                attCert = self._getAttCert(extAttCert=extAttCert)               
1239                # Access granted
1240                return attCert
1241           
1242            except CredWalletAttributeRequestDenied, attributeRequestDenied:
1243                if not mapFromTrustedHosts and not rtnExtAttCertList:
1244                    # Creating a mapped certificate is not allowed - raise
1245                    # authorisation denied exception saved from earlier
1246                    raise attributeRequestDenied
1247
1248                if isinstance(extAttCertList, list):
1249                    # An list of attribute certificates from trusted hosts
1250                    # is present continue cycling through this until one of
1251                    # them is accepted and a mapped certificate can be derived
1252                    log.debug("AC request denied - but external ACs available "
1253                              "to try mapped AC request ...")
1254                    continue
1255                             
1256                #  Use the input required role and the AA's trusted host list
1257                # to identify attribute certificates from other hosts which
1258                # could be used to make a mapped certificate
1259                log.debug("Getting a list of trusted hosts for mapped AC "
1260                          "request ...")
1261                try:
1262                    trustedHostInfo = self.getAATrustedHostInfo(reqRole,
1263                                            attributeAuthority=attributeAuthority)
1264                except NoMatchingRoleInTrustedHosts, e:
1265                    raise CredWalletAttributeRequestDenied(
1266                        'Can\'t get a mapped Attribute Certificate for '
1267                        'the "%s" role' % reqRole)
1268               
1269                except Exception, e:
1270                    raise CredWalletError, "Getting trusted hosts: %s" % e
1271
1272                if not trustedHostInfo:
1273                    raise CredWalletAttributeRequestDenied(
1274                        "Attribute Authority has no trusted hosts with "
1275                        "which to make a mapping")
1276
1277               
1278                # Initialise external certificate list here - if none are
1279                # found IndexError will be raised on the next iteration and
1280                # an access denied error will be raised
1281                extAttCertList = []
1282
1283                # Look for Attribute Certificates with matching issuer host
1284                # names
1285                log.debug("Checking wallet for ACs issued by one of the "
1286                          "trusted hosts...")
1287                for hostName in self._credentials:
1288
1289                    # Nb. Candidate certificates for mappings must have
1290                    # original provenance and contain at least one of the
1291                    # required roles
1292                    attCert = self._credentials[hostName]['attCert']
1293                   
1294                    if hostName in trustedHostInfo and attCert.isOriginal():                       
1295                        for role in attCert.roles:
1296                            if role in trustedHostInfo[hostName]['role']:                               
1297                                extAttCertList.append(attCert)
1298
1299
1300                if not extAttCertList:
1301                    log.debug("No wallet ACs matched any of the trusted " + \
1302                              "hosts.  - Try request for an AC from a " + \
1303                              "trusted host ...")
1304                   
1305                    # No certificates in the wallet matched the trusted host
1306                    # and required roles
1307                    #
1308                    # Try each host in turn in order to get a certificate with
1309                    # the required credentials in order to do a mapping
1310                    for host, info in trustedHostInfo.items():
1311                        try:
1312                            # Try request to trusted host
1313                            trustedAAClnt = self._createAttributeAuthorityClnt(info['attributeAuthorityURI'])
1314                            extAttCert=self._getAttCert(attributeAuthorityClnt=trustedAAClnt)
1315
1316                            # Check the certificate contains at least one of
1317                            # the required roles
1318                            if [True for r in extAttCert.roles \
1319                                if r in info['role']]:
1320                               extAttCertList.append(extAttCert)
1321
1322                               # For efficiency, stop once obtained a valid
1323                               # cert - but may want complete list for user to
1324                               # choose from
1325                               #break
1326                               
1327                        except Exception, e:
1328                            # ignore any errors and continue
1329                            log.warning('AC request to trusted host "%s"' % \
1330                                        info['attributeAuthorityURI'] + ' resulted in: %s'%e)
1331                           
1332                   
1333                if not extAttCertList:                       
1334                    raise CredWalletAttributeRequestDenied, \
1335                        "No certificates are available with which to " + \
1336                        "make a mapping to the Attribute Authority"
1337
1338
1339                if not mapFromTrustedHosts:
1340                   
1341                    # Exit here returning the list of candidate certificates
1342                    # that could be used to make a mapped certificate
1343                    msg = "User is not registered with Attribute " + \
1344                          "Authority - retry using one of the returned " + \
1345                          "Attribute Certificates obtained from other " + \
1346                          "trusted hosts"
1347                         
1348                    raise CredWalletAttributeRequestDenied(msg,
1349                                            extAttCertList=extAttCertList,
1350                                            trustedHostInfo=trustedHostInfo)           
1351
1352
1353class CredentialRepositoryError(_CredWalletException):   
1354    """Exception handling for NDG Credential Repository class."""
1355
1356
1357class CredentialRepository:
1358    """CredWallet's abstract interface class to a Credential Repository.  The
1359    Credential Repository is abstract store of user currently valid user
1360    credentials.  It enables retrieval of attribute certificates from a user's
1361    previous session(s)"""
1362       
1363    def __init__(self, propFilePath=None, dbPPhrase=None, **prop):
1364        """Initialise Credential Repository abstract base class.  Derive from
1365        this class to define Credentail Repository interface Credential
1366        Wallet
1367
1368        If the connection string or properties file is set a connection
1369        will be made
1370
1371        @type dbPPhrase: string
1372        @param dbPPhrase: pass-phrase to database if applicable
1373       
1374        @type propFilePath: string
1375        @param propFilePath: file path to a properties file.  This could
1376        contain configuration parameters for the repository e.g.  database
1377        connection parameters
1378       
1379        @type **prop: dict
1380        @param **prop: any other keywords required
1381        """
1382        raise NotImplementedError(
1383            self.__init__.__doc__.replace('\n       ',''))
1384
1385
1386    def addUser(self, userId, dn=None):
1387        """A new user to Credentials Repository
1388       
1389        @type userId: string
1390        @param userId: userId for new user
1391        @type dn: string
1392        @param dn: users Distinguished Name (optional)"""
1393        raise NotImplementedError(
1394            self.addUser.__doc__.replace('\n       ',''))
1395
1396                           
1397    def auditCredentials(self, userId=None, **attCertValidKeys):
1398        """Check the attribute certificates held in the repository and delete
1399        any that have expired
1400
1401        @type userId: basestring/list or tuple
1402        @param userId: audit credentials for the input user ID or list of IDs
1403        @type attCertValidKeys: dict
1404        @param **attCertValidKeys: keywords which set how to check the
1405        Attribute Certificate e.g. check validity time, XML signature, version
1406         etc.  Default is check validity time only - See AttCert class"""
1407        raise NotImplementedError(
1408            self.auditCredentials.__doc__.replace('\n       ',''))
1409
1410
1411    def getCredentials(self, userId):
1412        """Get the list of credentials for a given users DN
1413       
1414        @type userId: string
1415        @param userId: users userId, name or X.509 cert. distinguished name
1416        @rtype: list
1417        @return: list of Attribute Certificates"""
1418        raise NotImplementedError(
1419            self.getCredentials.__doc__.replace('\n       ',''))
1420
1421       
1422    def addCredentials(self, userId, attCertList):
1423        """Add new attribute certificates for a user.  The user must have
1424        been previously registered in the repository
1425
1426        @type userId: string
1427        @param userId: users userId, name or X.509 cert. distinguished name
1428        @type attCertList: list
1429        @param attCertList: list of attribute certificates"""
1430        raise NotImplementedError(
1431            self.addCredentials.__doc__.replace('\n       ',''))
1432
1433
1434
1435class NullCredentialRepository(CredentialRepository):
1436    """Implementation of Credential Repository interface with empty stubs. 
1437    Use this class in the case where no Credential Repository is required"""
1438   
1439    def __init__(self, propFilePath=None, dbPPhrase=None, **prop):
1440        pass
1441
1442    def addUser(self, userId):
1443        pass
1444                           
1445    def auditCredentials(self, **attCertValidKeys):
1446        pass
1447
1448    def getCredentials(self, userId):
1449        return []
1450       
1451    def addCredentials(self, userId, attCertList):
1452        pass
Note: See TracBrowser for help on using the repository browser.