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

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.common/ndg/security/common/credentialwallet.py@4447
Revision 4447, 70.8 KB checked in by pjkersha, 11 years ago (diff)
  • Updated Session Manager unit tests to include a call to a locally instantiated Attribute Authority
  • fixed bug in CredentialWallet?.getAttCert - ensure attributeAuthority keyword input correctly picked up.
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1"""NDG Credential 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__ = "Philip.Kershaw@stfc.ac.uk"
12__revision__ = '$Id:credentialwallet.py 4378 2008-10-29 10:30:14Z pjkersha $'
13
14import logging
15log = logging.getLogger(__name__)
16
17# Temporary store of certificates for use with CredentialWallet 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 AttributeAuthority instance locally without
27# using the WS
28aaImportError = True
29try:
30    # AttributeAuthority client package resides with CredentialWallet module in
31    # ndg.security.common
32    from ndg.security.common.attributeauthority import \
33        AttributeAuthorityClient, AttributeAuthorityClientError, \
34        AttributeRequestDenied, NoMatchingRoleInTrustedHosts
35    aaImportError = False
36except ImportError:
37    log.warning('Loading CredentialWallet without SOAP interface imports')
38    pass
39
40# Likewise - may not want to use WS and use AttributeAuthority locally in which
41# case no need to import it
42try:
43    from ndg.security.server.attributeauthority import AttributeAuthority, \
44        AttributeAuthorityError, AttributeAuthorityAccessDenied
45    aaImportError = False
46except:
47    log.warning('Loading CredentialWallet without Attribute Authority '
48                'interface imports')
49    pass
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 *
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 _CredentialWalletException(Exception):   
70    """Generic Exception class for CredentialWallet module.  Overrides
71    Exception to enable writing to the log"""
72    def __init__(self, msg):
73        log.error(msg)
74        Exception.__init__(self, msg)
75
76
77class CredentialWalletError(_CredentialWalletException):   
78    """Exception handling for NDG Credential Wallet class.  Overrides Exception
79    to enable writing to the log"""
80
81
82class CredentialWalletAttributeRequestDenied(CredentialWalletError):   
83    """Handling exception where CredentialWallet 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        CredentialWalletError.__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 _MetaCredentialWallet(type):
135    """Enable CredentialWallet to have read only class variables e.g.
136   
137    print CredentialWallet.accessDenied
138   
139    ... is allowed but,
140   
141    CredentialWallet.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# CredentialWallet is a 'new-style' class inheriting from "object" and making
159# use of new Get/Set methods for hiding of attributes
160class CredentialWallet(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 AttributeAuthorityClient instance
177    _attributeAuthorityClnt.  - See attributeAuthorityURI property for
178    details. (property attribute)
179   
180    @type attributeAuthority: ndg.security.server.attributeauthority.AttributeAuthority
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__ = _MetaCredentialWallet
269
270    propertyDefaults = dict(
271        userId=None,
272        userX509Cert=None,
273        userX509CertFilePath=None,
274        userPriKey=None,
275        userPriKeyFilePath=None,
276        issuingX509Cert=None,
277        issuingX509CertFilePath=None,
278        caCertFilePathList=[],
279        sslCACertFilePathList=[],
280        attributeAuthorityURI=None,
281        attributeAuthority=None,
282        credentialRepository=None,
283        mapFromTrustedHosts=False,
284        rtnExtAttCertList=True,
285        attCertRefreshElapse=7200,
286        wssCfgFilePath=None,
287        wssCfgSection='DEFAULT',
288        wssCfgPrefix='',
289        wssCfgKw={})
290   
291    _protectedAttrs = [
292        '_userX509Cert',
293        '_userX509CertFilePath',
294        '_userPriKey',
295        '_userPriKeyFilePath',
296        '_userPriKeyPwd',
297        '_issuingX509Cert',
298        '_issuingX509CertFilePath',
299        '_attributeAuthorityClnt',
300        '_attributeAuthority',
301        '_caCertFilePathList',
302        '_sslCACertFilePathList',
303        '_credentialRepository',
304        '_mapFromTrustedHosts',
305        '_rtnExtAttCertList',
306        '_attCertRefreshElapse',
307        '_cfg',
308        '_credentials',
309        '_dn',
310        '_attributeAuthorityURI'
311    ]
312   
313    __slots__ = propertyDefaults.keys() + _protectedAttrs
314   
315    def __init__(self, 
316                 cfg=None, 
317                 cfgFileSection='DEFAULT', 
318                 cfgPrefix='', 
319                 wssCfgKw={},
320                 **kw):
321        """Create store of user credentials for their current session
322
323        @type cfg: string / ConfigParser object
324        @param cfg: if a string type, this is interpreted as the file path to
325        a configuration file, otherwise it will be treated as a ConfigParser
326        object
327        @type cfgSection: string
328        @param cfgSection: sets the section name to retrieve config params
329        from
330        @type cfgPrefix: basestring
331        @param cfgPrefix: apply a prefix to all CredentialWallet config params
332        so that if placed in a file with other parameters they can be
333        distinguished
334        @type cfgKw: dict
335        @param cfgKw: set parameters as key value pairs."""
336
337        log.debug("Calling CredentialWallet.__init__ ...")
338
339        # Initialise attributes - 1st protected ones
340        attr = {}.fromkeys(CredentialWallet._protectedAttrs)
341       
342        # ... then properties
343        attr.update(CredentialWallet.propertyDefaults)
344        for k, v in attr.items():
345            setattr(self, k, v)
346           
347        # Update attributes from a config file
348        if cfg:
349            self.parseConfig(cfg, section=cfgFileSection, prefix=cfgPrefix)
350
351        # Update attributes from keywords passed - set user private key
352        # password first if it's present.  This is to avoid an error setting
353        # the private key
354        self.userPriKeyPwd = kw.pop('userPriKeyPwd', None)
355        for k,v in kw.items():
356            setattr(self, k, v)
357
358        # Get the distinguished name from the user certificate
359        if self._userX509Cert:
360            self._dn = self._userX509Cert.dn.serialise()
361       
362       
363        # Credentials are stored as a dictionary one element per attribute
364        # certicate held and indexed by certificate issuer name
365        self._credentials = {}
366
367
368        # Make a connection to the Credentials Repository
369        if self._credentialRepository is None:
370            log.info('Applying default CredentialRepository %r for user '
371                     '"%s"' % (NullCredentialRepository, self.userId))
372            self._credentialRepository = NullCredentialRepository()
373        else:
374            log.info('Checking CredentialRepository for credentials for user '
375                     '"%s"' % self.userId)
376           
377            if not issubclass(self._credentialRepository,CredentialRepository):
378                raise CredentialWalletError("Input Credential Repository instance "
379                                      "must be of a class derived from "
380                                      "\"CredentialRepository\"")
381   
382       
383            # Check for valid attribute certificates for the user
384            try:
385                self._credentialRepository.auditCredentials(self.userId)
386                userCred=self._credentialRepository.getCredentials(self.userId)
387   
388            except Exception, e:
389                log.error("Error updating wallet with credentials from "
390                          "repository: %s" % e)
391                raise
392   
393   
394            # Update wallet with attribute certificates stored in the
395            # repository.  Store ID and certificate instantiated as an AttCert
396            # type
397            try:
398                for cred in userCred: 
399                    attCert = AttCertParse(cred.attCert)
400                    issuerName = attCert['issuerName']
401                   
402                    self._credentials[issuerName] = {'id':cred.id, 
403                                                     'attCert':attCert}   
404            except Exception, e:
405                try:
406                    raise CredentialWalletError("Error parsing Attribute "
407                        "Certificate ID '%s' retrieved from the " 
408                        "Credentials Repository: %s" % (cred.id, e))           
409                except:
410                    raise CredentialWalletError("Error parsing Attribute "
411                                          "Certificate retrieved from the "
412                                          "Credentials Repository: %s:" % e)
413           
414            # Filter out expired or otherwise invalid certificates
415            self.audit()
416
417    def parseConfig(self, cfg, prefix='', section='DEFAULT'):
418        '''Extract parameters from _cfg config object'''
419       
420        if isinstance(cfg, basestring):
421            cfgFilePath = os.path.expandvars(cfg)
422            self._cfg = None
423        else:
424            cfgFilePath = None
425            self._cfg = cfg
426           
427        # Configuration file properties are held together in a dictionary
428        readAndValidate = INIPropertyFileWithValidation()
429        prop = readAndValidate(cfgFilePath,
430                               cfg=self._cfg,
431                               validKeys=CredentialWallet.propertyDefaults,
432                               prefix=prefix,
433                               sections=(section,))
434       
435        # Keep a copy of config for use by WS-Security SignatureHandler parser
436        if self._cfg is None:
437            self._cfg = readAndValidate.cfg
438       
439        # Copy prop dict into object attributes - __slots__ definition and
440        # property methods will ensure only the correct attributes are set
441        # Set user private key password first if it's present.  This is to
442        # avoid an error setting the private key
443        self.userPriKeyPwd = prop.pop('userPriKeyPwd', None)
444        for key, val in prop.items():
445            setattr(self, key, val)
446
447
448    def _getAttCertRefreshElapse(self):
449        """Get property method for Attribute Certificate wallet refresh time
450        @rtype: float or int
451        @return: "elapse time in seconds"""
452        return self._attCertRefreshElapse
453   
454    def _setAttCertRefreshElapse(self, val):
455        """Set property method for Attribute Certificate wallet refresh time
456        @type val: float or int
457        @param val: "elapse time in seconds"""
458        if isinstance(val, (float, int)):
459            self._attCertRefreshElapse = val
460           
461        elif isinstance(val, basestring):
462            self._attCertRefreshElapse = float(val)
463        else:
464            raise AttributeError("Expecting int, float or string type input "
465                                 "for attCertRefreshElapse")
466           
467    attCertRefreshElapse = property(fget=_getAttCertRefreshElapse, 
468                                    fset=_setAttCertRefreshElapse,
469                                    doc="If an existing one has AC less than "
470                                        "attCertRefreshElapse time in seconds "
471                                        "left before expiry then replace it")
472   
473    def _setX509Cert(self, cert):
474        """filter and convert input cert to signing verifying cert set
475        property methods.  For signingCert, set to None if it is not to be
476        included in the SOAP header.  For verifyingCert, set to None if this
477        cert can be expected to be retrieved from the SOAP header of the
478        message to be verified
479       
480        @type: ndg.security.common.X509.X509Cert / M2Crypto.X509.X509 /
481        string or None
482        @param cert: X.509 certificate. 
483       
484        @rtype ndg.security.common.X509.X509Cert
485        @return X.509 certificate object"""
486       
487        if cert is None or isinstance(cert, X509Cert):
488            # ndg.security.common.X509.X509Cert type / None
489            return cert
490           
491        elif isinstance(cert, X509.X509):
492            # M2Crypto.X509.X509 type
493            return X509Cert(m2CryptoX509=cert)
494           
495        elif isinstance(cert, basestring):
496            return X509CertParse(cert)
497       
498        else:
499            raise AttributeError("X.509 Cert. must be type: "
500                                 "ndg.security.common.X509.X509Cert, "
501                                 "M2Crypto.X509.X509 or a base64 encoded "
502                                 "string")
503
504    def _setUserX509Cert(self, userX509Cert):
505        "Set property method for X.509 user cert."
506        self._userX509Cert = self._setX509Cert(userX509Cert)
507       
508
509    def _getUserX509Cert(self):
510        """Get user cert X509Cert instance"""
511        return self._userX509Cert
512
513    userX509Cert = property(fget=_getUserX509Cert,
514                            fset=_setUserX509Cert,
515                            doc="X.509 user certificate instance")
516 
517    def _setUserX509CertFilePath(self, filePath):
518        "Set user X.509 cert file path property method"
519       
520        if isinstance(filePath, basestring):
521            filePath = os.path.expandvars(filePath)
522            self._userX509Cert = X509CertRead(filePath)
523           
524        elif filePath is not None:
525            raise AttributeError("User X.509 cert. file path must be a valid "
526                                 "string")
527       
528        self._userX509CertFilePath = filePath
529               
530    userX509CertFilePath = property(fset=_setUserX509CertFilePath,
531                                    doc="File path to user X.509 cert.")
532   
533    def _setIssuingX509Cert(self, issuingX509Cert):
534        "Set property method for X.509 user cert."
535        self._issuingX509Cert = self._setX509Cert(issuingX509Cert)
536       
537    def _getIssuingX509Cert(self):
538        """Get user cert X509Cert instance"""
539        return self._issuingX509Cert
540
541    issuingX509Cert = property(fget=_getIssuingX509Cert,
542                               fset=_setIssuingX509Cert,
543                               doc="X.509 user certificate instance")
544 
545    def _setIssuerX509CertFilePath(self, filePath):
546        "Set user X.509 cert file path property method"
547       
548        if isinstance(filePath, basestring):
549            filePath = os.path.expandvars(filePath)
550            self._issuerX509Cert = X509CertRead(filePath)
551           
552        elif filePath is not None:
553            raise AttributeError("User X.509 cert. file path must be a valid "
554                                 "string")
555       
556        self._issuerX509CertFilePath = filePath
557               
558    issuerX509CertFilePath = property(fset=_setIssuerX509CertFilePath,
559                                      doc="File path to user X.509 cert. "
560                                          "issuing cert.")     
561
562    def _getUserPriKey(self):
563        "Get method for user private key"
564        return self._userPriKey
565   
566    def _setUserPriKey(self, userPriKey):
567        """Set method for user private key
568       
569        Nb. if input is a string, userPriKeyPwd will need to be set if
570        the key is password protected.
571       
572        @type userPriKey: M2Crypto.RSA.RSA / string
573        @param userPriKey: private key used to sign message"""
574       
575        if userPriKey is None:
576            log.warning("Setting user private key to None")
577            self._userPriKey = None
578        elif isinstance(userPriKey, basestring):
579            pwdCallback = lambda *ar, **kw: self._userPriKeyPwd
580            self._userPriKey = RSA.load_key_string(userPriKey,
581                                                   callback=pwdCallback)
582        elif isinstance(userPriKey, RSA.RSA):
583            self._userPriKey = userPriKey         
584        else:
585            raise AttributeError("user private key must be a valid "
586                                 "M2Crypto.RSA.RSA type or a string")
587               
588    userPriKey = property(fget=_getUserPriKey,
589                          fset=_setUserPriKey,
590                          doc="User private key if set, used to sign outbound "
591                              "messages to Attribute authority")
592
593    def _setUserPriKeyFilePath(self, filePath):
594        "Set user private key file path property method"
595       
596        if isinstance(filePath, basestring):
597            filePath = os.path.expandvars(filePath)
598            try:
599                # Read Private key to sign with   
600                priKeyFile = BIO.File(open(filePath)) 
601                pwdCallback = lambda *ar, **kw: self._userPriKeyPwd
602                self._userPriKey = RSA.load_key_bio(priKeyFile, 
603                                                    callback=pwdCallback)   
604            except Exception, e:
605                raise AttributeError("Setting user private key: %s" % e)
606       
607        elif filePath is not None:
608            raise AttributeError("Private key file path must be a valid "
609                                 "string or None")
610       
611        self._userPriKeyFilePath = filePath
612       
613    userPriKeyFilePath = property(fset=_setUserPriKeyFilePath,
614                                  doc="File path to user private key")
615 
616    def _setUserPriKeyPwd(self, userPriKeyPwd):
617        "Set method for user private key file password"
618        if userPriKeyPwd is not None and not isinstance(userPriKeyPwd, 
619                                                        basestring):
620            raise AttributeError("Signing private key password must be None "
621                                 "or a valid string")
622       
623        # Explicitly convert to string as M2Crypto OpenSSL wrapper fails with
624        # unicode type
625        self._userPriKeyPwd = str(userPriKeyPwd)
626
627    def _getUserPriKeyPwd(self):
628        "Get property method for user private key"
629        return self._userPriKeyPwd
630       
631    userPriKeyPwd = property(fset=_setUserPriKeyPwd,
632                             fget=_getUserPriKeyPwd,
633                             doc="Password protecting user private key file")
634       
635    def _getCredentials(self):
636        """Get Property method.  Credentials are read-only
637       
638        @rtype: dict
639        @return: cached ACs indesed by issuing organisation name"""
640        return self._credentials
641
642    # Publish attribute
643    credentials = property(fget=_getCredentials,
644                           doc="List of Attribute Certificates linked to "
645                               "issuing authorities")
646
647
648    def _getCACertFilePathList(self):
649        """Get CA cert or certs used to validate AC signatures and signatures
650        of peer SOAP messages.
651       
652        @rtype caCertFilePathList: basestring, list or tuple
653        @return caCertFilePathList: file path(s) to CA certificates."""
654        return self._caCertFilePathList
655   
656    def _setCACertFilePathList(self, caCertFilePathList):
657        """Set CA cert or certs to validate AC signatures, signatures
658        of Attribute Authority SOAP responses and SSL connections where
659        AA SOAP service is run over SSL.
660       
661        @type caCertFilePathList: basestring, list, tuple or None
662        @param caCertFilePathList: file path(s) to CA certificates.  If None
663        then the input is quietly ignored."""
664       
665        if isinstance(caCertFilePathList, basestring):
666           self._caCertFilePathList = [caCertFilePathList]
667           
668        elif isinstance(caCertFilePathList, list):
669           self._caCertFilePathList = caCertFilePathList
670           
671        elif isinstance(caCertFilePathList, tuple):
672           self._caCertFilePathList = list(caCertFilePathList)
673
674        elif caCertFilePathList is not None:
675            raise CredentialWalletError("Input CA Certificate file path is not a "
676                                  "valid string")     
677       
678    caCertFilePathList = property(fget=_getCACertFilePathList,
679                                  fset=_setCACertFilePathList,
680                                  doc="CA Certificates - used for "
681                                      "verification of AC and SOAP message "
682                                      "signatures")
683
684    def _getSSLCACertFilePathList(self):
685        """Get CA cert or certs used to validate AC signatures and signatures
686        of peer SOAP messages.
687       
688        @rtype sslCACertFilePathList: basestring, list or tuple
689        @return sslCACertFilePathList: file path(s) to CA certificates."""
690        return self._sslCACertFilePathList
691   
692    def _setSSLCACertFilePathList(self, sslCACertFilePathList):
693        """Set CA cert or certs to validate AC signatures, signatures
694        of Attribute Authority SOAP responses and SSL connections where
695        AA SOAP service is run over SSL.
696       
697        @type sslCACertFilePathList: basestring, list, tuple or None
698        @param sslCACertFilePathList: file path(s) to CA certificates.  If None
699        then the input is quietly ignored."""
700       
701        if isinstance(sslCACertFilePathList, basestring):
702           self._sslCACertFilePathList = [sslCACertFilePathList]
703           
704        elif isinstance(sslCACertFilePathList, list):
705           self._sslCACertFilePathList = sslCACertFilePathList
706           
707        elif isinstance(sslCACertFilePathList, tuple):
708           self._sslCACertFilePathList = list(sslCACertFilePathList)
709
710        elif sslCACertFilePathList is not None:
711            raise CredentialWalletError("Input CA Certificate file path is "
712                                        "not a valid string")     
713       
714    sslCACertFilePathList = property(fget=_getSSLCACertFilePathList,
715                                  fset=_setSSLCACertFilePathList,
716                                  doc="CA Certificates - used for "
717                                      "verification of peer certs in SSL "
718                                      "connections")
719       
720    def _getAttributeAuthorityURI(self):
721        """Get property method for Attribute Authority Web Service URI to
722        connect to."""
723        return self._attributeAuthorityURI
724           
725    def _setAttributeAuthorityURI(self, attributeAuthorityURI):
726        """Set property method for Attribute Authority Web Service URI to
727        connect to.  This method ALSO RESETS attributeAuthority - a local
728        Attribute Authority instance - to None
729       
730        @type attributeAuthorityURI: basestring/None
731        @param attributeAuthorityURI: Attribute Authority Web Service URI.  Set
732        to None to initialise."""
733        if attributeAuthorityURI is not None and \
734           not isinstance(attributeAuthorityURI, basestring):
735            raise AttributeError("URI must be a string or None type")
736       
737        self._attributeAuthorityURI = attributeAuthorityURI
738       
739        # Re-initialize local instance
740        self._attributeAuthority = \
741                    CredentialWallet.propertyDefaults['attributeAuthority']
742           
743    attributeAuthorityURI = property(fget=_getAttributeAuthorityURI,
744                                     fset=_setAttributeAuthorityURI,
745                                     doc="Attribute Authority address - "
746                                         "setting also sets up "
747                                         "AttributeAuthorityClient instance!")
748
749    def _getAttributeAuthority(self):
750        """Get property method for Attribute Authority Web Service client
751        instance.  Use attributeAuthorityURI propert to set up
752        attributeAuthorityClnt
753       
754        @rtype attributeAuthority: ndg.security.server.attributeauthority.AttributeAuthority
755        @return attributeAuthority: Attribute Authority instance"""
756        return self._attributeAuthority
757
758    def _setAttributeAuthority(self, attributeAuthority):
759        """Set property method for Attribute Authority Web Service instance to
760        connect to.  This method ALSO RESETS attributeAuthorityURI - the
761        address of a remote Attribute Authority - to None
762       
763        @type attributeAuthority: ndg.security.server.attributeauthority.AttributeAuthority
764        @param attributeAuthority: Attribute Authority instance."""
765        if attributeAuthority is not None and \
766           not isinstance(attributeAuthority, AttributeAuthority):
767            raise AttributeError("Expecting %r for attributeAuthority "
768                                 "attribute" % AttributeAuthority)
769           
770        self._attributeAuthority = attributeAuthority
771       
772        # Re-initialize setting for remote service
773        self._attributeAuthorityURI = \
774                    CredentialWallet.propertyDefaults['attributeAuthorityURI']
775           
776    attributeAuthority = property(fget=_getAttributeAuthority,
777                                  fset=_setAttributeAuthority, 
778                                  doc="Attribute Authority instance")
779
780
781    def _getMapFromTrustedHosts(self):
782        """Get property method for boolean flag - if set to True it allows
783        role mapping to be attempted when connecting to an Attribute Authority
784       
785        @type mapFromTrustedHosts: bool
786        @param mapFromTrustedHosts: set to True to try role mapping in AC
787        requests to Attribute Authorities"""
788        return self._mapFromTrustedHosts
789
790    def _setMapFromTrustedHosts(self, mapFromTrustedHosts):
791        """Set property method for boolean flag - if set to True it allows
792        role mapping to be attempted when connecting to an Attribute Authority
793       
794        @type mapFromTrustedHosts: bool
795        @param mapFromTrustedHosts: Attribute Authority Web Service."""
796        if not isinstance(mapFromTrustedHosts, bool):
797            raise AttributeError("Expecting %r for mapFromTrustedHosts "
798                                 "attribute" % bool)
799           
800        self._mapFromTrustedHosts = mapFromTrustedHosts
801           
802    mapFromTrustedHosts = property(fget=_getMapFromTrustedHosts,
803                                   fset=_setMapFromTrustedHosts, 
804                                   doc="Set to True to enable mapped AC "
805                                       "requests")
806
807    def _getRtnExtAttCertList(self):
808        """Get property method for Attribute Authority Web Service client
809        instance.  Use rtnExtAttCertListURI propert to set up
810        rtnExtAttCertListClnt
811       
812        @type rtnExtAttCertList: bool
813        @param rtnExtAttCertList: """
814        return self._rtnExtAttCertList
815
816    def _setRtnExtAttCertList(self, rtnExtAttCertList):
817        """Set property method for boolean flag - when a AC request fails,
818        return a list of candidate ACs that could be used to re-try with in
819        order to get mapped AC.
820       
821        @type rtnExtAttCertList: bool
822        @param rtnExtAttCertList: set to True to configure getAttCert to return
823        a list of ACs that could be used in a re-try to get a mapped AC from
824        the target Attribute Authority."""
825        if not isinstance(rtnExtAttCertList, bool):
826            raise AttributeError("Expecting %r for rtnExtAttCertList "
827                                 "attribute" % bool)
828           
829        self._rtnExtAttCertList = rtnExtAttCertList
830           
831    rtnExtAttCertList = property(fget=_getRtnExtAttCertList,
832                                 fset=_setRtnExtAttCertList, 
833                                 doc="Set to True to enable mapped AC "
834                                     "requests")
835
836    def isValid(self, **x509CertKeys):
837        """Check wallet's user cert.  If expired return False
838       
839        @type **x509CertKeys: dict
840        @param **x509CertKeys: keywords applying to
841        ndg.security.common.X509.X509Cert.isValidTime method"""
842        if self._userX509Cert is not None:
843            return self._userX509Cert.isValidTime(**x509CertKeys)
844        else:
845            log.warning("CredentialWallet.isValid: no user certificate set in "
846                        "wallet")
847            return True
848
849
850    def addCredential(self, attCert, bUpdateCredentialRepository=True):
851        """Add a new attribute certificate to the list of credentials held.
852
853        @type attCert:
854        @param attCert: new attribute Certificate to be added
855        @type bUpdateCredentialRepository: bool
856        @param bUpdateCredentialRepository: if set to True, and a repository
857        exists it will be updated with the new credentials also
858       
859        @rtype: bool
860        @return: True if certificate was added otherwise False.  - If an
861        existing certificate from the same issuer has a later expiry it will
862        take precence and the new input certificate is ignored."""
863
864        # Check input
865        if not isinstance(attCert, AttCert):
866            raise CredentialWalletError("Attribute Certificate must be an AttCert "
867                                  "type object")
868
869        # Check certificate validity
870        try:
871            attCert.isValid(raiseExcep=True)
872           
873        except AttCertError, e:
874            raise CredentialWalletError("Adding Credential: %s" % e)
875       
876
877        # Check to see if there is an existing Attribute Certificate held
878        # that was issued by the same host.  If so, compare the expiry time.
879        # The one with the latest expiry will be retained and the other
880        # ingored
881        bUpdateCred = True
882        issuerName = attCert['issuerName']
883       
884        if issuerName in self._credentials:
885            # There is an existing certificate held with the same issuing
886            # host name as the new certificate
887            attCertOld = self._credentials[issuerName]['attCert']
888
889            # Get expiry times in datetime format to allow comparison
890            dtAttCertOldNotAfter = attCertOld.getValidityNotAfter(\
891                                                            asDatetime=True)
892            dtAttCertNotAfter = attCert.getValidityNotAfter(asDatetime=True)
893
894            # If the new certificate has an earlier expiry time then ignore it
895            bUpdateCred = dtAttCertNotAfter > dtAttCertOldNotAfter
896
897               
898        if bUpdateCred:
899            # Update: Nb. -1 ID value flags item as new.  Items read in
900            # from the CredentialRepository during creation of the wallet will
901            # have +ve IDs previously allocated by the database
902            self._credentials[issuerName] = {'id': -1, 'attCert': attCert}
903
904            # Update the Credentials Repository - the permanent store of user
905            # authorisation credentials.  This allows credentials for previous
906            # sessions to be re-instated
907            if self._credentialRepository and bUpdateCredentialRepository:
908                self.updateCredentialRepository()
909
910        # Flag to caller to indicate whether the input certificate was added
911        # to the credentials or an exsiting certificate from the same issuer
912        # took precedence
913        return bUpdateCred
914           
915
916    def audit(self):
917        """Check the credentials held in the wallet removing any that have
918        expired or are otherwise invalid."""
919
920        log.debug("CredentialWallet.audit ...")
921       
922        # Nb. No signature check is carried out.  To do a check, access is
923        # needed to the cert of the CA that issued the Attribute Authority's
924        # cert
925        #
926        # P J Kershaw 12/09/05
927        for key, val in self._credentials.items():
928            if not val['attCert'].isValid(chkSig=False):
929                del self._credentials[key]
930
931
932    def updateCredentialRepository(self, auditCred=True):
933        """Copy over non-persistent credentials held by wallet into the
934        perminent repository.
935       
936        @type auditCred: bool
937        @param auditCred: filter existing credentials in the repository
938        removing invalid ones"""
939
940        log.debug("CredentialWallet.updateCredentialRepository ...")
941       
942        if not self._credentialRepository:
943            raise CredentialWalletError("No Credential Repository has been created "
944                                  "for this wallet")
945                           
946        # Filter out invalid certs unless auditCred flag is explicitly set to
947        # false
948        if auditCred: self.audit()
949
950        # Update the database - only add new entries i.e. with an ID of -1
951        attCertList = [i['attCert'] for i in self._credentials.values() \
952                       if i['id'] == -1]
953
954        self._credentialRepository.addCredentials(self.userId, attCertList)
955
956
957    def _createAttributeAuthorityClnt(self, attributeAuthorityURI):
958        """Set up a client to an Attribute Authority with the given URI
959       
960        @type attributeAuthorityURI: string
961        @param attributeAuthorityURI: Attribute Authority Web Service URI.
962
963        @rtype: ndg.security.common.attributeauthority.AttributeAuthorityClient
964        @return: new Attribute Authority client instance"""
965
966        log.debug('CredentialWallet._createAttributeAuthorityClnt for '
967                  'service: "%s"' % attributeAuthorityURI)
968
969        attributeAuthorityClnt = AttributeAuthorityClient(
970                            uri=attributeAuthorityURI,
971                            sslCACertFilePathList=self._sslCACertFilePathList,
972                            cfg=self.wssCfgFilePath or self._cfg,
973                            cfgFileSection=self.wssCfgSection,
974                            cfgFilePrefix=self.wssCfgPrefix,
975                            **(self.wssCfgKw or {}))
976       
977        # If a user certificate is set, use this to sign messages instead of
978        # the default settings in the WS-Security config. 
979        if attributeAuthorityClnt.signatureHandler is not None and \
980           self.userPriKey is not None:
981            if self.issuingX509Cert is not None:
982                # Pass a chain of certificates -
983                # Initialise WS-Security signature handling to pass
984                # BinarySecurityToken containing user cert and cert for user
985                # cert issuer
986                attributeAuthorityClnt.signatureHandler.reqBinSecTokValType = \
987                            SignatureHandler.binSecTokValType["X509PKIPathv1"]
988                attributeAuthorityClnt.signatureHandler.signingCertChain = \
989                                    (self.issuingX509Cert, self.userX509Cert)               
990
991                attributeAuthorityClnt.signatureHandler.signingPriKey = \
992                                                            self.userPriKey
993            elif self.userX509Cert is not None:
994                # Pass user cert only - no need to pass a cert chain. 
995                # This type of token is more likely to be supported by the
996                # various WS-Security toolkits
997                attributeAuthorityClnt.signatureHandler.reqBinSecTokValType = \
998                                    SignatureHandler.binSecTokValType["X509v3"]
999                attributeAuthorityClnt.signatureHandler.signingCert = \
1000                                                            self.userX509Cert
1001
1002                attributeAuthorityClnt.signatureHandler.signingPriKey = \
1003                                                            self.userPriKey
1004
1005        return attributeAuthorityClnt
1006
1007
1008    def _getAttCert(self, 
1009                    attributeAuthorityURI=None, 
1010                    attributeAuthority=None,
1011                    extAttCert=None):       
1012        """Wrapper to Attribute Authority attribute certificate request.  See
1013        getAttCert for the classes' public interface.
1014       
1015        If successful, a new attribute certificate is issued to the user
1016        and added into the wallet
1017       
1018        @type attributeAuthorityURI: string
1019        @param attributeAuthorityURI: to call as a web service, specify the URI for the
1020        Attribute Authority.
1021       
1022        @type attributeAuthority: ndg.security.server.attributeauthority.AttributeAuthority
1023        @param attributeAuthority: Alternative to attributeAuthorityURI - to
1024        run on the local machine, specify a local Attribute Authority
1025        instance.
1026
1027        @type extAttCert: ndg.security.common.AttCert.AttCert
1028        @param extAttCert: an existing Attribute Certificate which can
1029        be used to making a mapping should the user not be registered with the
1030        Attribute Authority"""
1031     
1032        log.debug("CredentialWallet._getAttCert ...")
1033       
1034       
1035        # If a user cert. is present, ignore the user ID setting.  The
1036        # Attribute Authority will set the userId field of the
1037        # Attribute Certificate based on the DN of the user certificate
1038        if self.userX509Cert:
1039            userId = None
1040        else:
1041            userId = self.userId
1042           
1043        if attributeAuthority is not None and \
1044           attributeAuthorityURI is not None:
1045            raise KeyError("Both attributeAuthorityURI and attributeAuthority "
1046                           "keywords have been set")
1047       
1048        if attributeAuthority is None:
1049            attributeAuthority = self.attributeAuthority
1050           
1051        if attributeAuthorityURI is None:
1052            attributeAuthorityURI = self.attributeAuthorityURI
1053           
1054        # Set a client alias according to whether the Attribute Authority is
1055        # being called locally or as a remote service
1056        if attributeAuthorityURI is not None:
1057            # Call Remote Service at given URI
1058            aaInterface = self._createAttributeAuthorityClnt(
1059                                                        attributeAuthorityURI)                           
1060            log.debug('CredentialWallet._getAttCert for remote Attribute '
1061                      'Authority service: "%s" ...' % attributeAuthorityURI)
1062               
1063        elif attributeAuthority is not None:
1064            # Call local based Attribute Authority with settings from the
1065            # configuration file attributeAuthority
1066            aaInterface = attributeAuthority
1067            log.debug('CredentialWallet._getAttCert for local Attribute '
1068                      'Authority: "%r" ...' % attributeAuthority)
1069        else:
1070            raise CredentialWalletError("Error requesting attribute: "
1071                                        "certificate a URI or Attribute "
1072                                        "Authority instance must be specified")
1073       
1074        try:
1075            # Request a new attribute certificate from the Attribute
1076            # Authority
1077            attCert = aaInterface.getAttCert(userId=userId,
1078                                             userAttCert=extAttCert)
1079           
1080            log.info('Granted Attribute Certificate from issuer DN = "%s"'%
1081                     attCert.issuerDN)
1082           
1083        except (AttributeAuthorityAccessDenied, AttributeRequestDenied), e:
1084            # AttributeAuthorityAccessDenied is raised if
1085            # aaInterface is a local AA instance and
1086            # AttributeRequestDenied is raised for a client to a remote AA
1087            # service
1088            raise CredentialWalletAttributeRequestDenied(str(e))
1089                   
1090        except Exception, e:
1091            raise CredentialWalletError("Requesting attribute certificate: %s"%
1092                                        e)
1093
1094        # Update attribute Certificate instance with CA's certificate ready
1095        # for signature check in addCredential()
1096        if self._caCertFilePathList is None:
1097            raise CredentialWalletError("No CA certificate has been set")
1098       
1099        attCert.certFilePathList = self._caCertFilePathList
1100
1101       
1102        # Add credential into wallet
1103        #
1104        # Nb. if the certificates signature is invalid, it will be rejected
1105        log.debug("Adding credentials into wallet...")
1106        self.addCredential(attCert)
1107       
1108        return attCert
1109
1110
1111    def _getAAHostInfo(self, 
1112                       attributeAuthority=None,
1113                       attributeAuthorityURI=None):
1114        """Wrapper to Attribute Authority getHostInfo
1115       
1116        _getAAHostInfo([attributeAuthority=f|attributeAuthorityURI=u])
1117                   
1118        @type userRole: string
1119        @param userRole: get hosts which have a mapping to this role
1120       
1121        @type attributeAuthorityURI: string
1122        @param attributeAuthorityURI: to call as a web service, specify the URI for the
1123        Attribute Authority.
1124       
1125        @type attributeAuthority: string
1126        @param attributeAuthority: Alternative to attributeAuthorityURI - to
1127        run on the local machine, specify the local Attribute Authority
1128        instance.
1129        """
1130
1131        if attributeAuthority is None:
1132            attributeAuthority = self.attributeAuthority
1133           
1134        if attributeAuthorityURI is None:
1135            attributeAuthorityURI = self.attributeAuthorityURI
1136       
1137        log.debug('CredentialWallet._getAAHostInfo for service: "%s" ...' % 
1138                  attributeAuthorityURI or attributeAuthority)
1139           
1140        # Set a client alias according to whether the Attribute Authority is
1141        # being called locally or asa remote service
1142        if attributeAuthorityURI is not None:
1143            # Call Remote Service at given URI
1144            attributeAuthorityClnt = self._createAttributeAuthorityClnt(
1145                                                    attributeAuthorityURI)
1146
1147        elif attributeAuthority is not None:
1148            # Call local based Attribute Authority with settings from the
1149            # configuration file attributeAuthority
1150            attributeAuthorityClnt = attributeAuthority
1151           
1152        else:
1153            raise CredentialWalletError("Error requesting trusted hosts info: " 
1154                                        "a URI or Attribute Authority " 
1155                                        "configuration file must be specified")
1156           
1157        try:
1158            # Request a new attribute certificate from the Attribute
1159            # Authority
1160            return attributeAuthorityClnt.getHostInfo()
1161           
1162        except Exception, e:
1163            log.error("Requesting host info: %s" % e)
1164            raise
1165
1166
1167    def _getAATrustedHostInfo(self, 
1168                              userRole=None,
1169                              attributeAuthority=None,
1170                              attributeAuthorityURI=None):
1171        """Wrapper to Attribute Authority getTrustedHostInfo
1172       
1173        _getAATrustedHostInfo([userRole=r, ][attributeAuthority=f|
1174                              attributeAuthorityURI=u])
1175                   
1176        @type userRole: string
1177        @param userRole: get hosts which have a mapping to this role
1178       
1179        @type attributeAuthorityURI: string
1180        @param attributeAuthorityURI: to call as a web service, specify the URI for the
1181        Attribute Authority.
1182       
1183        @type attributeAuthority: string
1184        @param attributeAuthority: Alternative to attributeAuthorityURI - to
1185        run on the local machine, specify the local Attribute Authority
1186        instance.
1187        """
1188
1189        if attributeAuthority is None:
1190            attributeAuthority = self.attributeAuthority
1191           
1192        if attributeAuthorityURI is None:
1193            attributeAuthorityURI = self.attributeAuthorityURI
1194       
1195        log.debug('CredentialWallet._getAATrustedHostInfo for role "%s" and '
1196                  'service: "%s" ...' % (userRole, 
1197                                attributeAuthorityURI or attributeAuthority))
1198           
1199        # Set a client alias according to whether the Attribute Authority is
1200        # being called locally or asa remote service
1201        if attributeAuthorityURI is not None:
1202            # Call Remote Service at given URI
1203            attributeAuthorityClnt = self._createAttributeAuthorityClnt(
1204                                                    attributeAuthorityURI)
1205
1206        elif attributeAuthority is not None:
1207            # Call local based Attribute Authority with settings from the
1208            # configuration file attributeAuthority
1209            attributeAuthorityClnt = attributeAuthority
1210           
1211        else:
1212            raise CredentialWalletError("Error requesting trusted hosts info: " 
1213                                        "a URI or Attribute Authority " 
1214                                        "configuration file must be specified")
1215           
1216        try:
1217            # Request a new attribute certificate from the Attribute
1218            # Authority
1219            return attributeAuthorityClnt.getTrustedHostInfo(role=userRole)
1220           
1221        except Exception, e:
1222            log.error("Requesting trusted host info: %s" % e)
1223            raise
1224
1225
1226    def getAttCert(self,
1227                   reqRole=None,
1228                   attributeAuthority=None,
1229                   attributeAuthorityURI=None,
1230                   mapFromTrustedHosts=None,
1231                   rtnExtAttCertList=None,
1232                   extAttCertList=None,
1233                   extTrustedHostList=None,
1234                   refreshAttCert=False,
1235                   attCertRefreshElapse=None):
1236       
1237        """Get an Attribute Certificate from an Attribute Authority.  If this
1238        fails try to make a mapped Attribute Certificate by using a certificate
1239        from another host which has a trust relationship to the Attribute
1240        Authority in question.
1241
1242        getAttCert([reqRole=r, ][attributeAuthority=a|attributeAuthorityURI=u,]
1243                   [mapFromTrustedHosts=m, ]
1244                   [rtnExtAttCertList=e, ][extAttCertList=el, ]
1245                   [extTrustedHostList=et, ][refreshAttCert=ra])
1246                 
1247        The procedure is:
1248
1249        1) Try attribute request using user certificate
1250        2) If the Attribute Authority (AA) doesn't recognise the certificate,
1251        find out any other hosts which have a trust relationship to the AA.
1252        3) Look for Attribute Certificates held in the wallet corresponding
1253        to these hosts.
1254        4) If no Attribute Certificates are available, call the relevant
1255        hosts' AAs to get certificates
1256        5) Finally, use these new certificates to try to obtain a mapped
1257        certificate from the original AA
1258        6) If this fails access is denied     
1259                   
1260        @type reqRole: string
1261        @param reqRole: the required role to get access for
1262       
1263        @type attributeAuthorityURI: string
1264        @param attributeAuthorityURI: to call as a web service, specify the URI for the
1265        Attribute Authority.
1266       
1267        @type attributeAuthority: string
1268        @param attributeAuthority: Altenrative to attributeAuthorityURI - to
1269        run on the local machine, specify a local Attribute Authority
1270        instance.
1271                               
1272        @type mapFromTrustedHosts: bool / None     
1273        @param mapFromTrustedHosts: if request fails via the user's cert
1274        ID, then it is possible to get a mapped certificate by using
1275        certificates from other AA's.  Set this flag to True, to allow this
1276        second stage of generating a mapped certificate from the certificate
1277        stored in the wallet credentials.
1278
1279        If set to False, it is possible to return the list of certificates
1280        available for mapping and then choose which one or ones to use for
1281        mapping by re-calling getAttCert with extAttCertList set to these
1282        certificates.
1283       
1284        Defaults to None in which case self._mapFromTrustedHosts is not
1285        altered
1286
1287        The list is returned via CredentialWalletAttributeRequestDenied
1288        exception.  If no value is set, the default value held in
1289        self.mapFromTrustedHosts is used
1290
1291        @type rtnExtAttCertList: bool / None
1292        @param rtnExtAttCertList: If request fails, make a list of
1293        candidate certificates from other Attribute Authorities which the user
1294        could use to retry and get a mapped certificate.
1295                               
1296        If mapFromTrustedHosts is set True this flags value is overriden and
1297        effectively set to True.
1298
1299        If no value is set, the default value held in self._rtnExtAttCertList
1300        is used.
1301                               
1302        The list is returned via a CredentialWalletAttributeRequestDenied
1303        exception object.
1304                               
1305        @type extAttCertList: list
1306        @param extAttCertList: Attribute Certificate or list of certificates
1307        from other Attribute Authorities.  These can be used to get a mapped
1308        certificate if access fails based on the user's certificate
1309        credentials.  They are tried out in turn until access is granted so
1310        the order of the list decides the order in which they will be tried
1311
1312        @type extTrustedHostList:
1313        @param extTrustedHostList: same as extAttCertList keyword, but
1314        instead of providing Attribute Certificates, give a list of Attribute
1315        Authority hosts.  These will be matched up to Attribute Certificates
1316        held in the wallet.  Matching certificates will then be used to try to
1317        get a mapped Attribute Certificate.
1318       
1319        @type refreshAttCert: bool
1320        @param refreshAttCert: if set to True, the attribute request
1321        will go ahead even if the wallet already contains an Attribute
1322        Certificate from the target Attribute Authority.  The existing AC in
1323        the wallet will be replaced by the new one obtained from this call.
1324                               
1325        If set to False, this method will check to see if an AC issued by the
1326        target AA already exists in the wallet.  If so, it will return this AC
1327        to the caller without proceeding to make a call to the AA.
1328       
1329        @type attCertRefreshElapse: float / int
1330        @param attCertRefreshElapse: determine whether to replace an
1331        existing AC in the cache with a fresh one.  If the existing one has
1332        less than attCertRefreshElapse time in seconds left before expiry then
1333        replace it.
1334       
1335        @rtype: ndg.security.common.AttCert.AttCert
1336        @return: Attribute Certificate retrieved from Attribute Authority"""
1337       
1338        log.debug("CredentialWallet.getAttCert ...")
1339       
1340        # Both these assignments are calling set property methods implicitly!
1341        if attributeAuthorityURI:
1342            self.attributeAuthorityURI = attributeAuthorityURI
1343           
1344        if attributeAuthority is not None:
1345            self.attributeAuthority = attributeAuthority
1346           
1347        if not refreshAttCert and self._credentials:
1348            # Refresh flag is not set so it's OK to check for any existing
1349            # Attribute Certificate in the wallet whose issuerName match the
1350            # target AA's name
1351           
1352            # Find out the site ID for the target AA by calling AA's host
1353            # info WS method
1354            log.debug("CredentialWallet.getAttCert - check AA site ID ...")
1355            try:
1356                hostInfo = self._getAAHostInfo()
1357                aaName = hostInfo.keys()[0]
1358            except Exception, e:
1359                raise CredentialWalletError("Getting host info: %s" % e)
1360           
1361            # Look in the wallet for an AC with the same issuer name
1362            if aaName in self._credentials:
1363                # Existing Attribute Certificate found in wallet - Check that
1364                # it will be valid for at least the next 2 hours
1365                if attCertRefreshElapse is not None:
1366                    self.attCertRefreshElapse = attCertRefreshElapse
1367                   
1368                dtNow = datetime.utcnow() + \
1369                        timedelta(seconds=self.attCertRefreshElapse)
1370               
1371                attCert = self._credentials[aaName]['attCert']
1372                if attCert.isValidTime(dtNow=dtNow):
1373                    log.info("Retrieved an existing %s AC from the wallet" % 
1374                             aaName)
1375                    return attCert
1376           
1377           
1378        # Check for settings from input, if not set use previous settings
1379        # made
1380        if mapFromTrustedHosts is not None:
1381            self.mapFromTrustedHosts = mapFromTrustedHosts
1382
1383        if rtnExtAttCertList is not None:
1384            self.rtnExtAttCertList = rtnExtAttCertList
1385
1386
1387        # Check for list of external trusted hosts (other trusted NDG data
1388        # centres)
1389        if extTrustedHostList:
1390            log.info("Checking for ACs in wallet matching list of trusted "
1391                     "hosts set: %s" % extTrustedHostList)
1392           
1393            if not self.mapFromTrustedHosts:
1394                raise CredentialWalletError("A list of trusted hosts has been " 
1395                                      "input but mapping from trusted hosts "
1396                                      "is set to disallowed")
1397           
1398            if isinstance(extTrustedHostList, basestring):
1399                extTrustedHostList = [extTrustedHostList]
1400
1401            # Nb. Any extAttCertList is overriden by extTrustedHostList being
1402            # set
1403            extAttCertList = [self._credentials[hostName]['attCert'] \
1404                              for hostName in extTrustedHostList \
1405                              if hostName in self._credentials]
1406
1407        # Set an empty list to trigger an AttributeError by initialising it to
1408        # None
1409        if extAttCertList == []:
1410            extAttCertList = None
1411           
1412        # Repeat authorisation attempts until succeed or means are exhausted
1413        while True:
1414           
1415            # Check for candidate certificates for mapping
1416            try:
1417                # If list is set get the next cert
1418                extAttCert = extAttCertList.pop()
1419
1420            except AttributeError:
1421                log.debug("No external Attribute Certificates - trying "
1422                          "request without mapping...")
1423                # No List set - attempt request without
1424                # using mapping from trusted hosts
1425                extAttCert = None
1426                           
1427            except IndexError:
1428               
1429                # List has been emptied without attribute request succeeding -
1430                # give up
1431                errMsg = "Attempting to obtained a mapped certificate: " + \
1432                         "no external attribute certificates are available"
1433                   
1434                # Add the exception form the last call to the Attribute
1435                # Authority if an error exists
1436                try:
1437                    errMsg += ": %s" % attributeRequestDenied
1438                except NameError:
1439                    pass
1440
1441                raise CredentialWalletAttributeRequestDenied(errMsg)
1442                                                   
1443               
1444            # Request Attribute Certificate from Attribute Authority
1445            try:
1446                attCert = self._getAttCert(extAttCert=extAttCert)               
1447                # Access granted
1448                return attCert
1449           
1450            except CredentialWalletAttributeRequestDenied, \
1451                   attributeRequestDenied:
1452                if not self.mapFromTrustedHosts and not self.rtnExtAttCertList:
1453                    log.debug("Creating a mapped certificate option is not "
1454                              "set - raising "
1455                              "CredentialWalletAttributeRequestDenied "
1456                              "exception saved from earlier")
1457                    raise attributeRequestDenied
1458
1459                if isinstance(extAttCertList, list):
1460                    # An list of attribute certificates from trusted hosts
1461                    # is present continue cycling through this until one of
1462                    # them is accepted and a mapped certificate can be derived
1463                    log.debug("AC request denied - but external ACs available "
1464                              "to try mapped AC request ...")
1465                    continue
1466                             
1467                #  Use the input required role and the AA's trusted host list
1468                # to identify attribute certificates from other hosts which
1469                # could be used to make a mapped certificate
1470                log.debug("Getting a list of trusted hosts for mapped AC "
1471                          "request ...")
1472                try:
1473                    trustedHostInfo = self._getAATrustedHostInfo(reqRole)
1474                   
1475                except NoMatchingRoleInTrustedHosts, e:
1476                    raise CredentialWalletAttributeRequestDenied(
1477                        'Can\'t get a mapped Attribute Certificate for '
1478                        'the "%s" role' % reqRole)
1479               
1480                except Exception, e:
1481                    raise CredentialWalletError("Getting trusted hosts: %s"%e)
1482
1483                if not trustedHostInfo:
1484                    raise CredentialWalletAttributeRequestDenied(
1485                        "Attribute Authority has no trusted hosts with "
1486                        "which to make a mapping")
1487
1488               
1489                # Initialise external certificate list here - if none are
1490                # found IndexError will be raised on the next iteration and
1491                # an access denied error will be raised
1492                extAttCertList = []
1493
1494                # Look for Attribute Certificates with matching issuer host
1495                # names
1496                log.debug("Checking wallet for ACs issued by one of the "
1497                          "trusted hosts...")
1498                for hostName in self._credentials:
1499
1500                    # Nb. Candidate certificates for mappings must have
1501                    # original provenance and contain at least one of the
1502                    # required roles
1503                    attCert = self._credentials[hostName]['attCert']
1504                   
1505                    if hostName in trustedHostInfo and attCert.isOriginal():                       
1506                        for role in attCert.roles:
1507                            if role in trustedHostInfo[hostName]['role']:                               
1508                                extAttCertList.append(attCert)
1509
1510
1511                if not extAttCertList:
1512                    log.debug("No wallet ACs matched any of the trusted "
1513                              "hosts.  - Try request for an AC from a "
1514                              "trusted host ...")
1515                   
1516                    # No certificates in the wallet matched the trusted host
1517                    # and required roles
1518                    #
1519                    # Try each host in turn in order to get a certificate with
1520                    # the required credentials in order to do a mapping
1521                    for host, info in trustedHostInfo.items():
1522                        try:
1523                            # Try request to trusted host
1524                            extAttCert = self._getAttCert(\
1525                                        attributeAuthorityURI=info['aaURI'])
1526
1527                            # Check the certificate contains at least one of
1528                            # the required roles
1529                            if [True for r in extAttCert.roles \
1530                                if r in info['role']]:
1531                               extAttCertList.append(extAttCert)
1532
1533                               # For efficiency, stop once obtained a valid
1534                               # cert - but may want complete list for user to
1535                               # choose from
1536                               #break
1537                               
1538                        except Exception, e:
1539                            # ignore any errors and continue
1540                            log.warning('AC request to trusted host "%s"' 
1541                                        ' resulted in: %s' % (info['aaURI'],e))
1542                           
1543                   
1544                if not extAttCertList:                       
1545                    raise CredentialWalletAttributeRequestDenied(
1546                        "No certificates are available with which to "
1547                        "make a mapping to the Attribute Authority")
1548
1549
1550                if not self.mapFromTrustedHosts:
1551                   
1552                    # Exit here returning the list of candidate certificates
1553                    # that could be used to make a mapped certificate
1554                    msg = "User is not registered with Attribute " + \
1555                          "Authority - retry using one of the returned " + \
1556                          "Attribute Certificates obtained from other " + \
1557                          "trusted hosts"
1558                         
1559                    raise CredentialWalletAttributeRequestDenied(msg,
1560                                            extAttCertList=extAttCertList,
1561                                            trustedHostInfo=trustedHostInfo)           
1562
1563
1564class CredentialRepositoryError(_CredentialWalletException):   
1565    """Exception handling for NDG Credential Repository class."""
1566
1567
1568class CredentialRepository:
1569    """CredentialWallet's abstract interface class to a Credential Repository.
1570    The Credential Repository is abstract store of user currently valid user
1571    credentials.  It enables retrieval of attribute certificates from a user's
1572    previous session(s)"""
1573       
1574    def __init__(self, propFilePath=None, dbPPhrase=None, **prop):
1575        """Initialise Credential Repository abstract base class.  Derive from
1576        this class to define Credentail Repository interface Credential
1577        Wallet
1578
1579        If the connection string or properties file is set a connection
1580        will be made
1581
1582        @type dbPPhrase: string
1583        @param dbPPhrase: pass-phrase to database if applicable
1584       
1585        @type propFilePath: string
1586        @param propFilePath: file path to a properties file.  This could
1587        contain configuration parameters for the repository e.g.  database
1588        connection parameters
1589       
1590        @type **prop: dict
1591        @param **prop: any other keywords required
1592        """
1593        raise NotImplementedError(
1594            self.__init__.__doc__.replace('\n       ',''))
1595
1596
1597    def addUser(self, userId, dn=None):
1598        """A new user to Credentials Repository
1599       
1600        @type userId: string
1601        @param userId: userId for new user
1602        @type dn: string
1603        @param dn: users Distinguished Name (optional)"""
1604        raise NotImplementedError(
1605            self.addUser.__doc__.replace('\n       ',''))
1606
1607                           
1608    def auditCredentials(self, userId=None, **attCertValidKeys):
1609        """Check the attribute certificates held in the repository and delete
1610        any that have expired
1611
1612        @type userId: basestring/list or tuple
1613        @param userId: audit credentials for the input user ID or list of IDs
1614        @type attCertValidKeys: dict
1615        @param **attCertValidKeys: keywords which set how to check the
1616        Attribute Certificate e.g. check validity time, XML signature, version
1617         etc.  Default is check validity time only - See AttCert class"""
1618        raise NotImplementedError(
1619            self.auditCredentials.__doc__.replace('\n       ',''))
1620
1621
1622    def getCredentials(self, userId):
1623        """Get the list of credentials for a given users DN
1624       
1625        @type userId: string
1626        @param userId: users userId, name or X.509 cert. distinguished name
1627        @rtype: list
1628        @return: list of Attribute Certificates"""
1629        raise NotImplementedError(
1630            self.getCredentials.__doc__.replace('\n       ',''))
1631
1632       
1633    def addCredentials(self, userId, attCertList):
1634        """Add new attribute certificates for a user.  The user must have
1635        been previously registered in the repository
1636
1637        @type userId: string
1638        @param userId: users userId, name or X.509 cert. distinguished name
1639        @type attCertList: list
1640        @param attCertList: list of attribute certificates"""
1641        raise NotImplementedError(
1642            self.addCredentials.__doc__.replace('\n       ',''))
1643
1644
1645
1646class NullCredentialRepository(CredentialRepository):
1647    """Implementation of Credential Repository interface with empty stubs. 
1648    Use this class in the case where no Credential Repository is required"""
1649   
1650    def __init__(self, propFilePath=None, dbPPhrase=None, **prop):
1651        pass
1652
1653    def addUser(self, userId):
1654        pass
1655                           
1656    def auditCredentials(self, **attCertValidKeys):
1657        pass
1658
1659    def getCredentials(self, userId):
1660        return []
1661       
1662    def addCredentials(self, userId, attCertList):
1663        pass
Note: See TracBrowser for help on using the repository browser.