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

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