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

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

Renamed CredWallet? -> CredentialWallet?

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