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

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

Added a Policy Information Point to encapsulate subject attribute retrieval.

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