source: TI12-security/trunk/python/ndg.security.common/ndg/security/common/attributeauthority.py @ 4392

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

Rerun Credential Wallet and AA client unittests with refactored WSDL stubs in place.

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