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

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