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

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

Updated CredentialWallet? unit tests to include a call to a locally instantiated Attribute Authority

  • 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 configuration file must be "
1073                                        "specified")
1074       
1075        try:
1076            # Request a new attribute certificate from the Attribute
1077            # Authority
1078            attCert = aaInterface.getAttCert(userId=userId,
1079                                             userAttCert=extAttCert)
1080           
1081            log.info('Granted Attribute Certificate from issuer DN = "%s"'%
1082                     attCert.issuerDN)
1083           
1084        except (AttributeAuthorityAccessDenied, AttributeRequestDenied), e:
1085            # AttributeAuthorityAccessDenied is raised if
1086            # aaInterface is a local AA instance and
1087            # AttributeRequestDenied is raised for a client to a remote AA
1088            # service
1089            raise CredentialWalletAttributeRequestDenied(str(e))
1090                   
1091        except Exception, e:
1092            raise CredentialWalletError("Requesting attribute certificate: %s"%
1093                                        e)
1094
1095        # Update attribute Certificate instance with CA's certificate ready
1096        # for signature check in addCredential()
1097        if self._caCertFilePathList is None:
1098            raise CredentialWalletError("No CA certificate has been set")
1099       
1100        attCert.certFilePathList = self._caCertFilePathList
1101
1102       
1103        # Add credential into wallet
1104        #
1105        # Nb. if the certificates signature is invalid, it will be rejected
1106        log.debug("Adding credentials into wallet...")
1107        self.addCredential(attCert)
1108       
1109        return attCert
1110
1111
1112    def _getAAHostInfo(self, 
1113                       attributeAuthority=None,
1114                       attributeAuthorityURI=None):
1115        """Wrapper to Attribute Authority getHostInfo
1116       
1117        _getAAHostInfo([attributeAuthority=f|attributeAuthorityURI=u])
1118                   
1119        @type userRole: string
1120        @param userRole: get hosts which have a mapping to this role
1121       
1122        @type attributeAuthorityURI: string
1123        @param attributeAuthorityURI: to call as a web service, specify the URI for the
1124        Attribute Authority.
1125       
1126        @type attributeAuthority: string
1127        @param attributeAuthority: Alternative to attributeAuthorityURI - to
1128        run on the local machine, specify the local Attribute Authority
1129        instance.
1130        """
1131
1132        if attributeAuthority is None:
1133            attributeAuthority = self.attributeAuthority
1134           
1135        if attributeAuthorityURI is None:
1136            attributeAuthorityURI = self.attributeAuthorityURI
1137       
1138        log.debug('CredentialWallet._getAAHostInfo for service: "%s" ...' % 
1139                  attributeAuthorityURI or attributeAuthority)
1140           
1141        # Set a client alias according to whether the Attribute Authority is
1142        # being called locally or asa remote service
1143        if attributeAuthorityURI is not None:
1144            # Call Remote Service at given URI
1145            attributeAuthorityClnt = self._createAttributeAuthorityClnt(
1146                                                    attributeAuthorityURI)
1147
1148        elif attributeAuthority is not None:
1149            # Call local based Attribute Authority with settings from the
1150            # configuration file attributeAuthority
1151            attributeAuthorityClnt = attributeAuthority
1152           
1153        else:
1154            raise CredentialWalletError("Error requesting trusted hosts info: " 
1155                                        "a URI or Attribute Authority " 
1156                                        "configuration file must be specified")
1157           
1158        try:
1159            # Request a new attribute certificate from the Attribute
1160            # Authority
1161            return attributeAuthorityClnt.getHostInfo()
1162           
1163        except Exception, e:
1164            log.error("Requesting host info: %s" % e)
1165            raise
1166
1167
1168    def _getAATrustedHostInfo(self, 
1169                              userRole=None,
1170                              attributeAuthority=None,
1171                              attributeAuthorityURI=None):
1172        """Wrapper to Attribute Authority getTrustedHostInfo
1173       
1174        _getAATrustedHostInfo([userRole=r, ][attributeAuthority=f|
1175                              attributeAuthorityURI=u])
1176                   
1177        @type userRole: string
1178        @param userRole: get hosts which have a mapping to this role
1179       
1180        @type attributeAuthorityURI: string
1181        @param attributeAuthorityURI: to call as a web service, specify the URI for the
1182        Attribute Authority.
1183       
1184        @type attributeAuthority: string
1185        @param attributeAuthority: Alternative to attributeAuthorityURI - to
1186        run on the local machine, specify the local Attribute Authority
1187        instance.
1188        """
1189
1190        if attributeAuthority is None:
1191            attributeAuthority = self.attributeAuthority
1192           
1193        if attributeAuthorityURI is None:
1194            attributeAuthorityURI = self.attributeAuthorityURI
1195       
1196        log.debug('CredentialWallet._getAATrustedHostInfo for role "%s" and '
1197                  'service: "%s" ...' % (userRole, 
1198                                attributeAuthorityURI or attributeAuthority))
1199           
1200        # Set a client alias according to whether the Attribute Authority is
1201        # being called locally or asa remote service
1202        if attributeAuthorityURI is not None:
1203            # Call Remote Service at given URI
1204            attributeAuthorityClnt = self._createAttributeAuthorityClnt(
1205                                                    attributeAuthorityURI)
1206
1207        elif attributeAuthority is not None:
1208            # Call local based Attribute Authority with settings from the
1209            # configuration file attributeAuthority
1210            attributeAuthorityClnt = attributeAuthority
1211           
1212        else:
1213            raise CredentialWalletError("Error requesting trusted hosts info: " 
1214                                        "a URI or Attribute Authority " 
1215                                        "configuration file must be specified")
1216           
1217        try:
1218            # Request a new attribute certificate from the Attribute
1219            # Authority
1220            return attributeAuthorityClnt.getTrustedHostInfo(role=userRole)
1221           
1222        except Exception, e:
1223            log.error("Requesting trusted host info: %s" % e)
1224            raise
1225
1226
1227    def getAttCert(self,
1228                   reqRole=None,
1229                   attributeAuthority=None,
1230                   attributeAuthorityURI=None,
1231                   mapFromTrustedHosts=None,
1232                   rtnExtAttCertList=None,
1233                   extAttCertList=None,
1234                   extTrustedHostList=None,
1235                   refreshAttCert=False,
1236                   attCertRefreshElapse=None):
1237       
1238        """Get an Attribute Certificate from an Attribute Authority.  If this
1239        fails try to make a mapped Attribute Certificate by using a certificate
1240        from another host which has a trust relationship to the Attribute
1241        Authority in question.
1242
1243        getAttCert([reqRole=r, ][attributeAuthority=a|attributeAuthorityURI=u,]
1244                   [mapFromTrustedHosts=m, ]
1245                   [rtnExtAttCertList=e, ][extAttCertList=el, ]
1246                   [extTrustedHostList=et, ][refreshAttCert=ra])
1247                 
1248        The procedure is:
1249
1250        1) Try attribute request using user certificate
1251        2) If the Attribute Authority (AA) doesn't recognise the certificate,
1252        find out any other hosts which have a trust relationship to the AA.
1253        3) Look for Attribute Certificates held in the wallet corresponding
1254        to these hosts.
1255        4) If no Attribute Certificates are available, call the relevant
1256        hosts' AAs to get certificates
1257        5) Finally, use these new certificates to try to obtain a mapped
1258        certificate from the original AA
1259        6) If this fails access is denied     
1260                   
1261        @type reqRole: string
1262        @param reqRole: the required role to get access for
1263       
1264        @type attributeAuthorityURI: string
1265        @param attributeAuthorityURI: to call as a web service, specify the URI for the
1266        Attribute Authority.
1267       
1268        @type attributeAuthority: string
1269        @param attributeAuthority: Altenrative to attributeAuthorityURI - to
1270        run on the local machine, specify a local Attribute Authority
1271        instance.
1272                               
1273        @type mapFromTrustedHosts: bool / None     
1274        @param mapFromTrustedHosts: if request fails via the user's cert
1275        ID, then it is possible to get a mapped certificate by using
1276        certificates from other AA's.  Set this flag to True, to allow this
1277        second stage of generating a mapped certificate from the certificate
1278        stored in the wallet credentials.
1279
1280        If set to False, it is possible to return the list of certificates
1281        available for mapping and then choose which one or ones to use for
1282        mapping by re-calling getAttCert with extAttCertList set to these
1283        certificates.
1284       
1285        Defaults to None in which case self._mapFromTrustedHosts is not
1286        altered
1287
1288        The list is returned via CredentialWalletAttributeRequestDenied
1289        exception.  If no value is set, the default value held in
1290        self.mapFromTrustedHosts is used
1291
1292        @type rtnExtAttCertList: bool / None
1293        @param rtnExtAttCertList: If request fails, make a list of
1294        candidate certificates from other Attribute Authorities which the user
1295        could use to retry and get a mapped certificate.
1296                               
1297        If mapFromTrustedHosts is set True this flags value is overriden and
1298        effectively set to True.
1299
1300        If no value is set, the default value held in self._rtnExtAttCertList
1301        is used.
1302                               
1303        The list is returned via a CredentialWalletAttributeRequestDenied
1304        exception object.
1305                               
1306        @type extAttCertList: list
1307        @param extAttCertList: Attribute Certificate or list of certificates
1308        from other Attribute Authorities.  These can be used to get a mapped
1309        certificate if access fails based on the user's certificate
1310        credentials.  They are tried out in turn until access is granted so
1311        the order of the list decides the order in which they will be tried
1312
1313        @type extTrustedHostList:
1314        @param extTrustedHostList: same as extAttCertList keyword, but
1315        instead of providing Attribute Certificates, give a list of Attribute
1316        Authority hosts.  These will be matched up to Attribute Certificates
1317        held in the wallet.  Matching certificates will then be used to try to
1318        get a mapped Attribute Certificate.
1319       
1320        @type refreshAttCert: bool
1321        @param refreshAttCert: if set to True, the attribute request
1322        will go ahead even if the wallet already contains an Attribute
1323        Certificate from the target Attribute Authority.  The existing AC in
1324        the wallet will be replaced by the new one obtained from this call.
1325                               
1326        If set to False, this method will check to see if an AC issued by the
1327        target AA already exists in the wallet.  If so, it will return this AC
1328        to the caller without proceeding to make a call to the AA.
1329       
1330        @type attCertRefreshElapse: float / int
1331        @param attCertRefreshElapse: determine whether to replace an
1332        existing AC in the cache with a fresh one.  If the existing one has
1333        less than attCertRefreshElapse time in seconds left before expiry then
1334        replace it.
1335       
1336        @rtype: ndg.security.common.AttCert.AttCert
1337        @return: Attribute Certificate retrieved from Attribute Authority"""
1338       
1339        log.debug("CredentialWallet.getAttCert ...")
1340       
1341        # Both these assignments are calling set property methods implicitly!
1342        if attributeAuthorityURI:
1343            self.attributeAuthorityURI = attributeAuthorityURI
1344           
1345        if attributeAuthority:
1346            self.attributeAuthority = attributeAuthority
1347           
1348        if not refreshAttCert and self._credentials:
1349            # Refresh flag is not set so it's OK to check for any existing
1350            # Attribute Certificate in the wallet whose issuerName match the
1351            # target AA's name
1352           
1353            # Find out the site ID for the target AA by calling AA's host
1354            # info WS method
1355            log.debug("CredentialWallet.getAttCert - check AA site ID ...")
1356            try:
1357                hostInfo = self._getAAHostInfo()
1358                aaName = hostInfo.keys()[0]
1359            except Exception, e:
1360                raise CredentialWalletError("Getting host info: %s" % e)
1361           
1362            # Look in the wallet for an AC with the same issuer name
1363            if aaName in self._credentials:
1364                # Existing Attribute Certificate found in wallet - Check that
1365                # it will be valid for at least the next 2 hours
1366                if attCertRefreshElapse is not None:
1367                    self.attCertRefreshElapse = attCertRefreshElapse
1368                   
1369                dtNow = datetime.utcnow() + \
1370                        timedelta(seconds=self.attCertRefreshElapse)
1371               
1372                attCert = self._credentials[aaName]['attCert']
1373                if attCert.isValidTime(dtNow=dtNow):
1374                    log.info("Retrieved an existing %s AC from the wallet" % 
1375                             aaName)
1376                    return attCert
1377           
1378           
1379        # Check for settings from input, if not set use previous settings
1380        # made
1381        if mapFromTrustedHosts is not None:
1382            self.mapFromTrustedHosts = mapFromTrustedHosts
1383
1384        if rtnExtAttCertList is not None:
1385            self.rtnExtAttCertList = rtnExtAttCertList
1386
1387
1388        # Check for list of external trusted hosts (other trusted NDG data
1389        # centres)
1390        if extTrustedHostList:
1391            log.info("Checking for ACs in wallet matching list of trusted "
1392                     "hosts set: %s" % extTrustedHostList)
1393           
1394            if not self.mapFromTrustedHosts:
1395                raise CredentialWalletError("A list of trusted hosts has been " 
1396                                      "input but mapping from trusted hosts "
1397                                      "is set to disallowed")
1398           
1399            if isinstance(extTrustedHostList, basestring):
1400                extTrustedHostList = [extTrustedHostList]
1401
1402            # Nb. Any extAttCertList is overriden by extTrustedHostList being
1403            # set
1404            extAttCertList = [self._credentials[hostName]['attCert'] \
1405                              for hostName in extTrustedHostList \
1406                              if hostName in self._credentials]
1407
1408        # Set an empty list to trigger an AttributeError by initialising it to
1409        # None
1410        if extAttCertList == []:
1411            extAttCertList = None
1412           
1413        # Repeat authorisation attempts until succeed or means are exhausted
1414        while True:
1415           
1416            # Check for candidate certificates for mapping
1417            try:
1418                # If list is set get the next cert
1419                extAttCert = extAttCertList.pop()
1420
1421            except AttributeError:
1422                log.debug("No external Attribute Certificates - trying "
1423                          "request without mapping...")
1424                # No List set - attempt request without
1425                # using mapping from trusted hosts
1426                extAttCert = None
1427                           
1428            except IndexError:
1429               
1430                # List has been emptied without attribute request succeeding -
1431                # give up
1432                errMsg = "Attempting to obtained a mapped certificate: " + \
1433                         "no external attribute certificates are available"
1434                   
1435                # Add the exception form the last call to the Attribute
1436                # Authority if an error exists
1437                try:
1438                    errMsg += ": %s" % attributeRequestDenied
1439                except NameError:
1440                    pass
1441
1442                raise CredentialWalletAttributeRequestDenied(errMsg)
1443                                                   
1444               
1445            # Request Attribute Certificate from Attribute Authority
1446            try:
1447                attCert = self._getAttCert(extAttCert=extAttCert)               
1448                # Access granted
1449                return attCert
1450           
1451            except CredentialWalletAttributeRequestDenied, \
1452                   attributeRequestDenied:
1453                if not self.mapFromTrustedHosts and not self.rtnExtAttCertList:
1454                    log.debug("Creating a mapped certificate option is not "
1455                              "set - raising "
1456                              "CredentialWalletAttributeRequestDenied "
1457                              "exception saved from earlier")
1458                    raise attributeRequestDenied
1459
1460                if isinstance(extAttCertList, list):
1461                    # An list of attribute certificates from trusted hosts
1462                    # is present continue cycling through this until one of
1463                    # them is accepted and a mapped certificate can be derived
1464                    log.debug("AC request denied - but external ACs available "
1465                              "to try mapped AC request ...")
1466                    continue
1467                             
1468                #  Use the input required role and the AA's trusted host list
1469                # to identify attribute certificates from other hosts which
1470                # could be used to make a mapped certificate
1471                log.debug("Getting a list of trusted hosts for mapped AC "
1472                          "request ...")
1473                try:
1474                    trustedHostInfo = self._getAATrustedHostInfo(reqRole)
1475                   
1476                except NoMatchingRoleInTrustedHosts, e:
1477                    raise CredentialWalletAttributeRequestDenied(
1478                        'Can\'t get a mapped Attribute Certificate for '
1479                        'the "%s" role' % reqRole)
1480               
1481                except Exception, e:
1482                    raise CredentialWalletError("Getting trusted hosts: %s"%e)
1483
1484                if not trustedHostInfo:
1485                    raise CredentialWalletAttributeRequestDenied(
1486                        "Attribute Authority has no trusted hosts with "
1487                        "which to make a mapping")
1488
1489               
1490                # Initialise external certificate list here - if none are
1491                # found IndexError will be raised on the next iteration and
1492                # an access denied error will be raised
1493                extAttCertList = []
1494
1495                # Look for Attribute Certificates with matching issuer host
1496                # names
1497                log.debug("Checking wallet for ACs issued by one of the "
1498                          "trusted hosts...")
1499                for hostName in self._credentials:
1500
1501                    # Nb. Candidate certificates for mappings must have
1502                    # original provenance and contain at least one of the
1503                    # required roles
1504                    attCert = self._credentials[hostName]['attCert']
1505                   
1506                    if hostName in trustedHostInfo and attCert.isOriginal():                       
1507                        for role in attCert.roles:
1508                            if role in trustedHostInfo[hostName]['role']:                               
1509                                extAttCertList.append(attCert)
1510
1511
1512                if not extAttCertList:
1513                    log.debug("No wallet ACs matched any of the trusted "
1514                              "hosts.  - Try request for an AC from a "
1515                              "trusted host ...")
1516                   
1517                    # No certificates in the wallet matched the trusted host
1518                    # and required roles
1519                    #
1520                    # Try each host in turn in order to get a certificate with
1521                    # the required credentials in order to do a mapping
1522                    for host, info in trustedHostInfo.items():
1523                        try:
1524                            # Try request to trusted host
1525                            extAttCert = self._getAttCert(\
1526                                        attributeAuthorityURI=info['aaURI'])
1527
1528                            # Check the certificate contains at least one of
1529                            # the required roles
1530                            if [True for r in extAttCert.roles \
1531                                if r in info['role']]:
1532                               extAttCertList.append(extAttCert)
1533
1534                               # For efficiency, stop once obtained a valid
1535                               # cert - but may want complete list for user to
1536                               # choose from
1537                               #break
1538                               
1539                        except Exception, e:
1540                            # ignore any errors and continue
1541                            log.warning('AC request to trusted host "%s"' 
1542                                        ' resulted in: %s' % (info['aaURI'],e))
1543                           
1544                   
1545                if not extAttCertList:                       
1546                    raise CredentialWalletAttributeRequestDenied(
1547                        "No certificates are available with which to "
1548                        "make a mapping to the Attribute Authority")
1549
1550
1551                if not self.mapFromTrustedHosts:
1552                   
1553                    # Exit here returning the list of candidate certificates
1554                    # that could be used to make a mapped certificate
1555                    msg = "User is not registered with Attribute " + \
1556                          "Authority - retry using one of the returned " + \
1557                          "Attribute Certificates obtained from other " + \
1558                          "trusted hosts"
1559                         
1560                    raise CredentialWalletAttributeRequestDenied(msg,
1561                                            extAttCertList=extAttCertList,
1562                                            trustedHostInfo=trustedHostInfo)           
1563
1564
1565class CredentialRepositoryError(_CredentialWalletException):   
1566    """Exception handling for NDG Credential Repository class."""
1567
1568
1569class CredentialRepository:
1570    """CredentialWallet's abstract interface class to a Credential Repository.
1571    The Credential Repository is abstract store of user currently valid user
1572    credentials.  It enables retrieval of attribute certificates from a user's
1573    previous session(s)"""
1574       
1575    def __init__(self, propFilePath=None, dbPPhrase=None, **prop):
1576        """Initialise Credential Repository abstract base class.  Derive from
1577        this class to define Credentail Repository interface Credential
1578        Wallet
1579
1580        If the connection string or properties file is set a connection
1581        will be made
1582
1583        @type dbPPhrase: string
1584        @param dbPPhrase: pass-phrase to database if applicable
1585       
1586        @type propFilePath: string
1587        @param propFilePath: file path to a properties file.  This could
1588        contain configuration parameters for the repository e.g.  database
1589        connection parameters
1590       
1591        @type **prop: dict
1592        @param **prop: any other keywords required
1593        """
1594        raise NotImplementedError(
1595            self.__init__.__doc__.replace('\n       ',''))
1596
1597
1598    def addUser(self, userId, dn=None):
1599        """A new user to Credentials Repository
1600       
1601        @type userId: string
1602        @param userId: userId for new user
1603        @type dn: string
1604        @param dn: users Distinguished Name (optional)"""
1605        raise NotImplementedError(
1606            self.addUser.__doc__.replace('\n       ',''))
1607
1608                           
1609    def auditCredentials(self, userId=None, **attCertValidKeys):
1610        """Check the attribute certificates held in the repository and delete
1611        any that have expired
1612
1613        @type userId: basestring/list or tuple
1614        @param userId: audit credentials for the input user ID or list of IDs
1615        @type attCertValidKeys: dict
1616        @param **attCertValidKeys: keywords which set how to check the
1617        Attribute Certificate e.g. check validity time, XML signature, version
1618         etc.  Default is check validity time only - See AttCert class"""
1619        raise NotImplementedError(
1620            self.auditCredentials.__doc__.replace('\n       ',''))
1621
1622
1623    def getCredentials(self, userId):
1624        """Get the list of credentials for a given users DN
1625       
1626        @type userId: string
1627        @param userId: users userId, name or X.509 cert. distinguished name
1628        @rtype: list
1629        @return: list of Attribute Certificates"""
1630        raise NotImplementedError(
1631            self.getCredentials.__doc__.replace('\n       ',''))
1632
1633       
1634    def addCredentials(self, userId, attCertList):
1635        """Add new attribute certificates for a user.  The user must have
1636        been previously registered in the repository
1637
1638        @type userId: string
1639        @param userId: users userId, name or X.509 cert. distinguished name
1640        @type attCertList: list
1641        @param attCertList: list of attribute certificates"""
1642        raise NotImplementedError(
1643            self.addCredentials.__doc__.replace('\n       ',''))
1644
1645
1646
1647class NullCredentialRepository(CredentialRepository):
1648    """Implementation of Credential Repository interface with empty stubs. 
1649    Use this class in the case where no Credential Repository is required"""
1650   
1651    def __init__(self, propFilePath=None, dbPPhrase=None, **prop):
1652        pass
1653
1654    def addUser(self, userId):
1655        pass
1656                           
1657    def auditCredentials(self, **attCertValidKeys):
1658        pass
1659
1660    def getCredentials(self, userId):
1661        return []
1662       
1663    def addCredentials(self, userId, attCertList):
1664        pass
Note: See TracBrowser for help on using the repository browser.