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

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.common/ndg/security/common/AttAuthority/__init__.py@3914
Revision 3914, 19.2 KB checked in by pjkersha, 12 years ago (diff)
  • New ndg.security.common.zsi_util.httpproxy.ProxyHTTPConnection class replaces urllib2client - easier to fit into existing ZSI client framework.
  • Further OpenID integration into Single Sign On Service. User now authenticates OK but patches needed to AuthKit? + need to handle return_to URL dynamically according to page visited before WAYF call.
  • 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 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                 ignoreHttpProxyEnv=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 = HTTPProxyConnection
118       
119        if uri:
120            self.__setURI(uri)
121
122        self.__setHTTPProxyHost(httpProxyHost)
123        self.__setIgnoreHttpProxyEnv(ignoreHttpProxyEnv)
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 WSDL 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"
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            raise AttAuthorityClientError(\
186                "Setting HTTP Proxy Host but transport class is not " + \
187                "ProxyHTTPConnection type")
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 __setIgnoreHttpProxyEnv(self, val):
197        """Set to True to ignore any http_proxy environment variable setting"""
198        if self._transport != ProxyHTTPConnection:
199            raise AttAuthorityClientError(\
200                "Setting ignore HTTP Proxy Host flag but transport class " + \
201                "is not ProxyHTTPConnection type")
202       
203        self._transdict['ignoreHttpProxyEnv'] = val
204
205    ignoreHttpProxyEnv = property(fset=__setIgnoreHttpProxyEnv, 
206    doc="Set to True to ignore any http_proxy environment variable 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.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        try:
319            # Convert return tuple into list to enable use of pop() later
320            response = list(self.__srv.getHostInfo())
321        except httplib.BadStatusLine, e:
322            raise AttAuthorityClientError, "HTTP bad status line: %s" % e
323
324        except Exception, e:
325            # Try to detect exception type from SOAP fault message
326            errMsg = str(e)
327            for excep in self.excepMap:
328                if excep in errMsg:
329                    raise self.excepMap[excep]
330               
331            # Catch all   
332            raise e
333
334        # Unpack response into dict
335        hostInfoKw = ['aaURI',
336                      'aaDN',
337                      'loginURI',
338                      'loginServerDN',
339                      'loginRequestServerDN']
340        hostInfoKw.reverse()
341        hostInfo = {response.pop(): \
342                    dict([(k, response.pop()) for k in hostInfoKw])}
343
344        return hostInfo
345
346                                   
347    #_________________________________________________________________________
348    def getTrustedHostInfo(self, role=None):
349        """Get list of trusted hosts for an Attribute Authority
350       
351        @type role: string
352        @param role: get information for trusted hosts that have a mapping to
353        this role
354       
355        @rtype: dict
356        @return: dictionary of host information indexed by hostname derived
357        from the map configuration"""
358           
359        try:
360            trustedHosts = self.__srv.getTrustedHostInfo(role)
361
362        except httplib.BadStatusLine, e:
363            raise AttAuthorityClientError, "HTTP bad status line: %s" % e
364
365        except Exception, e:
366            # Try to detect exception type from SOAP fault message
367            errMsg = str(e)
368            for excep in self.excepMap:
369                if excep in errMsg:
370                    raise self.excepMap[excep]
371               
372            # Catch all   
373            raise e
374
375        # Convert into dictionary form as used by AttAuthority class
376        trustedHostInfo = {}
377        for host in trustedHosts:
378            hostname = host.get_element_hostname()
379           
380            trustedHostInfo[hostname] = \
381            {
382                'aaURI': host.AaURI,
383                'aaDN': host.AaDN,
384                'loginURI': host.LoginURI,
385                'loginServerDN': host.LoginServerDN,
386                'loginRequestServerDN': host.LoginRequestServerDN,
387                'role': host.RoleList
388            }
389           
390        return trustedHostInfo
391
392                                   
393    #_________________________________________________________________________
394    def getAllHostsInfo(self):
395        """Get list of all hosts for an Attribute Authority i.e. itself and
396        all the hosts it trusts
397       
398        @rtype: dict
399        @return: dictionary of host information indexed by hostname derived
400        from the map configuration"""
401
402        hosts = self.__srv.getAllHostsInfo()
403        try:
404            hosts = self.__srv.getAllHostsInfo()
405
406        except httplib.BadStatusLine, e:
407            raise AttAuthorityClientError, "HTTP bad status line: %s" % e
408
409        except Exception, e:
410            # Try to detect exception type from SOAP fault message
411            errMsg = str(e)
412            for excep in self.excepMap:
413                if excep in errMsg:
414                    raise self.excepMap[excep]
415               
416            # Catch all   
417            raise e
418
419        # Convert into dictionary form as used by AttAuthority class
420        allHostInfo = {}
421        for host in hosts:
422            hostname = host.Hostname
423            allHostInfo[hostname] = \
424            {
425                'aaURI': host.AaURI,
426                'aaDN': host.AaDN,
427                'loginURI': host.LoginURI,
428                'loginServerDN': host.LoginServerDN,
429                'loginRequestServerDN': host.LoginRequestServerDN,
430                'role': host.RoleList
431            }
432
433        return allHostInfo   
434
435
436    #_________________________________________________________________________
437    def getAttCert(self, userId=None, userCert=None, userAttCert=None):
438        """Request attribute certificate from NDG Attribute Authority Web
439        Service.
440       
441        @type userId: string
442        @param userId: DN of the X.509 certificate used in SOAP digital
443        signature corresponds to the *holder* of the Attribute Certificate
444        that is issued.  Set this additional field to specify an alternate
445        user ID to associate with the AC.  This is useful in the case where,
446        as in the DEWS project, the holder will be a server cert. rather than
447        a user proxy cert.
448       
449        If this keword is omitted, userId in the AC will default to the same
450        value as the holder DN.
451       
452        @type userCert: string
453        @param userCert: certificate corresponding to proxy private key and
454        proxy cert used to sign the request.  Enables server to establish
455        chain of trust proxy -> user cert -> CA cert.  If a standard
456        private key is used to sign the request, this argument is not
457        needed.
458       
459        @type userAttCert: string / AttCert
460        @param userAttCert: user attribute certificate from which to make a
461        mapped certificate at the target attribute authority.  userAttCert
462        must have been issued from a trusted host to the target.  This is not
463        necessary if the user is registered at the target Attribute Authority.
464       
465        @rtype ndg.security.common.AttCert.AttCert
466        @return attribute certificate for user.  iIf access is refused,
467        AttributeRequestDenied is raised"""
468
469        # Ensure cert is serialized before passing over web service interface
470        if isinstance(userAttCert, AttCert):
471            userAttCert = userAttCert.toString()
472
473        try:
474            sAttCert, msg = self.__srv.getAttCert(userId,userCert,userAttCert) 
475        except httplib.BadStatusLine, e:
476            raise AttAuthorityClientError, \
477                'Calling "%s" HTTP bad status line: %s' % (self.__uri, e)
478
479        except Exception, e:
480            # Try to detect exception type from SOAP fault message
481            errMsg = str(e)
482            for excep in self.excepMap:
483                if excep in errMsg:
484                    raise self.excepMap[excep]
485               
486            # Catch all   
487            raise e
488       
489        if sAttCert:
490            return AttCertParse(sAttCert)
491        else:
492            raise AttributeRequestDenied, msg
493
494                                   
495    #_________________________________________________________________________
496    def getX509Cert(self):
497        """Retrieve the X.509 certificate of the Attribute Authority
498       
499        @rtype: string
500        @return X.509 certificate for Attribute Authority"""
501       
502        try:
503            return self.__srv.getX509Cert()               
504        except httplib.BadStatusLine, e:
505            raise AttAuthorityClientError, "HTTP bad status line: %s" % e
Note: See TracBrowser for help on using the repository browser.