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

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

python/ndg.security.server/ndg/security/server/AttAuthority/server-config.tac:

python/www/html/attAuthority.wsdl,
python/ndg.security.server/ndg/security/server/AttAuthority/AttAuthority_services_server.py,
python/ndg.security.common/ndg/security/common/AttAuthority/AttAuthority_services_types.py,
python/ndg.security.common/ndg/security/common/AttAuthority/AttAuthority_services.py:
Include request denied message in getAttCertResponse.

python/ndg.security.server/ndg/security/server/AttAuthority/init.py:
fix to AttAuthorityAccessDenied? doc message.

python/ndg.security.server/ndg/security/server/SessionMgr/server-config.tac:
Exlpicitly convert AttCert? in response to string type.

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

  • make explicit imports from ndg.security.common.CredWallet?
  • make X509CertParse import
  • updated exception handling for getAttCert call to CredWallet?.

python/www/html/sessionMgr.wsdl,
python/ndg.security.server/ndg/security/server/SessionMgr/SessionMgr_services_server.py,
python/ndg.security.common/ndg/security/common/SessionMgr/SessionMgr_services.py,
python/ndg.security.common/ndg/security/common/SessionMgr/SessionMgr_services_types.py:
Remove statusCode from getAttCertResponse - not needed.

python/ndg.security.test/ndg/security/test/AttAuthority/AttAuthorityClientTest.py:
minor updates to getAttCert tests.

python/ndg.security.test/ndg/security/test/MyProxy/myProxyClientTest.cfg:
fix to test1Store settings

python/ndg.security.test/ndg/security/test/MyProxy/Makefile:
makefile copies proxy obtained from MyProxy? ready for use in AttAuthority? client tests.

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

  • add AttributeRequestDenied? import from SessionMgr?.
  • fix test4CookieDisconnect signing PKI settings
  • revised output tuple for getAttCert calls.
  • Added test6aCookieGetAttCertRefused to demonstrate attribute request denied exception
  • test3ProxyCertConnect signature verification failing at server!

python/ndg.security.test/ndg/security/test/SessionMgr/sessionMgrClientTest.cfg:
added more getAttCert test params.

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

python/ndg.security.common/ndg/security/common/wsSecurity.py:
comment out all print statements - only 'print decryptedData' affected in decrypt method
of EncryptionHandler?. This is not in use.

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

  • Added AttributeRequestDenied? exception for handling getAttCert calls.
  • msg now included in output tuple for getAttCert call.

python/ndg.security.common/ndg/security/common/AttCert.py:
Override XMLSecDoc parent class toString and str calls so that output is returned even
if the signature DOM object has not been initialised.

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

  • 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 AttAuthority import AttAuthorityClient, AttAuthorityClientError, \
32        AttributeRequestDenied
33    aaImportError = False
34   
35except ImportError:
36    pass
37
38# Likewise - may want to use WS and not 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, \
633                        "Attribute Certificate request denied: %s" % str(e)
634                           
635        elif aaPropFilePath is not None:
636
637            # Call local based Attribute Authority with settings from the
638            # configuration file aaPropFilePath
639
640            if not isinstance(aaPropFilePath, basestring):
641                raise CredWalletError, "Attribute Authority Configuration " +\
642                                       "file path must be a valid string"
643
644            try:
645                # Request a new attribute certificate from the Attribute
646                # Authority
647                attCert = self.__aa.getAttCert(userCert=self.__proxyCert,
648                                               userAttCert=extAttCert)
649               
650            except AttAuthorityAccessDenied, e:
651                raise CredWalletAttributeRequestDenied, \
652                                "Attribute Certificate request denied: %s" % e           
653            except Exception, e:
654                raise CredWalletError,"Requesting attribute certificate: %s"%e
655
656        else:
657            raise CredWalletError, "Error requesting authorisation: " + \
658                                   "a URI or Attribute Authority " + \
659                                   "configuration file must be specified"
660       
661
662        # Update attribute Certificate instance with CA's certificate ready
663        # for signature check in addCredential()
664        if self.__caCertFilePath is None:
665            raise CredWalletError, "No CA certificate has been set"
666       
667        attCert.certFilePathList = self.__caCertFilePath
668
669       
670        # Add credential into wallet
671        #
672        # Nb. if the certificates signature is invalid, it will be rejected
673        self.addCredential(attCert)
674
675
676        return attCert
677
678
679    #_________________________________________________________________________
680    def getAATrustedHostInfo(self, 
681                             userRole=None,
682                             aaPropFilePath=None,
683                             aaURI=None):
684        """Wrapper to Attribute Authority getTrustedHostInfo
685       
686        getAATrustedHostInfo([userRole=r, ][aaPropFilePath=f|aaURI=u])
687                   
688        @type userRole: string
689        @keyword userRole: get hosts which have a mapping to this role
690       
691        @type aaURI: string
692        @keyword aaURI: to call as a web service, specify the URI for the
693        Attribute Authority.
694       
695        @type aaPropFilePath: string
696        @keyword aaPropFilePath: Altenrative to aaURI - to run on the local
697        machine, specify the local Attribute Authority configuration file.
698        """
699       
700        if aaURI:
701            self.__setAAuri(aaURI)
702        elif aaPropFilePath:
703            self.__setAAPropFilePath
704
705           
706        if self.__aaClnt is not None:
707            # Call Attribute Authority WS
708            try:
709                return self.__aaClnt.getTrustedHostInfo(role=userRole)               
710                           
711            except Exception, e:
712                raise CredWalletError, \
713                            "Requesting trusted host information: %s" % str(e)               
714
715        elif self.__aa is not None:
716
717            # Call local based Attribute Authority with settings from the
718            # configuration file aaPropFilePath
719            try:
720                # Request a new attribute certificate from the Attribute
721                # Authority
722                return self.__aa.getTrustedHostInfo(role=userRole)
723               
724            except Exception, e:
725                raise CredWalletError, "Requesting trusted host info: %s" % e
726
727        else:
728            raise CredWalletError, "Error requesting trusted hosts info: " + \
729                                   "a URI or Attribute Authority " + \
730                                   "configuration file must be specified"
731
732
733    #_________________________________________________________________________
734    def getAttCert(self,
735                   reqRole=None,
736                   aaPropFilePath=None,
737                   aaURI=None,
738                   mapFromTrustedHosts=None,
739                   rtnExtAttCertList=None,
740                   extAttCertList=None,
741                   extTrustedHostList=None,
742                   refreshAttCert=False,
743                   attCertRefreshElapse=None):
744       
745        """For a given role, get an Attribute Certificate from an Attribute
746        Authority using a user's proxy certificate.  If this fails try to make
747        a mapped Attribute Certificate by using a certificate from another
748        host which has a trust relationship to the Attribute Authority in
749        question.
750
751        getAttCert([reqRole=r, ][aaPropFilePath=f|aaURI=u,]
752                   [mapFromTrustedHosts=m, ]
753                   [rtnExtAttCertList=e, ][extAttCertList=el, ]
754                   [extTrustedHostList=et, ][refreshAttCert=ra])
755                 
756        The procedure is:
757
758        1) Try authorisation using proxy certificate
759        2) If the Attribute Authority (AA) doesn't recognise the certificate,
760        find out any other hosts which have a trust relationship to the AA.
761        3) Look for Attribute Certificates held in the wallet corresponding
762        to these hosts.
763        4) If no Attribute Certificates are available, call the relevant
764        hosts' AAs to get certificates
765        5) Finally, use these new certificates to try to obtain a mapped
766        certificate from the original AA
767        6) If this fails access is denied     
768                   
769        @type reqRole: string
770        @keyword reqRole: the required role to get access for
771       
772        @type aaURI: string
773        @keyword aaURI: to call as a web service, specify the URI for the
774        Attribute Authority.
775       
776        @type aaPropFilePath: string
777        @keyword aaPropFilePath: Altenrative to aaURI - to run on the local
778        machine, specify the local Attribute Authority configuration file.
779                               
780        @type mapFromTrustedHosts: bool / None     
781        @keyword mapFromTrustedHosts: if request fails via the user's proxy
782        ID, then it is possible to get a mapped certificate by using
783        certificates from other AA's.  Set this flag to True, to allow this
784        second stage of generating a mapped certificate from the certificate
785        stored in the wallet credentials.
786
787        If set to False, it is possible to return the list of certificates
788        available for mapping and then choose which one or ones to use for
789        mapping by re-calling getAttCert with extAttCertList set to these
790        certificates.
791       
792        Defaults to None in which case self.__mapFromTrustedHosts is not
793        altered
794
795        The list is returned via CredWalletAttributeRequestDenied exception
796        If no value is set, the default value held in
797        self.__mapFromTrustedHosts is used
798
799        @type rtnExtAttCertList: bool / None
800        @keyword rtnExtAttCertList: If authorisation fails, make a list of
801        candidate certificates from other Attribute Authorities which the user
802        could use to retry and get a mapped certificate.
803                               
804        If mapFromTrustedHosts is set True this flags value is overriden and
805        effectively set to True.
806
807        If no value is set, the default value held in self.__rtnExtAttCertList
808        is used.
809                               
810        The list is returned via a CredWalletAttributeRequestDenied exception
811        object.
812                               
813        @type extAttCertList: list
814        @keyword extAttCertList: Attribute Certificate or list of certificates
815        from other Attribute Authorities.  These can be used to get a mapped
816        certificate if access fails based on the user's proxy certificate
817        credentials.  They are tried out in turn until access is granted so
818        the order of the list decides the order in which they will be tried
819
820        @type extTrustedHostList:
821        @keyword extTrustedHostList: same as extAttCertList keyword, but
822        instead of providing Attribute Certificates, give a list of Attribute
823        Authority hosts.  These will be matched up to Attribute Certificates
824        held in the wallet.  Matching certificates will then be used to try to
825        get a mapped Attribute Certificate.
826       
827        @type refreshAttCert: bool
828        @keyword refreshAttCert: if set to True, the attribute request
829        will go ahead even if the wallet already contains an Attribute
830        Certificate from the target Attribute Authority.  The existing AC in
831        the wallet will be replaced by the new one obtained from this call.
832                               
833        If set to False, this method will check to see if an AC issued by the
834        target AA already exists in the wallet.  If so, it will return this AC
835        to the caller without proceeding to make a call to the AA.
836       
837        @type attCertRefreshElapse: float / int
838        @keyword attCertRefreshElapse: determine whether to replace an
839        existing AC in the cache with a fresh one.  If the existing one has
840        less than attCertRefreshElapse time in seconds left before expiry then
841        replace it."""
842
843        if aaURI:
844            self.__setAAuri(aaURI)
845        elif aaPropFilePath:
846            self.__setAAPropFilePath
847           
848        if not refreshAttCert and self.__credentials:
849            # Refresh flag is not set so it's OK to check for any existing
850            # Attribute Certificate in the wallet whose issuerName match the
851            # target AA's name
852           
853            # Find out the site ID for the target AA by calling AA's host
854            # info WS method
855            try:
856                hostInfo = self.__aaClnt.getHostInfo()
857                aaName = hostInfo.keys()[0]
858            except Exception, e:
859                raise CredWalletError, "Getting host info: %s" % e
860           
861            # Look in the wallet for an AC with the same issuer name
862            if aaName in self.__credentials:
863                # Existing Attribute Certificate found in wallet - Check that
864                # it will be valid for at least the next 2 hours
865                if attCertRefreshElapse is not None:
866                    self.attCertRefreshElapse = attCertRefreshElapse
867                   
868                dtNow = datetime.utcnow() + \
869                        timedelta(seconds=self.attCertRefreshElapse)
870               
871                attCert = self.__credentials[aaName]['attCert']
872                if attCert.isValidTime(dtNow=dtNow):                                   
873                    return attCert
874           
875           
876        # Check for settings from input, if not set use previous settings
877        # made
878        if mapFromTrustedHosts is not None:
879            self.__mapFromTrustedHosts = mapFromTrustedHosts
880
881        if rtnExtAttCertList is not None:
882            self.__rtnExtAttCertList = rtnExtAttCertList
883
884
885        # Check for list of external trusted hosts (other trusted NDG data
886        # centres)
887        if extTrustedHostList:
888            if not self.__mapFromTrustedHosts:
889                raise CredWalletError, "A list of trusted hosts has been " + \
890                "input but mapping from trusted hosts is set to disallowed"
891           
892            if isinstance(extTrustedHostList, basestring):
893                extTrustedHostList = [extTrustedHostList]
894
895            # Nb. Any extAttCertList is overriden by extTrustedHostList being
896            # set
897            extAttCertList = [self.__credentials[hostName]['attCert'] \
898                              for hostName in extTrustedHostList \
899                              if hostName in self.__credentials]
900
901        # Set an empty list to trigger an AttributeError by initialising it to
902        # None
903        if extAttCertList == []:
904            extAttCertList = None
905           
906        # Repeat authorisation attempts until succeed or means are exhausted
907        while True:
908           
909            # Check for candidate certificates for mapping
910            try:
911                # If list is set get the next cert
912                extAttCert = extAttCertList.pop()
913
914            except AttributeError:
915               
916                # No List set - attempt authorisation without
917                # using mapping from trusted hosts
918                extAttCert = None
919                               
920            except IndexError:
921               
922                # List has been emptied without attribute request succeeding -
923                # give up
924                errMsg = "Attempting to obtained a mapped certificate: " + \
925                    "no external attribute certificates are available"
926                   
927                # Add the exception form the last call to the Attribute
928                # Authority if an error exists
929                try:
930                    errMsg += ": %s" % attributeRequestDenied
931                except NameError:
932                    pass
933
934                raise CredWalletAttributeRequestDenied, errMsg
935                                                   
936               
937            # Request Authorisation from Attribute Authority
938            try:
939                attCert = self.__getAttCert(extAttCert=extAttCert)               
940                # Access granted
941                return attCert
942           
943            except CredWalletAttributeRequestDenied, attributeRequestDenied:
944
945                # If a required role was set then it's possible to go
946                # to get certificates with mapped roles from trusted hosts
947                # Shouldn't need to set a role - setting a role makes it more
948                # efficient but it's not essential
949                #
950                # P J Kershaw 29/03/06
951#                if not reqRole:
952#                    raise CredWalletAttributeRequestDenied(\
953#                        "No user role was input in order to map to " + \
954#                        "a role in a trusted host")
955
956                if not mapFromTrustedHosts and not rtnExtAttCertList:
957                    # Creating a mapped certificate is not allowed - raise
958                    # authorisation denied exception saved from earlier
959                    raise attributeRequestDenied
960
961
962                if isinstance(extAttCertList, list):
963                    # An list of attribute certificates from trusted hosts
964                    # is present continue cycling through this until one of
965                    # them is accepted and a mapped certificate can be derived
966                    continue
967                             
968                #  Use the input required role and the AA's trusted host list
969                # to identify attribute certificates from other hosts which
970                # could be used to make a mapped certificate
971                try:
972                    trustedHostInfo = self.getAATrustedHostInfo(reqRole,
973                                            aaPropFilePath=aaPropFilePath)
974                except Exception, e:
975                    raise CredWalletError, "Getting trusted hosts: %s" % e
976
977                if not trustedHostInfo:
978                    raise CredWalletAttributeRequestDenied, \
979                        "Attribute Authority has no trusted hosts with " + \
980                        "which to make a mapping"
981
982               
983                # Initialise external certificate list here - if none are
984                # found IndexError will be raised on the next iteration and
985                # an access denied error will be raised
986                extAttCertList = []
987
988                # Look for Attribute Certificates with matching issuer host
989                # names
990                for hostName in self.__credentials:
991
992                    # Nb. Candidate certificates for mappings must have
993                    # original provenance and contain at least one of the
994                    # required roles
995                    attCert = self.__credentials[hostName]['attCert']
996                   
997                    if hostName in trustedHostInfo and attCert.isOriginal():                       
998                        for role in attCert.getRoles():
999                            if role in trustedHostInfo[hostName]['role']:                               
1000                                extAttCertList.append(attCert)
1001
1002
1003                if not extAttCertList:
1004                    # No certificates in the wallet matched the trusted host
1005                    # and required roles
1006                    #
1007                    # Try each host in turn in order to get a certificate with
1008                    # the required credentials in order to do a mapping
1009                    for key, val in trustedHostInfo.items():
1010
1011                        try:
1012                            extAttCert=self.__getAttCert(aaURI=val['aaURI'])
1013
1014                            # Check the certificate contains at least one of
1015                            # the required roles
1016                            roles = extAttCert.getRoles()
1017                            if [True for r in roles if r in val['role']]:
1018                               extAttCertList.append(extAttCert)
1019
1020                               # For efficiency, stop once obtained a valid
1021                               # cert - but may want complete list for user to
1022                               # choose from
1023                               #break
1024                               
1025                        except Exception, e:
1026                            pass    # ignore any errors and continue
1027                   
1028                if not extAttCertList:                       
1029                    raise CredWalletAttributeRequestDenied, \
1030                        "No certificates are available with which to " + \
1031                        "make a mapping to the Attribute Authority"
1032
1033
1034                if not mapFromTrustedHosts:
1035                   
1036                    # Exit here returning the list of candidate certificates
1037                    # that could be used to make a mapped certificate
1038                    msg = "User is not registered with Attribute " + \
1039                          "Authority - retry using one of the returned " + \
1040                          "Attribute Certificates obtained from other " + \
1041                          "trusted hosts"
1042                         
1043                    raise CredWalletAttributeRequestDenied(msg=msg,
1044                                            extAttCertList=extAttCertList,
1045                                            trustedHostInfo=trustedHostInfo)
1046               
1047            except Exception, authorisationError:
1048                # Authorisation request raised an error other than access
1049                # denied
1050                raise authorisationError
1051           
1052             
1053       
1054#_____________________________________________________________________________
1055class CredReposError(Exception):   
1056    """Exception handling for NDG Credential Repository class."""
1057    pass 
1058
1059
1060#_____________________________________________________________________________
1061class CredRepos:
1062    """CredWallet's interface class to a Credential Repository"""
1063   
1064
1065    def __init__(self, propFilePath=None, dbPPhrase=None, **prop):
1066        """Initialise Credential Repository abstract base class derive from
1067        this class to define Credentail Repository interface Credential
1068        Wallet
1069
1070        If the connection string or properties file is set a connection
1071        will be made
1072
1073        @type dbPPhrase: string
1074        @keyword dbPPhrase: pass-phrase to database if applicable
1075       
1076        @type propFilePath: string
1077        @keyword propFilePath: file path to a properties file.  This could
1078        contain configuration parameters for the repository e.g.  database
1079        connection parameters
1080       
1081        @type **prop: dict
1082        @param **prop: any other keywords required
1083        """
1084        raise NotImplementedError, \
1085            self.__init__.__doc__.replace('\n       ','')
1086
1087
1088    def addUser(self, username, dn):
1089        """A new user to Credentials Repository
1090       
1091        @type username: string
1092        @param username: username for new user
1093        @type dn: string
1094        @param dn: users Distinguished Name"""
1095        raise NotImplementedError, \
1096            self.addUser.__doc__.replace('\n       ','')
1097
1098                           
1099    def auditCredentials(self, **attCertValidKeys):
1100        """Check the attribute certificates held in the repository and delete
1101        any that have expired
1102
1103        @type attCertValidKeys: dict
1104        @param **attCertValidKeys: keywords which set how to check the
1105        Attribute Certificate e.g. check validity time, XML signature, version
1106         etc.  Default is check validity time only - See AttCert class"""
1107        raise NotImplementedError, \
1108            self.auditCredentials.__doc__.replace('\n       ','')
1109
1110
1111    def getCredentials(self, dn):
1112        """Get the list of credentials for a given users DN
1113       
1114        @type dn: string
1115        @param dn: users distinguished name
1116        @rtype: list
1117        @return: list of Attribute Certificates"""
1118        raise NotImplementedError, \
1119            self.getCredentials.__doc__.replace('\n       ','')
1120
1121       
1122    def addCredentials(self, dn, attCertList):
1123        """Add new attribute certificates for a user.  The user must have
1124        been previously registered in the repository
1125
1126        @type dn: string
1127        @param dn: users Distinguished name
1128        @type attCertList: list
1129        @param attCertList: list of attribute certificates"""
1130        raise NotImplementedError, \
1131            self.addCredentials.__doc__.replace('\n       ','')
1132
1133
1134
1135#_____________________________________________________________________________
1136class NullCredRepos(CredRepos):
1137    """Implementation of Credential Repository interface with empty stubs. 
1138    This allows for where no Credential Repository is required"""
1139   
1140    def __init__(self, propFilePath=None, dbPPhrase=None, **prop):
1141        pass
1142
1143    def addUser(self, userName, dn):
1144        pass
1145                           
1146    def auditCredentials(self, **attCertValidKeys):
1147        pass
1148
1149    def getCredentials(self, dn):
1150        return []
1151       
1152    def addCredentials(self, dn, attCertList):
1153        pass
Note: See TracBrowser for help on using the repository browser.