source: TI12-security/trunk/python/ndg.security.common/ndg/security/common/AttAuthority/__init__.py @ 4129

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.common/ndg/security/common/AttAuthority/__init__.py@4129
Revision 4129, 19.9 KB checked in by cbyrom, 11 years ago (diff)

General refactoring and updating of code, including:

Removal of refC14nKw and singnedInfoC14nKw keywords in wsssecurity session manager config
(the refC14nInclNS and signedInfoC14nInclNS keywords are sufficient);
Creation of new DOM signature handler class, dom.py, based on the wsSecurity
class;
Abstraction of common code between dom.py and etree.py into new parent
class, BaseSignatureHandler?.py.
Fixing and extending use of properties in the SignatureHandler? code.
Fixing a few bugs with the original SignatureHandler? code.
Updating of test cases to new code/code structure.

  • Property svn:keywords set to Id
Line 
1"""NDG Security Attribute Authority client - client interface classes to the
2Attribute Authority.  These have been separated from their
3original location in the SecurityClient since they have the
4unusual place of being required by both client and server
5NDG security packages.  For the server side they are required
6as the CredWallet invoked by the Session Manager acts as a
7client to Attribute Authorities when negotiating the required
8Attribute Certificate.
9
10Make requests for Attribute Certificates used for authorisation
11
12NERC Data Grid Project
13"""
14__author__ = "P J Kershaw"
15__date__ = "17/11/06"
16__copyright__ = "(C) 2007 STFC & NERC"
17__contact__ = "P.J.Kershaw@rl.ac.uk"
18__license__ = \
19"""This software may be distributed under the terms of the Q Public
20License, version 1.0 or later."""
21__contact__ = "P.J.Kershaw@rl.ac.uk"
22__revision__ = "$Id$"
23
24__all__ = [
25    'AttAuthorityClient',
26    'AttAuthorityClientError',
27    'AttributeRequestDenied',
28    'NoTrustedHosts',]
29
30# Determine https http transport
31import urlparse, httplib
32from ZSI.wstools.Utility import HTTPResponse
33
34from AttAuthority_services import AttAuthorityServiceLocator
35from ndg.security.common.wssecurity.dom import SignatureHandler
36from ndg.security.common.AttCert import AttCert, AttCertParse
37from ndg.security.common.m2CryptoSSLUtility import HTTPSConnection, \
38    HostCheck
39
40from ndg.security.common.zsi_utils.httpproxy import ProxyHTTPConnection
41
42import logging
43log = logging.getLogger(__name__)
44
45#_____________________________________________________________________________
46class AttAuthorityClientError(Exception):
47    """Exception handling for AttributeAuthorityClient class"""
48
49class AttributeRequestDenied(Exception):
50    """Raise when a getAttCert call to the AA is denied"""
51
52class NoTrustedHosts(AttAuthorityClientError):
53    """Raise from getTrustedHosts if there are no trusted hosts defined in
54    the map configuration"""
55
56class NoMatchingRoleInTrustedHosts(AttAuthorityClientError):
57    """Raise from getTrustedHosts if there is no mapping to any of the
58    trusted hosts for the given input role name"""
59
60#_____________________________________________________________________________
61class AttAuthorityClient(object):
62    """Client interface to Attribute Authority web service
63   
64    @type excepMap: dict
65    @cvar excepMap: map exception strings returned from SOAP fault to client
66    Exception class to call"""
67   
68    excepMap = {
69        'AttAuthorityNoTrustedHosts': NoTrustedHosts,
70        'AttAuthorityNoMatchingRoleInTrustedHosts': NoMatchingRoleInTrustedHosts
71        }
72   
73    #_________________________________________________________________________
74    def __init__(self, 
75                 uri=None, 
76                 tracefile=None,
77                 httpProxyHost=None,
78                 noHttpProxyList=False,
79                 sslCACertList=[],
80                 sslCACertFilePathList=[],
81                 sslPeerCertCN=None, 
82                 setSignatureHandler=True,
83                 **signatureHandlerKw):
84        """
85        @type uri: string
86        @param uri: URI for Attribute Authority WS.  Setting it will also
87        initialise the Service Proxy
88                                         
89        @param tracefile: set to file object such as sys.stderr to give
90        extra WS debug information
91       
92        @type sslCACertList: list
93        @param sslCACertList: This keyword is for use with SSL connections
94        only.  Set a list of one ore more CA certificates.  The peer cert.
95        must verify against at least one of these otherwise the connection
96        is dropped.
97       
98        @type sslCACertFilePathList: list
99        @param sslCACertFilePathList: the same as the above except CA certs
100        can be passed as a list of file paths to read from
101       
102        @type sslPeerCertCN: string
103        @param sslPeerCertCN: set an alternate CommonName to match with peer
104        cert.  This keyword is for use with SSL connections only.
105                     
106        @type setSignatureHandler: bool
107        @param setSignatureHandler: flag to determine whether to apply
108        WS-Security Signature Handler or not
109
110        @type signatureHandlerKw: dict
111        @param signatureHandlerKw: keywords to configure signature handler"""
112
113        log.debug("AttAuthorityClient.__init__ ...")
114        self.__srv = None
115        self.__uri = None
116        self._transdict = {}       
117        self._transport = ProxyHTTPConnection
118       
119        if uri:
120            self.__setURI(uri)
121
122        self.__setHTTPProxyHost(httpProxyHost)
123        self.__setNoHttpProxyList(noHttpProxyList)
124       
125        if sslPeerCertCN:
126            self.__setSSLPeerCertCN(sslPeerCertCN)
127       
128        if sslCACertList:
129            self.__setSSLCACertList(sslCACertList)
130        elif sslCACertFilePathList:
131            self.__setSSLCACertFilePathList(sslCACertFilePathList)
132       
133        # WS-Security Signature handler - set only if any of the keywords were
134        # set
135        if setSignatureHandler:
136            log.debug('signatureHandlerKw = %s' % signatureHandlerKw)
137            self.__signatureHandler = SignatureHandler(**signatureHandlerKw)
138        else:
139            self.__signatureHandler = None
140           
141        self.__tracefile = tracefile
142       
143        # Instantiate Attribute Authority WS proxy
144        if self.__uri:
145            self.initService()
146       
147
148    #_________________________________________________________________________
149    def __setURI(self, uri):
150        """Set URI for service
151        @type uri: string
152        @param uri: URI for service to connect to"""
153        if not isinstance(uri, basestring):
154            raise AttAuthorityClientError(
155                        "Attribute Authority URI must be a valid string")
156       
157        self.__uri = uri
158        try:
159            scheme = urlparse.urlparse(self.__uri)[0]
160        except TypeError:
161            raise AttributeAuthorityClientError(
162                "Error parsing transport type from URI: %s" % self.__uri)
163               
164        if scheme == "https":
165            self._transport = HTTPSConnection
166        else:
167            self._transport = ProxyHTTPConnection
168
169
170    #_________________________________________________________________________
171    def __getURI(self):
172        """Get URI for service
173        @rtype: string
174        @return: uri for service to be invoked"""
175        return self.__uri
176           
177    uri = property(fset=__setURI, fget=__getURI,doc="Attribute Authority URI")
178
179
180    #_________________________________________________________________________
181    def __setHTTPProxyHost(self, val):
182        """Set a HTTP Proxy host overriding any http_proxy environment variable
183        setting"""
184        if self._transport != ProxyHTTPConnection:
185            log.info("Ignoring httpProxyHost setting: transport class is " +\
186                     "not ProxyHTTPConnection type")
187            return
188       
189        self._transdict['httpProxyHost'] = val
190
191    httpProxyHost = property(fset=__setHTTPProxyHost, 
192        doc="HTTP Proxy hostname - overrides any http_proxy env var setting")
193
194
195    #_________________________________________________________________________
196    def __setNoHttpProxyList(self, val):
197        """Set to list of hosts for which to ignore the HTTP Proxy setting"""
198        if self._transport != ProxyHTTPConnection:
199            log.info("Ignore noHttpProxyList setting: transport " + \
200                     "class is not ProxyHTTPConnection type")
201            return
202       
203        self._transdict['noHttpProxyList'] = val
204
205    noHttpProxyList = property(fset=__setNoHttpProxyList, 
206    doc="Set to list of hosts for which to ignore the HTTP Proxy setting")
207
208
209    #_________________________________________________________________________
210    def __setSSLPeerCertCN(self, cn):
211        """For use with HTTPS connections only.  Specify the Common
212        Name to match with Common Name of the peer certificate.  This is not
213        needed if the peer cert CN = peer hostname"""
214        if self._transport != HTTPSConnection:
215            return
216       
217        if self._transdict.get('postConnectionCheck'):
218            self._transdict['postConnectionCheck'].peerCertCN = cn
219        else:
220            self._transdict['postConnectionCheck'] = HostCheck(peerCertCN=cn)
221
222    sslPeerCertCN = property(fset=__setSSLPeerCertCN, 
223doc="for https connections, set CN of peer cert if other than peer hostname")
224
225
226    #_________________________________________________________________________
227    def __setSSLCACertList(self, caCertList):
228        """For use with HTTPS connections only.  Specify CA certs to one of
229        which the peer cert must verify its signature against"""
230        if self._transport != HTTPSConnection:
231            return
232       
233        if self._transdict.get('postConnectionCheck'):
234            self._transdict['postConnectionCheck'].caCertList = caCertList
235        else:
236            self._transdict['postConnectionCheck'] = \
237                                            HostCheck(caCertList=caCertList)
238
239    sslCACertList = property(fset=__setSSLCACertList, 
240doc="for https connections, set list of CA certs from which to verify peer cert")
241
242
243    #_________________________________________________________________________
244    def __setSSLCACertFilePathList(self, caCertFilePathList):
245        """For use with HTTPS connections only.  Specify CA certs to one of
246        which the peer cert must verify its signature against"""
247        if self._transport != HTTPSConnection:
248            return
249       
250        if self._transdict.get('postConnectionCheck'):
251            self._transdict['postConnectionCheck'].caCertFilePathList = \
252                                                            caCertFilePathList
253        else:
254            self._transdict['postConnectionCheck'] = \
255                            HostCheck(caCertFilePathList=caCertFilePathList)
256
257    sslCACertFilePathList = property(fset=__setSSLCACertFilePathList, 
258doc="for https connections, set list of CA cert files from which to verify peer cert")
259
260   
261    #_________________________________________________________________________
262    def __setSignatureHandler(self, signatureHandler):
263        """Set SignatureHandler object property method - set to None to for no
264        digital signature and verification"""
265        if signatureHandler is not None and \
266           not isinstance(signatureHandler, signatureHandler):
267            raise AttributeError, \
268    "Signature Handler must be %s type or None for no message security" % \
269        "ndg.security.common.wssecurity.dom.SignatureHandler"
270                           
271        self.__signatureHandler = signatureHandler
272   
273
274    #_________________________________________________________________________
275    def __getSignatureHandler(self):
276        "Get SignatureHandler object property method"
277        return self.__signatureHandler
278   
279    signatureHandler = property(fget=__getSignatureHandler,
280                                fset=__setSignatureHandler,
281                                doc="SignatureHandler object")
282   
283       
284    #_________________________________________________________________________
285    def initService(self, uri=None):
286        """Set the WS proxy for the Attribute Authority
287       
288        @type uri: string
289        @param uri: URI for service to invoke"""
290       
291        if uri:
292            self.__setURI(uri)
293
294        # WS-Security Signature handler object is passed to binding
295        try:
296            locator = AttAuthorityServiceLocator()
297            self.__srv = locator.getAttAuthority(self.__uri, 
298                                         sig_handler=self.__signatureHandler,
299                                         tracefile=self.__tracefile,
300                                         transport=self._transport,
301                                         transdict=self._transdict)
302        except HTTPResponse, e:
303            raise AttAuthorityClientError, \
304                "Error initialising service for \"%s\": %s %s" % \
305                (self.__uri, e.status, e.reason)
306
307                                   
308    #_________________________________________________________________________
309    def getHostInfo(self):
310        """Get host information for the data provider which the
311        Attribute Authority represents
312       
313        @rtype: dict
314        @return: dictionary of host information derived from the Attribute
315        Authority's map configuration
316        """
317   
318        if not self.__srv:
319            raise InvalidSessionMgrClientCtx(\
320                                        "Client binding is not initialised")
321
322        try:
323            # Convert return tuple into list to enable use of pop() later
324            response = list(self.__srv.getHostInfo())
325        except httplib.BadStatusLine, e:
326            raise AttAuthorityClientError, "HTTP bad status line: %s" % e
327
328        except Exception, e:
329            # Try to detect exception type from SOAP fault message
330            errMsg = str(e)
331            for excep in self.excepMap:
332                if excep in errMsg:
333                    raise self.excepMap[excep]
334               
335            # Catch all   
336            raise e
337
338        # Unpack response into dict
339        hostInfoKw = ['aaURI',
340                      'aaDN',
341                      'loginURI',
342                      'loginServerDN',
343                      'loginRequestServerDN']
344        hostInfoKw.reverse()
345        hostInfo = {response.pop(): \
346                    dict([(k, response.pop()) for k in hostInfoKw])}
347
348        return hostInfo
349
350                                   
351    #_________________________________________________________________________
352    def getTrustedHostInfo(self, role=None):
353        """Get list of trusted hosts for an Attribute Authority
354       
355        @type role: string
356        @param role: get information for trusted hosts that have a mapping to
357        this role
358       
359        @rtype: dict
360        @return: dictionary of host information indexed by hostname derived
361        from the map configuration"""
362   
363        if not self.__srv:
364            raise InvalidSessionMgrClientCtx(\
365                                        "Client binding is not initialised")
366           
367        try:
368            trustedHosts = self.__srv.getTrustedHostInfo(role)
369
370        except httplib.BadStatusLine, e:
371            raise AttAuthorityClientError, "HTTP bad status line: %s" % e
372
373        except Exception, e:
374            # Try to detect exception type from SOAP fault message
375            errMsg = str(e)
376            for excep in self.excepMap:
377                if excep in errMsg:
378                    raise self.excepMap[excep]
379               
380            # Catch all   
381            raise e
382
383        # Convert into dictionary form as used by AttAuthority class
384        trustedHostInfo = {}
385        for host in trustedHosts:
386            hostname = host.get_element_hostname()
387           
388            trustedHostInfo[hostname] = \
389            {
390                'aaURI': host.AaURI,
391                'aaDN': host.AaDN,
392                'loginURI': host.LoginURI,
393                'loginServerDN': host.LoginServerDN,
394                'loginRequestServerDN': host.LoginRequestServerDN,
395                'role': host.RoleList
396            }
397           
398        return trustedHostInfo
399
400                                   
401    #_________________________________________________________________________
402    def getAllHostsInfo(self):
403        """Get list of all hosts for an Attribute Authority i.e. itself and
404        all the hosts it trusts
405       
406        @rtype: dict
407        @return: dictionary of host information indexed by hostname derived
408        from the map configuration"""
409   
410        if not self.__srv:
411            raise InvalidSessionMgrClientCtx(\
412                                        "Client binding is not initialised")
413
414        hosts = self.__srv.getAllHostsInfo()
415        try:
416            hosts = self.__srv.getAllHostsInfo()
417
418        except httplib.BadStatusLine, e:
419            raise AttAuthorityClientError, "HTTP bad status line: %s" % e
420
421        except Exception, e:
422            # Try to detect exception type from SOAP fault message
423            errMsg = str(e)
424            for excep in self.excepMap:
425                if excep in errMsg:
426                    raise self.excepMap[excep]
427               
428            # Catch all   
429            raise e
430
431        # Convert into dictionary form as used by AttAuthority class
432        allHostInfo = {}
433        for host in hosts:
434            hostname = host.Hostname
435            allHostInfo[hostname] = \
436            {
437                'aaURI': host.AaURI,
438                'aaDN': host.AaDN,
439                'loginURI': host.LoginURI,
440                'loginServerDN': host.LoginServerDN,
441                'loginRequestServerDN': host.LoginRequestServerDN,
442                'role': host.RoleList
443            }
444
445        return allHostInfo   
446
447
448    #_________________________________________________________________________
449    def getAttCert(self, userId=None, userCert=None, userAttCert=None):
450        """Request attribute certificate from NDG Attribute Authority Web
451        Service.
452       
453        @type userId: string
454        @param userId: DN of the X.509 certificate used in SOAP digital
455        signature corresponds to the *holder* of the Attribute Certificate
456        that is issued.  Set this additional field to specify an alternate
457        user ID to associate with the AC.  This is useful in the case where,
458        as in the DEWS project, the holder will be a server cert. rather than
459        a user proxy cert.
460       
461        If this keword is omitted, userId in the AC will default to the same
462        value as the holder DN.
463       
464        @type userCert: string
465        @param userCert: certificate corresponding to proxy private key and
466        proxy cert used to sign the request.  Enables server to establish
467        chain of trust proxy -> user cert -> CA cert.  If a standard
468        private key is used to sign the request, this argument is not
469        needed.
470       
471        @type userAttCert: string / AttCert
472        @param userAttCert: user attribute certificate from which to make a
473        mapped certificate at the target attribute authority.  userAttCert
474        must have been issued from a trusted host to the target.  This is not
475        necessary if the user is registered at the target Attribute Authority.
476       
477        @rtype ndg.security.common.AttCert.AttCert
478        @return attribute certificate for user.  iIf access is refused,
479        AttributeRequestDenied is raised"""
480   
481        if not self.__srv:
482            raise InvalidSessionMgrClientCtx(\
483                                        "Client binding is not initialised")
484
485        # Ensure cert is serialized before passing over web service interface
486        if isinstance(userAttCert, AttCert):
487            userAttCert = userAttCert.toString()
488
489        try:
490            sAttCert, msg = self.__srv.getAttCert(userId,userCert,userAttCert) 
491        except httplib.BadStatusLine, e:
492            raise AttAuthorityClientError, \
493                'Calling "%s" HTTP bad status line: %s' % (self.__uri, e)
494
495        except Exception, e:
496            # Try to detect exception type from SOAP fault message
497            errMsg = str(e)
498            for excep in self.excepMap:
499                if excep in errMsg:
500                    raise self.excepMap[excep]
501               
502            # Catch all   
503            raise e
504       
505        if sAttCert:
506            return AttCertParse(sAttCert)
507        else:
508            raise AttributeRequestDenied, msg
509
510                                   
511    #_________________________________________________________________________
512    def getX509Cert(self):
513        """Retrieve the X.509 certificate of the Attribute Authority
514       
515        @rtype: string
516        @return X.509 certificate for Attribute Authority"""
517   
518        if not self.__srv:
519            raise InvalidSessionMgrClientCtx(\
520                                        "Client binding is not initialised")
521       
522        try:
523            return self.__srv.getX509Cert()               
524        except httplib.BadStatusLine, e:
525            raise AttAuthorityClientError, "HTTP bad status line: %s" % e
Note: See TracBrowser for help on using the repository browser.