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

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/NDGSecurity/python/ndg_security_common/ndg/security/common/saml_utils/binding/soap/__init__.py@6570
Revision 6570, 10.3 KB checked in by pjkersha, 11 years ago (diff)
  • Refactored classfactory module as a more generic factory for importing any module object
  • Started unit tests with refactored SAML SOAP bindings.
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.__deserialise = importModuleObject(value)
97           
98        elif callable(value):
99            self.__deserialise = value
100        else:
101            raise TypeError('Expecting callable for "serialise"; got %r' % 
102                            value)
103        self.__serialise = value
104
105    serialise = property(_getSerialise, _setSerialise, 
106                         doc="callable to serialise request into XML type")
107
108    def _getDeserialise(self):
109        return self.__deserialise
110
111    def _setDeserialise(self, value):
112        if isinstance(value, basestring):
113            self.__deserialise = importModuleObject(value)
114           
115        elif callable(value):
116            self.__deserialise = value
117        else:
118            raise TypeError('Expecting callable for "deserialise"; got %r' % 
119                            value)
120       
121
122    deserialise = property(_getDeserialise, 
123                           _setDeserialise, 
124                           doc="callable to de-serialise response from XML "
125                               "type")
126
127    def _getRequestEnvelopeClass(self):
128        return self.__requestEnvelopeClass
129
130    def _setRequestEnvelopeClass(self, value):
131        if isinstance(value, basestring):
132            self.client.responseEnvelopeClass = importClass(value)
133           
134        elif issubclass(value, SOAPEnvelopeBase):
135            self.client.responseEnvelopeClass = value
136        else:
137            raise TypeError('Expecting %r derived type or string for '
138                            '"requestEnvelopeClass" attribute; got %r' % 
139                            (SOAPEnvelopeBase, value))
140       
141        self.__requestEnvelopeClass = value
142
143    requestEnvelopeClass = property(_getRequestEnvelopeClass, 
144                                    _setRequestEnvelopeClass, 
145                                    doc="SOAP Envelope Request Class")
146
147    def _getClient(self):
148        return self.__client
149
150    def _setClient(self, value):     
151        if not isinstance(value, UrlLib2SOAPClient):
152            raise TypeError('Expecting %r for "client"; got %r' % 
153                            (UrlLib2SOAPClient, type(value)))
154        self.__client = value
155
156    client = property(_getClient, _setClient, 
157                      doc="SOAP Client object")   
158
159    def send(self, samlObj, uri=None, request=None):
160        '''Make an request/query to a remote SAML service
161       
162        @type samlObj: saml.common.SAMLObject
163        @param samlObj: SAML query/request object
164        @type uri: basestring
165        @param uri: uri of service.  May be omitted if set from request.url
166        @type request: ndg.security.common.soap.UrlLib2SOAPRequest
167        @param request: SOAP request object to which query will be attached
168        defaults to ndg.security.common.soap.client.UrlLib2SOAPRequest
169        '''
170        if self.serialise is None:
171            raise AttributeError('No "serialise" method set to serialise the '
172                                 'request')
173
174        if self.deserialise is None:
175            raise AttributeError('No "deserialise" method set to deserialise '
176                                 'the response')
177           
178        if not isinstance(samlObj, SAMLObject):
179            raise TypeError('Expecting %r for input attribute query; got %r'
180                            % (SAMLObject, type(samlObj)))
181           
182        if request is None:
183            request = UrlLib2SOAPRequest()           
184            request.envelope = self.requestEnvelopeClass()
185            request.envelope.create()
186           
187        if uri is not None:
188            request.url = uri
189       
190        samlElem = self.serialise(samlObj)
191
192        # Attach query to SOAP body
193        request.envelope.body.elem.append(samlElem)
194           
195        response = self.client.send(request)
196       
197        if len(response.envelope.body.elem) != 1:
198            raise SOAPBindingInvalidResponse("Expecting single child element "
199                                             "is SOAP body")
200           
201        response = self.deserialise(response.envelope.body.elem[0])
202       
203        return response
204
205    @classmethod
206    def fromConfig(cls, cfg, **kw):
207        '''Alternative constructor makes object from config file settings
208        @type cfg: basestring /ConfigParser derived type
209        @param cfg: configuration file path or ConfigParser type object
210        @rtype: ndg.security.common.saml_utils.binding.soap.SOAPBinding
211        @return: new instance of this class
212        '''
213        obj = cls()
214        obj.parseConfig(cfg, **kw)
215       
216        return obj
217
218    def parseConfig(self, cfg, prefix='', section='DEFAULT'):
219        '''Read config file settings
220        @type cfg: basestring /ConfigParser derived type
221        @param cfg: configuration file path or ConfigParser type object
222        @type prefix: basestring
223        @param prefix: prefix for option names e.g. "attributeQuery."
224        @type section: baestring
225        @param section: configuration file section from which to extract
226        parameters.
227        ''' 
228        if isinstance(cfg, basestring):
229            cfgFilePath = path.expandvars(cfg)
230            _cfg = CaseSensitiveConfigParser()
231            _cfg.read(cfgFilePath)
232           
233        elif isinstance(cfg, ConfigParser):
234            _cfg = cfg   
235        else:
236            raise AttributeError('Expecting basestring or ConfigParser type '
237                                 'for "cfg" attribute; got %r type' % type(cfg))
238       
239        prefixLen = len(prefix)
240        for optName, val in _cfg.items(section):
241            if prefix:
242                # Filter attributes based on prefix
243                if optName.startswith(prefix):
244                    setattr(self, optName[prefixLen:], val)
245            else:
246                # No prefix set - attempt to set all attributes   
247                setattr(self, optName, val)
248               
249    def __setattr__(self, name, value):
250        """Enable setting of SOAPBinding.client.responseEnvelopeClass as if it
251        were an attribute of self
252        """
253        try:
254            super(SOAPBinding, self).__setattr__(name, value)
255           
256        except AttributeError:
257            if 'name' == SOAPBinding.RESPONSE_ENVELOPE_CLASS_OPTNAME:
258                if isinstance(value, basestring):
259                    self.client.responseEnvelopeClass = importClass(value)
260                elif issubclass(value, SOAPEnvelopeBase):
261                    self.client.responseEnvelopeClass = value
262                else:
263                    raise TypeError('Expecting string or type instance for %r; '
264                                    'got %r instead.' % (name, type(value)))
265            else:
266                raise
267                   
268    def __getstate__(self):
269        '''Explicit implementation needed with __slots__'''
270        _dict = {}
271        for attrName in SOAPBinding.__slots__:
272            # Ugly hack to allow for derived classes setting private member
273            # variables
274            if attrName.startswith('__'):
275                attrName = "_SOAPBinding" + attrName
276               
277            _dict[attrName] = getattr(self, attrName)
278           
279        return _dict
280       
281    def __setstate__(self, attrDict):
282        '''Explicit implementation needed with __slots__'''
283        for attr, val in attrDict.items():
284            setattr(self, attr, val)
Note: See TracBrowser for help on using the repository browser.