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

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

Updated imports for new location of signature handlers in ndg.security.common.wssecurity.signaturehandler. Re-ran Java Attribute Authority client unit tests against updated Attribute Authority WSGI based service.

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