source: TI12-security/trunk/python/ndg.security.common/ndg/security/common/CredWallet.py @ 2178

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

* Change to AttCert? format and AA WS interface and AttAuthority? class for DEWS *

  • New userId element in Attribute Certificates + getAttCert call to an AA can specify a

user ID to be set in the returned AC.

python/ndg.security.server/ndg/security/server/AttAuthority/server-config.tac,
python/ndg.security.server/ndg/security/server/AttAuthority/AttAuthority_services_server.py,
python/ndg.security.common/ndg/security/common/AttAuthority/AttAuthority_services.py,
python/ndg.security.common/ndg/security/common/AttAuthority/AttAuthority_services_types.py,
python/www/html/attAuthority.wsdl:
added userId to WSDL interface.

python/ndg.security.server/ndg/security/server/AttAuthority/init.py:

  • added userId to getAttCert method.
  • changed refs to proxyCert to holderCert because cert meay not be a proxy
  • changed call to AttCert?.getRoles to AttCert?.roles
  • changed refs to userDN to userId

python/ndg.security.common/ndg/security/common/XMLSec.py: "ns1" is not needed for
reference C14N unsuppressed prefixes.

python/ndg.security.common/ndg/security/common/X509.py: made 'serialize' and 'deserialize'
aliases to serialise and deserialise methods respectively.

python/ndg.security.common/ndg/security/common/AttCert.py:

  • made AttCert? namespace a configurable class variable
  • changed all get/set attribute methods to private methods used by new-style class

properties.

  • updated setitem to use appropriate set* methods.
  • fix to setIssuerSerialNumber ref to 'issuerSerialNumber' instead of 'serialNumber'

python/ndg.security.common/ndg/security/common/AttAuthority/init.py: AA WS client -
added userId as keyword to getAttCert.

python/ndg.security.common/ndg/security/common/CredWallet.py: replace AttCert?.getRoles()
calls with AttCert?.roles property

python/ndg.security.test/ndg/security/test/AttAuthority/siteAUserRoles.py,
python/ndg.security.test/ndg/security/test/AttAuthority/siteBUserRoles.py:
swap refs to userDN with userId.

python/ndg.security.test/ndg/security/test/AttAuthority/AttAuthorityClientTest.py:
added new test for where an explicit userId is set.

python/ndg.security.test/ndg/security/test/AttAuthority/attAuthorityClientTest.cfg:
added userId parameter.

python/ndg.security.test/ndg/security/test/AttCert/AttCertTest.py: added tests for
property get calls.

python/ndg.security.test/ndg/security/test/MyProxy/Makefile: include call to MyProxy?
test to get proxy cert and private key.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1"""NDG Credentials Wallet
2
3NERC Data Grid Project
4
5@author P J Kershaw 30/11/05
6
7@copyright (C) 2007 CCLRC & NERC
8
9@license This software may be distributed under the terms of the Q Public
10License, version 1.0 or later.
11"""
12
13reposID = '$Id$'
14
15
16# Temporary store of certificates for use with CredWallet getAttCert()
17import tempfile
18
19# Check Attribute Certificate validity times
20from datetime import datetime
21from datetime import timedelta
22
23
24# Access Attribute Authority's web service using ZSI - allow pass if not
25# loaded since it's possible to make AttAuthority instance locally without
26# using the WS
27aaImportError = True
28try:
29    # AttAuthority client package resides with CredWallet module in
30    # ndg.security.common
31    from ndg.security.common.AttAuthority import AttAuthorityClient, \
32        AttAuthorityClientError, AttributeRequestDenied
33    aaImportError = False
34   
35except ImportError:
36    pass
37
38# Likewise - may not want to use WS and use AttAuthority locally in which case
39# no need to import it
40try:
41    from ndg.security.server.AttAuthority import AttAuthority, \
42        AttAuthorityError, AttAuthorityAccessDenied
43    aaImportError = False
44except:
45    pass
46
47if aaImportError:
48    raise ImportError, \
49        "Either AttAuthority or AttAuthorityClient classes must be " + \
50        "present to allow interoperation with Attribute Authorities"
51
52# Authentication X.509 Certificate
53from ndg.security.common.X509 import *
54from M2Crypto import X509, BIO, RSA
55
56# Authorisation - attribute certificate
57from ndg.security.common.AttCert import *
58
59
60#_____________________________________________________________________________
61class CredWalletError(Exception):   
62    """Exception handling for NDG CredentialWallet class."""
63    pass
64
65
66#_____________________________________________________________________________
67class CredWalletAttributeRequestDenied(Exception):   
68    """Handling exception where CredWallet is denied authorisation by an
69    Attribute Authority."""
70   
71    def __init__(self, msg=None, extAttCertList=[], trustedHostInfo={}):
72        """Raise exception for authorisation denied with option to give
73        caller hint to certificates that could used to try to obtain a
74        mapped certificate
75       
76        @type msg: string
77        @keyword msg: error message
78       
79        @type extAttCertList: list
80        @keyword extAttCertList: list of candidate Attribute Certificates that
81        could be used to try to get a mapped certificate from the target
82        Attribute Authority
83       
84        @type trustedHostInfo: dict
85        @keyword trustedHostInfo: dictionary indexed by host name giving
86        details of Attribute Authority URI and roles for trusted hosts"""
87
88        self.__msg = msg
89        self.__trustedHostInfo = trustedHostInfo
90       
91        # Prevent None type setting
92        if extAttCertList is None:
93            extAttCertList = []
94           
95        self.__extAttCertList = extAttCertList
96
97       
98    def __str__(self):
99        return self.__msg
100
101
102    def __getMsg(self):
103        """Get message text"""
104        return self.__msg
105
106    msg = property(fget=__getMsg, doc="Error message text")
107
108
109    def __getTrustedHostInfo(self):
110        """Get message text"""
111        return self.__msg
112
113    trustedHostInfo = property(fget=__getTrustedHostInfo, 
114                               doc="URI and roles details for trusted hosts")
115   
116   
117    def __getExtAttCertList(self):
118        """Return list of candidate Attribute Certificates that could be used
119        to try to get a mapped certificate from the target Attribute Authority
120        """
121        return self.__extAttCertList
122
123
124    extAttCertList = property(fget=__getExtAttCertList,
125                              doc="list of candidate Attribute " + \
126                              "Certificates that could be used " + \
127                              "to try to get a mapped certificate " + \
128                              "from the target Attribute Authority")
129
130
131           
132#_____________________________________________________________________________
133class _MetaCredWallet(type):
134    """Enable CredWallet to have read only class variables e.g.
135   
136    print CredWallet.accessDenied
137   
138    ... is allowed but,
139   
140    CredWallet.accessDenied = None
141   
142    ... raises - AttributeError: can't set attribute"""
143   
144    def __getAccessDenied(cls):
145        '''accessDenied get method'''
146        return False
147   
148    accessDenied = property(fget=__getAccessDenied)
149   
150    def __getAccessGranted(cls):
151        '''accessGranted get method'''
152        return True
153   
154    accessGranted = property(fget=__getAccessGranted)
155
156#_____________________________________________________________________________       
157# CredWallet is a 'new-style' class inheriting from "object" and making use
158# of new Get/Set methods for hiding of attributes
159class CredWallet(object):
160    """Volatile store of user credentials associated with a user session"""
161
162    __metaclass__ = _MetaCredWallet
163   
164    def __init__(self,
165                 proxyCert,
166                 proxyPriKey,
167                 userCert,
168                 aaURI=None,
169                 aaPropFilePath=None,
170                 caCertFilePath=None,
171                 credRepos=None,
172                 mapFromTrustedHosts=False,
173                 rtnExtAttCertList=True,
174                 attCertRefreshElapse=7200):
175        """Create store of user credentials for their current session
176
177        @type proxyCert: string / M2Crypto.X509.X509 /
178        ndg.security.common.X509.X509Cert
179        @param proxyCert: X.509 proxy certificate for user
180       
181        @type proxyPriKey: string / M2Crypto.RSA.RSA
182        @param proxyPriKey: private key for proxy
183       
184        @type userCert: string / ndg.security.common.X509.X509Cert
185        @param userCert: X.509 cert for issuer of proxy
186       
187        @type aaURI: string
188        @keyword aaURI: URI of Attribute Authority to make requests to. 
189        Setting this ALSO creates an AttAuthorityClient instance
190        self.__aaClnt.  - See aaURI property for details.
191       
192        @type aaPropFilePath: string
193        @keyword aaPropFilePath: properties file path for an Attribute
194        Authority to make requests to.  Setting this ALSO creates an
195        AttAuthority instance self.__aa running locally.   - See aa property
196        for details.  aaURI takes precedence over this keyword i.e. if an
197        aaURI has been set, then calls are made to the AA web service at this
198        location rather to any self.__aa running locally.
199       
200        @type caCertFilePath: string
201        @keyword caCertFilePath: Certificate Authority's certificate - used in
202        validation of signed Attribute Certificates.  If not set here, it must
203        be input in call to getAttCert.
204               
205        @type credRepos: instance of CredRepos derived class
206        @keyword credRepos: Credential Repository instance.  If not set,
207        defaults to NullCredRepos type - see class below...
208       
209        @type mapFromTrustedHosts: bool
210        @keyword mapFromTrustedHosts sets behaviour for getAttCert().  If
211        set True and authorisation fails with the given Attribute Authority,
212        attempt to get authorisation using Attribute Certificates issued by
213        other trusted AAs.
214       
215        @type rtnExtAttCertList: bool
216        @keyword rtnExtAttCertList: behaviour for getAttCert().  If True, and
217        authorisation fails with the given Attribute Authority, return a list
218        of Attribute Certificates from other trusted AAs which could be used
219        to obtain a mapped Attribute Certificate on a subsequent authorisation
220        attempt
221       
222        @type attCertRefreshElapse: float / int
223        @keyword attCertRefreshElapse: used by getAttCert to determine
224        whether to replace an existing AC in the cache with a fresh one.  If
225        the existing one has less than attCertRefreshElapse time in seconds
226        left before expiry then replace it."""
227
228
229        self.attCertRefreshElapse = attCertRefreshElapse
230       
231        self.__setProxyCert(proxyCert)
232        self.__setProxyPriKey(proxyPriKey)
233        self.__setUserCert(userCert)
234       
235        self.__setAAuri(aaURI)
236        self.__setCAcertFilePath(caCertFilePath)
237               
238        self.__credRepos = credRepos or NullCredRepos()
239       
240        # Set behaviour for authorisation requests
241        self.__mapFromTrustedHosts = mapFromTrustedHosts
242        self.__rtnExtAttCertList = rtnExtAttCertList
243       
244       
245        # Get the distinguished name from the proxy certificate
246        self.__dn = self.__proxyCert.dn.serialise()
247       
248       
249        # Credentials are stored as a dictionary one element per attribute
250        # certicate held and indexed by certificate issuer name
251        self.__credentials = {}
252
253
254        # Make a connection to the Credentials Repository
255        if self.__credRepos:
256            if not isinstance(self.__credRepos, CredRepos):
257                raise CredWalletError, \
258                    "Input Credentials Repository instance must be of a " + \
259                    "class derived from \"CredRepos\""
260   
261       
262            # Check for valid attribute certificates for the user
263            try:
264                self.__credRepos.auditCredentials(dn=self.__dn)
265                userCred = self.__credRepos.getCredentials(self.__dn)
266   
267            except Exception, e:
268                raise CredWalletError, \
269                "Error updating wallet with credentials from repository: " + \
270                    str(e)
271   
272   
273            # Update wallet with attribute certificates stored in the
274            # repository.  Store ID and certificate instantiated as an AttCert
275            # type
276            try:
277                for cred in userCred:
278                   
279                    attCert = AttCertParse(cred.attCert)
280                    issuerName = attCert['issuerName']
281                   
282                    self.__credentials[issuerName] = \
283                                             {'id':cred.id, 'attCert':attCert}   
284            except Exception, e:
285                try:
286                    raise CredWalletError, \
287                            "Error parsing Attribute Certificate ID '" + \
288                                    cred.id + "' retrieved from the " + \
289                                    "Credentials Repository: %s" % str(e)               
290                except:
291                    raise CredWalletError, "Error parsing Attribute " + \
292                                          "Certificate retrieved from " + \
293                                          "the Credentials Repository: %s:" \
294                                          % str(e)
295           
296           
297            # Filter out expired or otherwise invalid certificates
298            self.audit()
299       
300
301    def __str__(self):
302        return "<Credential Wallet instance>"
303
304       
305    #_________________________________________________________________________
306    def __setCert(self, cert):
307        """filter and convert input cert to signing verifying cert set
308        property methods.  For signingCert, set to None if it is not to be
309        included in the SOAP header.  For verifyingCert, set to None if this
310        cert can be expected to be retrieved from the SOAP header of the
311        message to be verified
312       
313        @type: ndg.security.common.X509.X509Cert / M2Crypto.X509.X509 /
314        string or None
315        @param cert: X.509 certificate. 
316       
317        @rtype ndg.security.common.X509.X509Cert
318        @return X.509 certificate object"""
319       
320        if cert is None or isinstance(cert, X509Cert):
321            # ndg.security.common.X509.X509Cert type / None
322            return cert
323           
324        elif isinstance(cert, X509.X509):
325            # M2Crypto.X509.X509 type
326            return X509Cert(m2CryptoX509=cert)
327           
328        elif isinstance(cert, basestring):
329            return X509CertParse(cert)
330       
331        else:
332            raise AttributeError, "X.509 Cert. must be type: " + \
333                "ndg.security.common.X509.X509Cert, M2Crypto.X509.X509 or " +\
334                "a base64 encoded string"
335
336
337    #_________________________________________________________________________
338    def __setProxyCert(self, proxyCert):
339        "Set property method for X.509 proxy cert."
340        self.__proxyCert = self.__setCert(proxyCert)
341       
342
343    def __getProxyCert(self):
344        """Get proxy cert X509Cert instance"""
345        return self.__proxyCert
346
347
348    # Proxy Cert instance is read-only - to set it, set proxyCertTxt
349    proxyCert = property(fget=__getProxyCert,
350                         fset=__setProxyCert,
351                         doc="X.509 proxy certificate instance")
352
353
354    #_________________________________________________________________________
355    def __setUserCert(self, userCert):
356        "Set property method for X.509 user cert."
357        self.__userCert = self.__setCert(userCert)
358       
359
360    def __getUserCert(self):
361        """Get user cert X509Cert instance"""
362        return self.__userCert
363
364
365    # User Cert instance is read-only - to set it, set proxyCertTxt
366    userCert = property(fget=__getUserCert,
367                         fset=__setUserCert,
368                         doc="X.509 user certificate instance")
369     
370 
371    #_________________________________________________________________________
372    def __setProxyPriKey(self, proxyPriKey):
373        """Set method for client private key
374       
375        Nb. if input is a string, proxyPriKeyPwd will need to be set if
376        the key is password protected.
377       
378        @type proxyPriKey: M2Crypto.RSA.RSA / string
379        @param proxyPriKey: private key used to sign message"""
380       
381        if isinstance(proxyPriKey, basestring):
382            self.__proxyPriKey = RSA.load_key_string(proxyPriKey,
383                                             callback=lambda *ar, **kw: None)
384
385        elif isinstance(proxyPriKey, RSA.RSA):
386            self.__proxyPriKey = proxyPriKey
387                   
388        else:
389            raise AttributeError, "Proxy private key must be a valid " + \
390                                  "M2Crypto.RSA.RSA type or a string"
391               
392    proxyPriKey = property(fset=__setProxyPriKey,
393                             doc="Private key used to sign outbound message")
394   
395   
396    #_________________________________________________________________________
397    # Credentials are read-only
398    def __getCredentials(self):
399        return self.__credentials
400
401    # Publish attribute
402    credentials = property(fget=__getCredentials,
403                           doc="List of Attribute Certificates")   
404
405
406    #_________________________________________________________________________
407    def __setCAcertFilePath(self, caCertFilePath):
408       
409        if not isinstance(caCertFilePath, basestring) and \
410           caCertFilePath is not None:
411            raise CredWalletError, \
412                        "Input CA Certificate file path is not a valid string"
413               
414        self.__caCertFilePath = caCertFilePath
415       
416       
417    caCertFilePath = property(fset=__setCAcertFilePath,
418                              doc="CA Certificate  - use to check AC XML Sig")
419
420
421    #_________________________________________________________________________
422    def __setAAuri(self, aaURI):
423        """Set property method for Attribute Authority Web Service URI to
424        connect to.  This method ALSO SETS UP THE CLIENT INTERFACE
425       
426        @type aaURI: string
427        @param aaURI: Attribute Authority Web Service URI.  Set to None to
428        initialise.  Set to a URI to instantiate a new AA client"""
429        if aaURI is None:
430            self.__aaClnt = None
431            return
432       
433        self.__aaClnt = AttAuthorityClient(uri=aaURI, 
434                                           signingCert=self.__proxyCert,
435                                           signingPriKey=self.__proxyPriKey)
436           
437    aaURI = property(fset=__setAAuri,
438             doc="AA URI - setting also sets up AttAuthorityClient instance!")
439
440
441    #_________________________________________________________________________
442    def __getAAclnt(self):
443        """Get property method for Attribute Authority Web Service client
444        instance.  Use aaURI propert to set up aaClnt
445       
446        @type aaClnt: AttAuthorityClient
447        @param aaClnt: Attribute Authority Web Service client instance"""
448        return self.__aaClnt
449           
450    aaClnt = property(fget=__getAAclnt, doc="AA web service client instance")
451
452
453    #_________________________________________________________________________
454    def __setAApropFilePath(self, aaPropFilePath):
455        """Set property method for the properties file of a local
456        Attribute Authority.  This method ALSO SETS UP THE LOCAL Attribute
457        Authority object to retrieve ACs from.  the property aaURI takes
458        precedence: if an aaURI is set then it assumed that an Attribute
459        Authority will be connected to via a web service call
460       
461        @type aaPropFilePath: string
462        @param aaPropFilePath: Attribute Authority properties file.  Setting
463        this instantiates a new AA locally"""
464        if aaPropFilePath is None:
465            self.__aa = None
466            return
467
468        # Make a new attribute authority instance
469        self.__aa = AttAuthority(propFilePath=aaPropFilePath)
470
471    aaPropFilePath = property(fset=__setAApropFilePath,
472    doc="AA properties file path - setting this also sets up an AA locally!")
473
474
475    #_________________________________________________________________________
476    def __getAA(self):
477        """Get property method for Attribute Authority Web Service client
478        instance.  Use aaURI propert to set up aaClnt
479       
480        @type aaClnt: AttAuthorityClient
481        @param aaClnt: Attribute Authority Web Service client instance"""
482        return self.__aaClnt
483           
484    aa = property(fget=__getAA, doc="Attribute Authority instance")
485
486
487    #_________________________________________________________________________
488    def isValid(self, **x509CertKeys):
489        """Check wallet's proxy cert.  If expired return False
490       
491        @type **x509CertKeys: dict
492        @param **x509CertKeys: keywords applying to
493        ndg.security.common.X509.X509Cert.isValid method"""
494        try:
495            return self.__proxyCert.isValidTime(**x509CertKeys)
496
497        except Exception, e:
498            raise CredWalletError, "Credential Wallet: %s" % e
499
500   
501    #_________________________________________________________________________
502    def addCredential(self, attCert, bUpdateCredRepos=True):
503        """Add a new attribute certificate to the list of credentials held.
504
505        @type attCert:
506        @param attCert: new attribute Certificate to be added
507        @type bUpdateCredRepos: bool
508        @keyword bUpdateCredRepos: if set to True, and a repository exists it
509        will be updated with the new credentials also
510       
511        @rtype: bool
512        @return: True if certificate was added otherwise False.  - If an
513        existing certificate from the same issuer has a later expiry it will
514        take precence and the new input certificate is ignored."""
515
516        # Check input
517        if not isinstance(attCert, AttCert):
518            raise CredWalletError,\
519                "Attribute Certificate must be an AttCert type object"
520
521        # Check certificate validity
522        try:
523            attCert.isValid(raiseExcep=True)
524           
525        except AttCertError, e:
526            raise CredWalletError, "Adding Credential: %s" % e
527       
528
529        # Check to see if there is an existing Attribute Certificate held
530        # that was issued by the same host.  If so, compare the expiry time.
531        # The one with the latest expiry will be retained and the other
532        # ingored
533        bUpdateCred = True
534        issuerName = attCert['issuerName']
535       
536        if issuerName in self.__credentials:
537            # There is an existing certificate held with the same issuing
538            # host name as the new certificate
539            attCertOld = self.__credentials[issuerName]['attCert']
540
541            # Get expiry times in datetime format to allow comparison
542            dtAttCertOldNotAfter = attCertOld.getValidityNotAfter(\
543                                                            asDatetime=True)
544            dtAttCertNotAfter = attCert.getValidityNotAfter(asDatetime=True)
545
546            # If the new certificate has an earlier expiry time then ignore it
547            bUpdateCred = dtAttCertNotAfter > dtAttCertOldNotAfter
548
549               
550        if bUpdateCred:
551            # Update: Nb. -1 ID value flags item as new.  Items read in
552            # from the CredentialRepository during creation of the wallet will
553            # have +ve IDs previously allocated by the database
554            self.__credentials[issuerName] = {'id': -1, 'attCert': attCert}
555
556            # Update the Credentials Repository - the permanent store of user
557            # authorisation credentials.  This allows credentials for previous
558            # sessions to be re-instated
559            if self.__credRepos and bUpdateCredRepos:
560                self.updateCredRepos()
561
562        # Flag to caller to indicate whether the input certificate was added
563        # to the credentials or an exsiting certificate from the same issuer
564        # took precedence
565        return bUpdateCred
566           
567
568    #_________________________________________________________________________
569    def audit(self):
570        """Check the credentials held in the wallet removing any that have
571        expired or are otherwise invalid."""
572
573        # Nb. No signature check is carried out.  To do a check, access is
574        # needed to the cert of the CA that issued the Attribute Authority's
575        # cert
576        #
577        # P J Kershaw 12/09/05
578        for key, val in self.__credentials.items():
579            if not val['attCert'].isValid(chkSig=False):
580                del self.__credentials[key]
581
582
583    #_________________________________________________________________________           
584    def updateCredRepos(self, auditCred=True):
585        """Copy over non-persistent credentials held by wallet into the
586        perminent repository.
587       
588        @type auditCred: bool
589        @keyword auditCred: filter existing credentials in the repository
590        removing invalid ones"""
591
592        if not self.__credRepos:
593            raise CredWalletError, \
594                  "No Credential Repository has been created for this wallet"
595                           
596        # Filter out invalid certs unless auditCred flag is explicitly set to
597        # false
598        if auditCred: self.audit()
599
600        # Update the database - only add new entries i.e. with an ID of -1
601        attCertList = [i['attCert'] for i in self.__credentials.values() \
602                        if i['id'] == -1]
603
604        self.__credRepos.addCredentials(self.__dn, attCertList)
605
606
607    #_________________________________________________________________________                   
608    def __getAttCert(self, extAttCert=None):
609       
610        """Wrapper to Attribute Authority attribute certificate request.  See
611        getAttCert for the classes' public interface.
612
613        To call the Attribute Authority as a Web Service, specify a URI
614        otherwise set the properties file path.
615       
616        If successful, a new attribute certificate is issued to the user
617        and added into the wallet
618
619        @type extAttCert: ndg.security.common.AttCert.AttCert
620        @keyword extAttCert: an existing Attribute Certificate which can
621        be used to making a mapping should the user not be registered with the
622        Attribute Authority"""
623         
624        if self.__aaClnt is not None:
625            try:
626                attCert = self.__aaClnt.getAttCert(self.__userCert.toString(), 
627                                                   userAttCert=extAttCert)               
628            except AttributeRequestDenied, e:
629                raise CredWalletAttributeRequestDenied, str(e)
630           
631            except Exception, e:
632                raise CredWalletError, str(e)
633                           
634        elif aaPropFilePath is not None:
635
636            # Call local based Attribute Authority with settings from the
637            # configuration file aaPropFilePath
638
639            if not isinstance(aaPropFilePath, basestring):
640                raise CredWalletError, "Attribute Authority Configuration " +\
641                                       "file path must be a valid string"
642
643            try:
644                # Request a new attribute certificate from the Attribute
645                # Authority
646                attCert = self.__aa.getAttCert(userCert=self.__proxyCert,
647                                               userAttCert=extAttCert)
648               
649            except AttAuthorityAccessDenied, e:
650                raise CredWalletAttributeRequestDenied, str(e)           
651            except Exception, e:
652                raise CredWalletError,"Requesting attribute certificate: %s"%e
653
654        else:
655            raise CredWalletError, "Error requesting authorisation: " + \
656                                   "a URI or Attribute Authority " + \
657                                   "configuration file must be specified"
658       
659
660        # Update attribute Certificate instance with CA's certificate ready
661        # for signature check in addCredential()
662        if self.__caCertFilePath is None:
663            raise CredWalletError, "No CA certificate has been set"
664       
665        attCert.certFilePathList = self.__caCertFilePath
666
667       
668        # Add credential into wallet
669        #
670        # Nb. if the certificates signature is invalid, it will be rejected
671        self.addCredential(attCert)
672
673
674        return attCert
675
676
677    #_________________________________________________________________________
678    def getAATrustedHostInfo(self, 
679                             userRole=None,
680                             aaPropFilePath=None,
681                             aaURI=None):
682        """Wrapper to Attribute Authority getTrustedHostInfo
683       
684        getAATrustedHostInfo([userRole=r, ][aaPropFilePath=f|aaURI=u])
685                   
686        @type userRole: string
687        @keyword userRole: get hosts which have a mapping to this role
688       
689        @type aaURI: string
690        @keyword aaURI: to call as a web service, specify the URI for the
691        Attribute Authority.
692       
693        @type aaPropFilePath: string
694        @keyword aaPropFilePath: Altenrative to aaURI - to run on the local
695        machine, specify the local Attribute Authority configuration file.
696        """
697       
698        if aaURI:
699            self.__setAAuri(aaURI)
700        elif aaPropFilePath:
701            self.__setAAPropFilePath
702
703           
704        if self.__aaClnt is not None:
705            # Call Attribute Authority WS
706            try:
707                return self.__aaClnt.getTrustedHostInfo(role=userRole)               
708                           
709            except Exception, e:
710                raise CredWalletError, \
711                            "Requesting trusted host information: %s" % str(e)               
712
713        elif self.__aa is not None:
714
715            # Call local based Attribute Authority with settings from the
716            # configuration file aaPropFilePath
717            try:
718                # Request a new attribute certificate from the Attribute
719                # Authority
720                return self.__aa.getTrustedHostInfo(role=userRole)
721               
722            except Exception, e:
723                raise CredWalletError, "Requesting trusted host info: %s" % e
724
725        else:
726            raise CredWalletError, "Error requesting trusted hosts info: " + \
727                                   "a URI or Attribute Authority " + \
728                                   "configuration file must be specified"
729
730
731    #_________________________________________________________________________
732    def getAttCert(self,
733                   reqRole=None,
734                   aaPropFilePath=None,
735                   aaURI=None,
736                   mapFromTrustedHosts=None,
737                   rtnExtAttCertList=None,
738                   extAttCertList=None,
739                   extTrustedHostList=None,
740                   refreshAttCert=False,
741                   attCertRefreshElapse=None):
742       
743        """For a given role, get an Attribute Certificate from an Attribute
744        Authority using a user's proxy certificate.  If this fails try to make
745        a mapped Attribute Certificate by using a certificate from another
746        host which has a trust relationship to the Attribute Authority in
747        question.
748
749        getAttCert([reqRole=r, ][aaPropFilePath=f|aaURI=u,]
750                   [mapFromTrustedHosts=m, ]
751                   [rtnExtAttCertList=e, ][extAttCertList=el, ]
752                   [extTrustedHostList=et, ][refreshAttCert=ra])
753                 
754        The procedure is:
755
756        1) Try authorisation using proxy certificate
757        2) If the Attribute Authority (AA) doesn't recognise the certificate,
758        find out any other hosts which have a trust relationship to the AA.
759        3) Look for Attribute Certificates held in the wallet corresponding
760        to these hosts.
761        4) If no Attribute Certificates are available, call the relevant
762        hosts' AAs to get certificates
763        5) Finally, use these new certificates to try to obtain a mapped
764        certificate from the original AA
765        6) If this fails access is denied     
766                   
767        @type reqRole: string
768        @keyword reqRole: the required role to get access for
769       
770        @type aaURI: string
771        @keyword aaURI: to call as a web service, specify the URI for the
772        Attribute Authority.
773       
774        @type aaPropFilePath: string
775        @keyword aaPropFilePath: Altenrative to aaURI - to run on the local
776        machine, specify the local Attribute Authority configuration file.
777                               
778        @type mapFromTrustedHosts: bool / None     
779        @keyword mapFromTrustedHosts: if request fails via the user's proxy
780        ID, then it is possible to get a mapped certificate by using
781        certificates from other AA's.  Set this flag to True, to allow this
782        second stage of generating a mapped certificate from the certificate
783        stored in the wallet credentials.
784
785        If set to False, it is possible to return the list of certificates
786        available for mapping and then choose which one or ones to use for
787        mapping by re-calling getAttCert with extAttCertList set to these
788        certificates.
789       
790        Defaults to None in which case self.__mapFromTrustedHosts is not
791        altered
792
793        The list is returned via CredWalletAttributeRequestDenied exception
794        If no value is set, the default value held in
795        self.__mapFromTrustedHosts is used
796
797        @type rtnExtAttCertList: bool / None
798        @keyword rtnExtAttCertList: If authorisation fails, make a list of
799        candidate certificates from other Attribute Authorities which the user
800        could use to retry and get a mapped certificate.
801                               
802        If mapFromTrustedHosts is set True this flags value is overriden and
803        effectively set to True.
804
805        If no value is set, the default value held in self.__rtnExtAttCertList
806        is used.
807                               
808        The list is returned via a CredWalletAttributeRequestDenied exception
809        object.
810                               
811        @type extAttCertList: list
812        @keyword extAttCertList: Attribute Certificate or list of certificates
813        from other Attribute Authorities.  These can be used to get a mapped
814        certificate if access fails based on the user's proxy certificate
815        credentials.  They are tried out in turn until access is granted so
816        the order of the list decides the order in which they will be tried
817
818        @type extTrustedHostList:
819        @keyword extTrustedHostList: same as extAttCertList keyword, but
820        instead of providing Attribute Certificates, give a list of Attribute
821        Authority hosts.  These will be matched up to Attribute Certificates
822        held in the wallet.  Matching certificates will then be used to try to
823        get a mapped Attribute Certificate.
824       
825        @type refreshAttCert: bool
826        @keyword refreshAttCert: if set to True, the attribute request
827        will go ahead even if the wallet already contains an Attribute
828        Certificate from the target Attribute Authority.  The existing AC in
829        the wallet will be replaced by the new one obtained from this call.
830                               
831        If set to False, this method will check to see if an AC issued by the
832        target AA already exists in the wallet.  If so, it will return this AC
833        to the caller without proceeding to make a call to the AA.
834       
835        @type attCertRefreshElapse: float / int
836        @keyword attCertRefreshElapse: determine whether to replace an
837        existing AC in the cache with a fresh one.  If the existing one has
838        less than attCertRefreshElapse time in seconds left before expiry then
839        replace it."""
840
841        if aaURI:
842            self.__setAAuri(aaURI)
843        elif aaPropFilePath:
844            self.__setAAPropFilePath
845           
846        if not refreshAttCert and self.__credentials:
847            # Refresh flag is not set so it's OK to check for any existing
848            # Attribute Certificate in the wallet whose issuerName match the
849            # target AA's name
850           
851            # Find out the site ID for the target AA by calling AA's host
852            # info WS method
853            try:
854                hostInfo = self.__aaClnt.getHostInfo()
855                aaName = hostInfo.keys()[0]
856            except Exception, e:
857                raise CredWalletError, "Getting host info: %s" % e
858           
859            # Look in the wallet for an AC with the same issuer name
860            if aaName in self.__credentials:
861                # Existing Attribute Certificate found in wallet - Check that
862                # it will be valid for at least the next 2 hours
863                if attCertRefreshElapse is not None:
864                    self.attCertRefreshElapse = attCertRefreshElapse
865                   
866                dtNow = datetime.utcnow() + \
867                        timedelta(seconds=self.attCertRefreshElapse)
868               
869                attCert = self.__credentials[aaName]['attCert']
870                if attCert.isValidTime(dtNow=dtNow):                                   
871                    return attCert
872           
873           
874        # Check for settings from input, if not set use previous settings
875        # made
876        if mapFromTrustedHosts is not None:
877            self.__mapFromTrustedHosts = mapFromTrustedHosts
878
879        if rtnExtAttCertList is not None:
880            self.__rtnExtAttCertList = rtnExtAttCertList
881
882
883        # Check for list of external trusted hosts (other trusted NDG data
884        # centres)
885        if extTrustedHostList:
886            if not self.__mapFromTrustedHosts:
887                raise CredWalletError, "A list of trusted hosts has been " + \
888                "input but mapping from trusted hosts is set to disallowed"
889           
890            if isinstance(extTrustedHostList, basestring):
891                extTrustedHostList = [extTrustedHostList]
892
893            # Nb. Any extAttCertList is overriden by extTrustedHostList being
894            # set
895            extAttCertList = [self.__credentials[hostName]['attCert'] \
896                              for hostName in extTrustedHostList \
897                              if hostName in self.__credentials]
898
899        # Set an empty list to trigger an AttributeError by initialising it to
900        # None
901        if extAttCertList == []:
902            extAttCertList = None
903           
904        # Repeat authorisation attempts until succeed or means are exhausted
905        while True:
906           
907            # Check for candidate certificates for mapping
908            try:
909                # If list is set get the next cert
910                extAttCert = extAttCertList.pop()
911
912            except AttributeError:
913               
914                # No List set - attempt authorisation without
915                # using mapping from trusted hosts
916                extAttCert = None
917                               
918            except IndexError:
919               
920                # List has been emptied without attribute request succeeding -
921                # give up
922                errMsg = "Attempting to obtained a mapped certificate: " + \
923                    "no external attribute certificates are available"
924                   
925                # Add the exception form the last call to the Attribute
926                # Authority if an error exists
927                try:
928                    errMsg += ": %s" % attributeRequestDenied
929                except NameError:
930                    pass
931
932                raise CredWalletAttributeRequestDenied, errMsg
933                                                   
934               
935            # Request Attribute Certificate from Attribute Authority
936            try:
937                attCert = self.__getAttCert(extAttCert=extAttCert)               
938                # Access granted
939                return attCert
940           
941            except CredWalletAttributeRequestDenied, attributeRequestDenied:
942
943                # If a required role was set then it's possible to go
944                # to get certificates with mapped roles from trusted hosts
945                # Shouldn't need to set a role - setting a role makes it more
946                # efficient but it's not essential
947                #
948                # P J Kershaw 29/03/06
949#                if not reqRole:
950#                    raise CredWalletAttributeRequestDenied(\
951#                        "No user role was input in order to map to " + \
952#                        "a role in a trusted host")
953
954                if not mapFromTrustedHosts and not rtnExtAttCertList:
955                    # Creating a mapped certificate is not allowed - raise
956                    # authorisation denied exception saved from earlier
957                    raise attributeRequestDenied
958
959
960                if isinstance(extAttCertList, list):
961                    # An list of attribute certificates from trusted hosts
962                    # is present continue cycling through this until one of
963                    # them is accepted and a mapped certificate can be derived
964                    continue
965                             
966                #  Use the input required role and the AA's trusted host list
967                # to identify attribute certificates from other hosts which
968                # could be used to make a mapped certificate
969                try:
970                    trustedHostInfo = self.getAATrustedHostInfo(reqRole,
971                                            aaPropFilePath=aaPropFilePath)
972                except Exception, e:
973                    raise CredWalletError, "Getting trusted hosts: %s" % e
974
975                if not trustedHostInfo:
976                    raise CredWalletAttributeRequestDenied, \
977                        "Attribute Authority has no trusted hosts with " + \
978                        "which to make a mapping"
979
980               
981                # Initialise external certificate list here - if none are
982                # found IndexError will be raised on the next iteration and
983                # an access denied error will be raised
984                extAttCertList = []
985
986                # Look for Attribute Certificates with matching issuer host
987                # names
988                for hostName in self.__credentials:
989
990                    # Nb. Candidate certificates for mappings must have
991                    # original provenance and contain at least one of the
992                    # required roles
993                    attCert = self.__credentials[hostName]['attCert']
994                   
995                    if hostName in trustedHostInfo and attCert.isOriginal():                       
996                        for role in attCert.roles:
997                            if role in trustedHostInfo[hostName]['role']:                               
998                                extAttCertList.append(attCert)
999
1000
1001                if not extAttCertList:
1002                    # No certificates in the wallet matched the trusted host
1003                    # and required roles
1004                    #
1005                    # Try each host in turn in order to get a certificate with
1006                    # the required credentials in order to do a mapping
1007                    for host, info in trustedHostInfo.items():
1008                        try:
1009                            # Try request to trusted host
1010                            self.aaURI = info['aaURI']
1011                            extAttCert=self.__getAttCert()
1012
1013                            # Check the certificate contains at least one of
1014                            # the required roles
1015                            if [True for r in extAttCert.roles \
1016                                if r in info['role']]:
1017                               extAttCertList.append(extAttCert)
1018
1019                               # For efficiency, stop once obtained a valid
1020                               # cert - but may want complete list for user to
1021                               # choose from
1022                               #break
1023                               
1024                        except Exception, e:
1025                            pass    # ignore any errors and continue
1026                   
1027                if not extAttCertList:                       
1028                    raise CredWalletAttributeRequestDenied, \
1029                        "No certificates are available with which to " + \
1030                        "make a mapping to the Attribute Authority"
1031
1032
1033                if not mapFromTrustedHosts:
1034                   
1035                    # Exit here returning the list of candidate certificates
1036                    # that could be used to make a mapped certificate
1037                    msg = "User is not registered with Attribute " + \
1038                          "Authority - retry using one of the returned " + \
1039                          "Attribute Certificates obtained from other " + \
1040                          "trusted hosts"
1041                         
1042                    raise CredWalletAttributeRequestDenied(msg=msg,
1043                                            extAttCertList=extAttCertList,
1044                                            trustedHostInfo=trustedHostInfo)
1045               
1046            except Exception, authorisationError:
1047                # Authorisation request raised an error other than access
1048                # denied
1049                raise authorisationError
1050           
1051             
1052       
1053#_____________________________________________________________________________
1054class CredReposError(Exception):   
1055    """Exception handling for NDG Credential Repository class."""
1056    pass 
1057
1058
1059#_____________________________________________________________________________
1060class CredRepos:
1061    """CredWallet's interface class to a Credential Repository"""
1062   
1063
1064    def __init__(self, propFilePath=None, dbPPhrase=None, **prop):
1065        """Initialise Credential Repository abstract base class derive from
1066        this class to define Credentail Repository interface Credential
1067        Wallet
1068
1069        If the connection string or properties file is set a connection
1070        will be made
1071
1072        @type dbPPhrase: string
1073        @keyword dbPPhrase: pass-phrase to database if applicable
1074       
1075        @type propFilePath: string
1076        @keyword propFilePath: file path to a properties file.  This could
1077        contain configuration parameters for the repository e.g.  database
1078        connection parameters
1079       
1080        @type **prop: dict
1081        @param **prop: any other keywords required
1082        """
1083        raise NotImplementedError, \
1084            self.__init__.__doc__.replace('\n       ','')
1085
1086
1087    def addUser(self, username, dn):
1088        """A new user to Credentials Repository
1089       
1090        @type username: string
1091        @param username: username for new user
1092        @type dn: string
1093        @param dn: users Distinguished Name"""
1094        raise NotImplementedError, \
1095            self.addUser.__doc__.replace('\n       ','')
1096
1097                           
1098    def auditCredentials(self, **attCertValidKeys):
1099        """Check the attribute certificates held in the repository and delete
1100        any that have expired
1101
1102        @type attCertValidKeys: dict
1103        @param **attCertValidKeys: keywords which set how to check the
1104        Attribute Certificate e.g. check validity time, XML signature, version
1105         etc.  Default is check validity time only - See AttCert class"""
1106        raise NotImplementedError, \
1107            self.auditCredentials.__doc__.replace('\n       ','')
1108
1109
1110    def getCredentials(self, dn):
1111        """Get the list of credentials for a given users DN
1112       
1113        @type dn: string
1114        @param dn: users distinguished name
1115        @rtype: list
1116        @return: list of Attribute Certificates"""
1117        raise NotImplementedError, \
1118            self.getCredentials.__doc__.replace('\n       ','')
1119
1120       
1121    def addCredentials(self, dn, attCertList):
1122        """Add new attribute certificates for a user.  The user must have
1123        been previously registered in the repository
1124
1125        @type dn: string
1126        @param dn: users Distinguished name
1127        @type attCertList: list
1128        @param attCertList: list of attribute certificates"""
1129        raise NotImplementedError, \
1130            self.addCredentials.__doc__.replace('\n       ','')
1131
1132
1133
1134#_____________________________________________________________________________
1135class NullCredRepos(CredRepos):
1136    """Implementation of Credential Repository interface with empty stubs. 
1137    This allows for where no Credential Repository is required"""
1138   
1139    def __init__(self, propFilePath=None, dbPPhrase=None, **prop):
1140        pass
1141
1142    def addUser(self, userName, dn):
1143        pass
1144                           
1145    def auditCredentials(self, **attCertValidKeys):
1146        pass
1147
1148    def getCredentials(self, dn):
1149        return []
1150       
1151    def addCredentials(self, dn, attCertList):
1152        pass
Note: See TracBrowser for help on using the repository browser.