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

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

Implemented a caching scheme for Attribute Certificates in the security filter deployed on the application middleware stack:

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