source: TI12-security/trunk/NDGSecurity/python/ndg_security_common/ndg/security/common/saml_utils/binding/soap/__init__.py @ 6578

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg-security/TI12-security/trunk/NDGSecurity/python/ndg_security_common/ndg/security/common/saml_utils/binding/soap/__init__.py@6578
Revision 6578, 10.3 KB checked in by pjkersha, 11 years ago (diff)
  • Important fix for SOAP client used with SAML SOAP binding: set text/xml content type.
  • Refactored SAML SOAP binding query clients.
Line 
1"""SAML 2.0 bindings module implements SOAP binding for attribute query
2
3NERC DataGrid Project
4"""
5__author__ = "P J Kershaw"
6__date__ = "02/09/09"
7__copyright__ = "(C) 2009 Science and Technology Facilities Council"
8__license__ = "BSD - see LICENSE file in top-level directory"
9__contact__ = "Philip.Kershaw@stfc.ac.uk"
10__revision__ = '$Id: $'
11import logging
12log = logging.getLogger(__name__)
13
14from os import path
15from ConfigParser import ConfigParser
16
17from saml.common import SAMLObject
18
19from ndg.security.common.utils.factory import importModuleObject
20from ndg.security.common.utils.configfileparsers import (
21                                                    CaseSensitiveConfigParser)
22from ndg.security.common.soap import SOAPEnvelopeBase
23from ndg.security.common.soap.etree import SOAPEnvelope
24from ndg.security.common.soap.client import (UrlLib2SOAPClient, 
25                                             UrlLib2SOAPRequest)
26
27
28class SOAPBindingError(Exception):
29    '''Base exception type for client SAML SOAP Binding for Attribute Query'''
30
31
32class SOAPBindingInvalidResponse(SOAPBindingError):
33    '''Raise if the response is invalid'''
34   
35   
36_isIterable = lambda obj: getattr(obj, '__iter__', False) 
37   
38
39class SOAPBinding(object):
40    '''Client SAML SOAP Binding'''
41    REQUEST_ENVELOPE_CLASS_OPTNAME = 'requestEnvelopeClass'
42    RESPONSE_ENVELOPE_CLASS_OPTNAME = 'responseEnvelopeClass'
43    SERIALISE_OPTNAME = 'serialise'
44    DESERIALISE_OPTNAME = 'deserialise' 
45   
46    CONFIG_FILE_OPTNAMES = (
47        REQUEST_ENVELOPE_CLASS_OPTNAME,
48        RESPONSE_ENVELOPE_CLASS_OPTNAME,
49        SERIALISE_OPTNAME,
50        DESERIALISE_OPTNAME
51    )
52   
53    __PRIVATE_ATTR_PREFIX = "__"
54    __slots__ = tuple([__PRIVATE_ATTR_PREFIX + i
55                       for i in CONFIG_FILE_OPTNAMES + ("client",)])
56    del i
57   
58    isIterable = staticmethod(_isIterable)
59   
60    def __init__(self, 
61                 requestEnvelopeClass=SOAPEnvelope,
62                 responseEnvelopeClass=SOAPEnvelope,
63                 serialise=None,
64                 deserialise=None,
65                 handlers=()):
66        '''Create SAML SOAP Client - Nb. serialisation functions must be set
67        before send()ing the request'''
68        self.__client = None
69        self.__serialise = None
70        self.__deserialise = None
71       
72        if serialise is not None:
73            self.serialise = serialise
74           
75        if deserialise is not None:
76            self.deserialise = deserialise
77       
78        self.client = UrlLib2SOAPClient()
79       
80        # Configurable envelope classes
81        self.requestEnvelopeClass = requestEnvelopeClass
82        self.client.responseEnvelopeClass = responseEnvelopeClass
83
84        if not SOAPBinding.isIterable(handlers):
85            raise TypeError('Expecting iterable for "handlers" keyword; got %r'
86                            % type(handlers))
87           
88        for handler in handlers:
89            self.client.openerDirector.add_handler(handler())
90
91    def _getSerialise(self):
92        return self.__serialise
93
94    def _setSerialise(self, value):
95        if isinstance(value, basestring):
96            self.__serialise = importModuleObject(value)
97           
98        elif callable(value):
99            self.__serialise = value
100        else:
101            raise TypeError('Expecting callable for "serialise"; got %r' % 
102                            value)
103
104    serialise = property(_getSerialise, _setSerialise, 
105                         doc="callable to serialise request into XML type")
106
107    def _getDeserialise(self):
108        return self.__deserialise
109
110    def _setDeserialise(self, value):
111        if isinstance(value, basestring):
112            self.__deserialise = importModuleObject(value)
113           
114        elif callable(value):
115            self.__deserialise = value
116        else:
117            raise TypeError('Expecting callable for "deserialise"; got %r' % 
118                            value)
119       
120
121    deserialise = property(_getDeserialise, 
122                           _setDeserialise, 
123                           doc="callable to de-serialise response from XML "
124                               "type")
125
126    def _getRequestEnvelopeClass(self):
127        return self.__requestEnvelopeClass
128
129    def _setRequestEnvelopeClass(self, value):
130        if isinstance(value, basestring):
131            self.client.responseEnvelopeClass = importClass(value)
132           
133        elif issubclass(value, SOAPEnvelopeBase):
134            self.client.responseEnvelopeClass = value
135        else:
136            raise TypeError('Expecting %r derived type or string for '
137                            '"requestEnvelopeClass" attribute; got %r' % 
138                            (SOAPEnvelopeBase, value))
139       
140        self.__requestEnvelopeClass = value
141
142    requestEnvelopeClass = property(_getRequestEnvelopeClass, 
143                                    _setRequestEnvelopeClass, 
144                                    doc="SOAP Envelope Request Class")
145
146    def _getClient(self):
147        return self.__client
148
149    def _setClient(self, value):     
150        if not isinstance(value, UrlLib2SOAPClient):
151            raise TypeError('Expecting %r for "client"; got %r' % 
152                            (UrlLib2SOAPClient, type(value)))
153        self.__client = value
154
155    client = property(_getClient, _setClient, 
156                      doc="SOAP Client object")   
157
158    def send(self, samlObj, uri=None, request=None):
159        '''Make an request/query to a remote SAML service
160       
161        @type samlObj: saml.common.SAMLObject
162        @param samlObj: SAML query/request object
163        @type uri: basestring
164        @param uri: uri of service.  May be omitted if set from request.url
165        @type request: ndg.security.common.soap.UrlLib2SOAPRequest
166        @param request: SOAP request object to which query will be attached
167        defaults to ndg.security.common.soap.client.UrlLib2SOAPRequest
168        '''
169        if self.serialise is None:
170            raise AttributeError('No "serialise" method set to serialise the '
171                                 'request')
172
173        if self.deserialise is None:
174            raise AttributeError('No "deserialise" method set to deserialise '
175                                 'the response')
176           
177        if not isinstance(samlObj, SAMLObject):
178            raise TypeError('Expecting %r for input attribute query; got %r'
179                            % (SAMLObject, type(samlObj)))
180           
181        if request is None:
182            request = UrlLib2SOAPRequest()
183            request.envelope = self.requestEnvelopeClass()
184            request.envelope.create()
185           
186        if uri is not None:
187            request.url = uri
188       
189        samlElem = self.serialise(samlObj)
190           
191        # Attach query to SOAP body
192        request.envelope.body.elem.append(samlElem)
193           
194        response = self.client.send(request)
195       
196        if len(response.envelope.body.elem) != 1:
197            raise SOAPBindingInvalidResponse("Expecting single child element "
198                                             "is SOAP body")
199           
200        response = self.deserialise(response.envelope.body.elem[0])
201       
202        return response
203
204    @classmethod
205    def fromConfig(cls, cfg, **kw):
206        '''Alternative constructor makes object from config file settings
207        @type cfg: basestring /ConfigParser derived type
208        @param cfg: configuration file path or ConfigParser type object
209        @rtype: ndg.security.common.saml_utils.binding.soap.SOAPBinding
210        @return: new instance of this class
211        '''
212        obj = cls()
213        obj.parseConfig(cfg, **kw)
214       
215        return obj
216
217    def parseConfig(self, cfg, prefix='', section='DEFAULT'):
218        '''Read config file settings
219        @type cfg: basestring /ConfigParser derived type
220        @param cfg: configuration file path or ConfigParser type object
221        @type prefix: basestring
222        @param prefix: prefix for option names e.g. "attributeQuery."
223        @type section: baestring
224        @param section: configuration file section from which to extract
225        parameters.
226        ''' 
227        if isinstance(cfg, basestring):
228            cfgFilePath = path.expandvars(cfg)
229            _cfg = CaseSensitiveConfigParser()
230            _cfg.read(cfgFilePath)
231           
232        elif isinstance(cfg, ConfigParser):
233            _cfg = cfg   
234        else:
235            raise AttributeError('Expecting basestring or ConfigParser type '
236                                 'for "cfg" attribute; got %r type' % type(cfg))
237       
238        prefixLen = len(prefix)
239        for optName, val in _cfg.items(section):
240            if prefix:
241                # Filter attributes based on prefix
242                if optName.startswith(prefix):
243                    setattr(self, optName[prefixLen:], val)
244            else:
245                # No prefix set - attempt to set all attributes   
246                setattr(self, optName, val)
247               
248    def __setattr__(self, name, value):
249        """Enable setting of SOAPBinding.client.responseEnvelopeClass as if it
250        were an attribute of self
251        """
252        try:
253            super(SOAPBinding, self).__setattr__(name, value)
254           
255        except AttributeError:
256            if 'name' == SOAPBinding.RESPONSE_ENVELOPE_CLASS_OPTNAME:
257                if isinstance(value, basestring):
258                    self.client.responseEnvelopeClass = importClass(value)
259                elif issubclass(value, SOAPEnvelopeBase):
260                    self.client.responseEnvelopeClass = value
261                else:
262                    raise TypeError('Expecting string or type instance for %r; '
263                                    'got %r instead.' % (name, type(value)))
264            else:
265                raise
266                   
267    def __getstate__(self):
268        '''Explicit implementation needed with __slots__'''
269        _dict = {}
270        for attrName in SOAPBinding.__slots__:
271            # Ugly hack to allow for derived classes setting private member
272            # variables
273            if attrName.startswith('__'):
274                attrName = "_SOAPBinding" + attrName
275               
276            _dict[attrName] = getattr(self, attrName)
277           
278        return _dict
279       
280    def __setstate__(self, attrDict):
281        '''Explicit implementation needed with __slots__'''
282        for attr, val in attrDict.items():
283            setattr(self, attr, val)
Note: See TracBrowser for help on using the repository browser.