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

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@6567
Revision 6567, 8.8 KB checked in by pjkersha, 10 years ago (diff)

Refactoring SAML SOAP bindings module to include AuthzDecisionQuery?:

  • improved package structure
  • Generic SubjectQuerySOAPBinding type which AuthzDecision? and Attribute query types extend.
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.configfileparsers import (
20                                                    CaseSensitiveConfigParser)
21from ndg.security.common.utils.etree import QName   
22from ndg.security.common.X509 import X500DN
23from ndg.security.common.soap import SOAPEnvelopeBase
24from ndg.security.common.soap.etree import SOAPEnvelope
25from ndg.security.common.soap.client import (UrlLib2SOAPClient, 
26                                             UrlLib2SOAPRequest)
27
28
29class SOAPBindingError(Exception):
30    '''Base exception type for client SAML SOAP Binding for Attribute Query'''
31
32
33class SOAPBindingInvalidResponse(SOAPBindingError):
34    '''Raise if the response is invalid'''
35   
36   
37_isIterable = lambda obj: getattr(obj, '__iter__', False) 
38   
39
40class SOAPBinding(object):
41    '''Client SAML SOAP Binding'''
42   
43    isIterable = staticmethod(_isIterable)
44    __slots__ = (
45        "__client",
46        "__requestEnvelopeClass",
47        "__serialise",
48        "__deserialise"
49    )
50   
51    def __init__(self, 
52                 requestEnvelopeClass=SOAPEnvelope,
53                 responseEnvelopeClass=SOAPEnvelope,
54                 serialise=None,
55                 deserialise=None,
56                 handlers=(HTTPSHandler,)):
57        '''Create SAML SOAP Client - Nb. serialisation functions must be set
58        before send()ing the request'''
59        self.__client = None
60        self.__serialise = None
61        self.__deserialise = None
62       
63        if serialise is not None:
64            self.serialise = serialise
65           
66        if deserialise is not None:
67            self.deserialise = deserialise
68       
69        self.client = UrlLib2SOAPClient()
70       
71        # ElementTree based envelope class
72        self.requestEnvelopeClass = requestEnvelopeClass
73        self.client.responseEnvelopeClass = responseEnvelopeClass
74
75        if not SOAPBinding.isIterable(handlers):
76            raise TypeError('Expecting iterable for "handlers" keyword; got %r'
77                            % type(handlers))
78           
79        for handler in handlers:
80            self.client.openerDirector.add_handler(handler())
81
82    def _getSerialise(self):
83        return self.__serialise
84
85    def _setSerialise(self, value):
86        if not callable(value):
87            raise TypeError('Expecting callable for "serialise"; got %r' % 
88                            value)
89        self.__serialise = value
90
91    serialise = property(_getSerialise, _setSerialise, 
92                         doc="callable to serialise request into XML type")
93
94    def _getDeserialise(self):
95        return self.__deserialise
96
97    def _setDeserialise(self, value):
98        if not callable(value):
99            raise TypeError('Expecting callable for "deserialise"; got %r' % 
100                            value)
101        self.__deserialise = value
102
103    deserialise = property(_getDeserialise, 
104                           _setDeserialise, 
105                           doc="callable to de-serialise response from XML "
106                               "type")
107
108    def _getRequestEnvelopeClass(self):
109        return self.__requestEnvelopeClass
110
111    def _setRequestEnvelopeClass(self, value):
112        if not issubclass(value, SOAPEnvelopeBase):
113            raise TypeError('Expecting %r for "requestEnvelopeClass"; got %r' % 
114                            (SOAPEnvelopeBase, value))
115       
116        self.__requestEnvelopeClass = value
117
118    requestEnvelopeClass = property(_getRequestEnvelopeClass, 
119                                    _setRequestEnvelopeClass, 
120                                    doc="SOAP Envelope Request Class")
121
122    def _getClient(self):
123        return self.__client
124
125    def _setClient(self, value):     
126        if not isinstance(value, UrlLib2SOAPClient):
127            raise TypeError('Expecting %r for "client"; got %r' % 
128                            (UrlLib2SOAPClient, type(value)))
129        self.__client = value
130
131    client = property(_getClient, _setClient, 
132                      doc="SOAP Client object")   
133
134    def send(self, samlObj, uri=None, request=None):
135        '''Make an request/query to a remote SAML service
136       
137        @type samlObj: saml.common.SAMLObject
138        @param samlObj: SAML query/request object
139        @type uri: basestring
140        @param uri: uri of service.  May be omitted if set from request.url
141        @type request: ndg.security.common.soap.UrlLib2SOAPRequest
142        @param request: SOAP request object to which query will be attached
143        defaults to ndg.security.common.soap.client.UrlLib2SOAPRequest
144        '''
145        if self.serialise is None:
146            raise AttributeError('No "serialise" method set to serialise the '
147                                 'request')
148
149        if self.deserialise is None:
150            raise AttributeError('No "deserialise" method set to deserialise '
151                                 'the response')
152           
153        if not isinstance(samlObj, SAMLObject):
154            raise TypeError('Expecting %r for input attribute query; got %r'
155                            % (SAMLObject, type(samlObj)))
156           
157        if request is None:
158            request = UrlLib2SOAPRequest()           
159            request.envelope = self.requestEnvelopeClass()
160            request.envelope.create()
161           
162        if uri is not None:
163            request.url = uri
164       
165        samlElem = self.serialise(samlObj)
166
167        # Attach query to SOAP body
168        request.envelope.body.elem.append(samlElem)
169           
170        response = self.client.send(request)
171       
172        if len(response.envelope.body.elem) != 1:
173            raise SOAPBindingInvalidResponse("Expecting single child element "
174                                             "is SOAP body")
175           
176        if QName.getLocalPart(response.envelope.body.elem[0].tag)!='Response':
177            raise SOAPBindingInvalidResponse('Expecting "Response" element in '
178                                             'SOAP body')
179           
180        response = self.deserialise(response.envelope.body.elem[0])
181       
182        return response
183
184    @classmethod
185    def fromConfig(cls, cfg, **kw):
186        '''Alternative constructor makes object from config file settings
187        @type cfg: basestring /ConfigParser derived type
188        @param cfg: configuration file path or ConfigParser type object
189        @rtype: ndg.security.common.saml_utils.binding.soap.SOAPBinding
190        @return: new instance of this class
191        '''
192        obj = cls()
193        obj.parseConfig(cfg, **kw)
194       
195        return obj
196
197    def parseConfig(self, cfg, prefix='', section='DEFAULT'):
198        '''Read config file settings
199        @type cfg: basestring /ConfigParser derived type
200        @param cfg: configuration file path or ConfigParser type object
201        @type prefix: basestring
202        @param prefix: prefix for option names e.g. "attributeQuery."
203        @type section: baestring
204        @param section: configuration file section from which to extract
205        parameters.
206        ''' 
207        if isinstance(cfg, basestring):
208            cfgFilePath = path.expandvars(cfg)
209            _cfg = CaseSensitiveConfigParser()
210            _cfg.read(cfgFilePath)
211           
212        elif isinstance(cfg, ConfigParser):
213            _cfg = cfg   
214        else:
215            raise AttributeError('Expecting basestring or ConfigParser type '
216                                 'for "cfg" attribute; got %r type' % type(cfg))
217       
218        prefixLen = len(prefix)
219        for optName, val in _cfg.items(section):
220            if prefix:
221                # Filter attributes based on prefix
222                if optName.startswith(prefix):
223                    setattr(self, optName[prefixLen:], val)
224            else:
225                # No prefix set - attempt to set all attributes   
226                setattr(self, optName, val)
227       
228    def __getstate__(self):
229        '''Explicit implementation needed with __slots__'''
230        _dict = {}
231        for attrName in SOAPBinding.__slots__:
232            # Ugly hack to allow for derived classes setting private member
233            # variables
234            if attrName.startswith('__'):
235                attrName = "_SOAPBinding" + attrName
236               
237            _dict[attrName] = getattr(self, attrName)
238           
239        return _dict
240       
241    def __setstate__(self, attrDict):
242        '''Explicit implementation needed with __slots__'''
243        for attr, val in attrDict.items():
244            setattr(self, attr, val)
Note: See TracBrowser for help on using the repository browser.