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

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@6572
Revision 6572, 10.2 KB checked in by pjkersha, 11 years ago (diff)

Working refactored Attribute Authority Client unit tests.

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.