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

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@6584
Revision 6584, 10.4 KB checked in by pjkersha, 12 years ago (diff)
  • Fixes to SAML Attribute Query client.
  • Work query to ESG Authz service
  • fixes for MyProxy? SAML callout
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    SOAP_ACTION = 'http://www.oasis-open.org/committees/security'
42   
43    REQUEST_ENVELOPE_CLASS_OPTNAME = 'requestEnvelopeClass'
44    RESPONSE_ENVELOPE_CLASS_OPTNAME = 'responseEnvelopeClass'
45    SERIALISE_OPTNAME = 'serialise'
46    DESERIALISE_OPTNAME = 'deserialise' 
47   
48    CONFIG_FILE_OPTNAMES = (
49        REQUEST_ENVELOPE_CLASS_OPTNAME,
50        RESPONSE_ENVELOPE_CLASS_OPTNAME,
51        SERIALISE_OPTNAME,
52        DESERIALISE_OPTNAME
53    )
54   
55    __PRIVATE_ATTR_PREFIX = "__"
56    __slots__ = tuple([__PRIVATE_ATTR_PREFIX + i
57                       for i in CONFIG_FILE_OPTNAMES + ("client",)])
58    del i
59   
60    isIterable = staticmethod(_isIterable)
61   
62    def __init__(self, 
63                 requestEnvelopeClass=SOAPEnvelope,
64                 responseEnvelopeClass=SOAPEnvelope,
65                 serialise=None,
66                 deserialise=None,
67                 handlers=()):
68        '''Create SAML SOAP Client - Nb. serialisation functions must be set
69        before send()ing the request'''
70        self.__client = None
71        self.__serialise = None
72        self.__deserialise = None
73       
74        if serialise is not None:
75            self.serialise = serialise
76           
77        if deserialise is not None:
78            self.deserialise = deserialise
79       
80        self.client = UrlLib2SOAPClient()
81        self.client.httpHeader['SOAPAction'] = SOAPBinding.SOAP_ACTION
82       
83        # Configurable envelope classes
84        self.requestEnvelopeClass = requestEnvelopeClass
85        self.client.responseEnvelopeClass = responseEnvelopeClass
86
87        if not SOAPBinding.isIterable(handlers):
88            raise TypeError('Expecting iterable for "handlers" keyword; got %r'
89                            % type(handlers))
90           
91        for handler in handlers:
92            self.client.openerDirector.add_handler(handler())
93
94    def _getSerialise(self):
95        return self.__serialise
96
97    def _setSerialise(self, value):
98        if isinstance(value, basestring):
99            self.__serialise = importModuleObject(value)
100           
101        elif callable(value):
102            self.__serialise = value
103        else:
104            raise TypeError('Expecting callable for "serialise"; got %r' % 
105                            value)
106
107    serialise = property(_getSerialise, _setSerialise, 
108                         doc="callable to serialise request into XML type")
109
110    def _getDeserialise(self):
111        return self.__deserialise
112
113    def _setDeserialise(self, value):
114        if isinstance(value, basestring):
115            self.__deserialise = importModuleObject(value)
116           
117        elif callable(value):
118            self.__deserialise = value
119        else:
120            raise TypeError('Expecting callable for "deserialise"; got %r' % 
121                            value)
122       
123
124    deserialise = property(_getDeserialise, 
125                           _setDeserialise, 
126                           doc="callable to de-serialise response from XML "
127                               "type")
128
129    def _getRequestEnvelopeClass(self):
130        return self.__requestEnvelopeClass
131
132    def _setRequestEnvelopeClass(self, value):
133        if isinstance(value, basestring):
134            self.client.responseEnvelopeClass = importClass(value)
135           
136        elif issubclass(value, SOAPEnvelopeBase):
137            self.client.responseEnvelopeClass = value
138        else:
139            raise TypeError('Expecting %r derived type or string for '
140                            '"requestEnvelopeClass" attribute; got %r' % 
141                            (SOAPEnvelopeBase, value))
142       
143        self.__requestEnvelopeClass = value
144
145    requestEnvelopeClass = property(_getRequestEnvelopeClass, 
146                                    _setRequestEnvelopeClass, 
147                                    doc="SOAP Envelope Request Class")
148
149    def _getClient(self):
150        return self.__client
151
152    def _setClient(self, value):     
153        if not isinstance(value, UrlLib2SOAPClient):
154            raise TypeError('Expecting %r for "client"; got %r' % 
155                            (UrlLib2SOAPClient, type(value)))
156        self.__client = value
157
158    client = property(_getClient, _setClient, 
159                      doc="SOAP Client object")   
160
161    def send(self, samlObj, uri=None, request=None):
162        '''Make an request/query to a remote SAML service
163       
164        @type samlObj: saml.common.SAMLObject
165        @param samlObj: SAML query/request object
166        @type uri: basestring
167        @param uri: uri of service.  May be omitted if set from request.url
168        @type request: ndg.security.common.soap.UrlLib2SOAPRequest
169        @param request: SOAP request object to which query will be attached
170        defaults to ndg.security.common.soap.client.UrlLib2SOAPRequest
171        '''
172        if self.serialise is None:
173            raise AttributeError('No "serialise" method set to serialise the '
174                                 'request')
175
176        if self.deserialise is None:
177            raise AttributeError('No "deserialise" method set to deserialise '
178                                 'the response')
179           
180        if not isinstance(samlObj, SAMLObject):
181            raise TypeError('Expecting %r for input attribute query; got %r'
182                            % (SAMLObject, type(samlObj)))
183           
184        if request is None:
185            request = UrlLib2SOAPRequest()
186            request.envelope = self.requestEnvelopeClass()
187            request.envelope.create()
188           
189        if uri is not None:
190            request.url = uri
191       
192        samlElem = self.serialise(samlObj)
193           
194        # Attach query to SOAP body
195        request.envelope.body.elem.append(samlElem)
196           
197        response = self.client.send(request)
198       
199        if len(response.envelope.body.elem) != 1:
200            raise SOAPBindingInvalidResponse("Expecting single child element "
201                                             "is SOAP body")
202           
203        response = self.deserialise(response.envelope.body.elem[0])
204       
205        return response
206
207    @classmethod
208    def fromConfig(cls, cfg, **kw):
209        '''Alternative constructor makes object from config file settings
210        @type cfg: basestring /ConfigParser derived type
211        @param cfg: configuration file path or ConfigParser type object
212        @rtype: ndg.security.common.saml_utils.binding.soap.SOAPBinding
213        @return: new instance of this class
214        '''
215        obj = cls()
216        obj.parseConfig(cfg, **kw)
217       
218        return obj
219
220    def parseConfig(self, cfg, prefix='', section='DEFAULT'):
221        '''Read config file settings
222        @type cfg: basestring /ConfigParser derived type
223        @param cfg: configuration file path or ConfigParser type object
224        @type prefix: basestring
225        @param prefix: prefix for option names e.g. "attributeQuery."
226        @type section: baestring
227        @param section: configuration file section from which to extract
228        parameters.
229        ''' 
230        if isinstance(cfg, basestring):
231            cfgFilePath = path.expandvars(cfg)
232            _cfg = CaseSensitiveConfigParser()
233            _cfg.read(cfgFilePath)
234           
235        elif isinstance(cfg, ConfigParser):
236            _cfg = cfg   
237        else:
238            raise AttributeError('Expecting basestring or ConfigParser type '
239                                 'for "cfg" attribute; got %r type' % type(cfg))
240       
241        prefixLen = len(prefix)
242        for optName, val in _cfg.items(section):
243            if prefix:
244                # Filter attributes based on prefix
245                if optName.startswith(prefix):
246                    setattr(self, optName[prefixLen:], val)
247            else:
248                # No prefix set - attempt to set all attributes   
249                setattr(self, optName, val)
250               
251    def __setattr__(self, name, value):
252        """Enable setting of SOAPBinding.client.responseEnvelopeClass as if it
253        were an attribute of self
254        """
255        try:
256            super(SOAPBinding, self).__setattr__(name, value)
257           
258        except AttributeError:
259            if 'name' == SOAPBinding.RESPONSE_ENVELOPE_CLASS_OPTNAME:
260                if isinstance(value, basestring):
261                    self.client.responseEnvelopeClass = importClass(value)
262                elif issubclass(value, SOAPEnvelopeBase):
263                    self.client.responseEnvelopeClass = value
264                else:
265                    raise TypeError('Expecting string or type instance for %r; '
266                                    'got %r instead.' % (name, type(value)))
267            else:
268                raise
269                   
270    def __getstate__(self):
271        '''Explicit implementation needed with __slots__'''
272        _dict = {}
273        for attrName in SOAPBinding.__slots__:
274            # Ugly hack to allow for derived classes setting private member
275            # variables
276            if attrName.startswith('__'):
277                attrName = "_SOAPBinding" + attrName
278               
279            _dict[attrName] = getattr(self, attrName)
280           
281        return _dict
282       
283    def __setstate__(self, attrDict):
284        '''Explicit implementation needed with __slots__'''
285        for attr, val in attrDict.items():
286            setattr(self, attr, val)
Note: See TracBrowser for help on using the repository browser.