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

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

ndg.security.server/ndg/security/server/conf/sessionMgrProperties.xml:

  • don't comment out hostname instead include by default

ndg.security.server/ndg/security/server/SessionMgr/init.py:

  • fixed comment typo

ndg.security.server/ndg/security/server/MyProxy.py:

to prevent setting of OpenSSL config file without the required file name and
directory path.

ndg.security.test/ndg/security/test/AttCert/attCertTest.cfg,
ndg.security.test/ndg/security/test/AttCert/AttCertTest.py:

  • fixed unit tests for AC signature verification. certFilePathList can now

be set to include CA certs. to verify the X.509 cert. used in the signature

ndg.security.test/ndg/security/test/SessionMgr/SessionMgrClientTest.py:

  • fix: extAttCertList is no longer returned in getAttCert calls to SM client.

ndg.security.test/ndg/security/test/SessionMgr/sessionMgrClientTest.cfg:

  • tests with services on glue

ndg.security.common/ndg/security/common/XMLSec.py:

  • fixed verifyEnvelopedSignature so that it is now possible to verify the

X.509 cert. in the signature against it's issuing CA cert.

ndg.security.common/ndg/security/common/SessionMgr/init.py:

  • modified getAttCert call so that extAttCertList is no longer passed back in

the returned tuple but is instead included as an attribute of the
AttributeRequestDenied? exception type.

  • updated pydoc for getAttCert method

ndg.security.common/ndg/security/common/AttAuthority/init.py:

  • typo fix - doesn't affect execution

ndg.security.common/ndg/security/common/CredWallet.py:

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