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

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

Refactoring SAML SOAP bindings module to include AuthzDecisionQuery?

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
14import re
15from os import path
16from datetime import datetime, timedelta
17from uuid import uuid4
18from ConfigParser import ConfigParser
19
20from M2Crypto.m2urllib2 import HTTPSHandler
21
22from saml.common import SAMLObject
23from saml.utils import SAMLDateTime
24from saml.saml2.core import (Attribute, AttributeQuery, StatusCode, Response,
25                             Issuer, Subject, SAMLVersion, NameID)
26from saml.xml.etree import AttributeQueryElementTree, ResponseElementTree
27
28from ndg.security.common.saml_utils.esg import EsgSamlNamespaces
29from ndg.security.common.utils import TypedList, str2Bool
30from ndg.security.common.utils.configfileparsers import (
31                                                    CaseSensitiveConfigParser)
32from ndg.security.common.utils.etree import QName   
33from ndg.security.common.X509 import X500DN
34from ndg.security.common.soap import SOAPEnvelopeBase
35from ndg.security.common.soap.etree import SOAPEnvelope
36from ndg.security.common.soap.client import (UrlLib2SOAPClient, 
37                                             UrlLib2SOAPRequest)
38
39# Prevent whole module breaking if this is not available - it's only needed for
40# AttributeQuerySslSOAPBinding
41try:
42    from ndg.security.common.utils.m2crypto import SSLContextProxy
43    _sslContextProxySupport = True
44   
45except ImportError:
46    _sslContextProxySupport = False
47
48
49class SOAPBindingError(Exception):
50    '''Base exception type for client SAML SOAP Binding for Attribute Query'''
51
52
53class SOAPBindingInvalidResponse(SOAPBindingError):
54    '''Raise if the response is invalid'''
55   
56   
57_isIterable = lambda obj: getattr(obj, '__iter__', False) 
58   
59
60class SOAPBinding(object):
61    '''Client SAML SOAP Binding'''
62   
63    isIterable = staticmethod(_isIterable)
64    __slots__ = (
65        "__client",
66        "__requestEnvelopeClass",
67        "__serialise",
68        "__deserialise"
69    )
70   
71    def __init__(self, 
72                 requestEnvelopeClass=SOAPEnvelope,
73                 responseEnvelopeClass=SOAPEnvelope,
74                 serialise=AttributeQueryElementTree.toXML,
75                 deserialise=ResponseElementTree.fromXML,
76                 handlers=(HTTPSHandler,)):
77        '''Create SAML SOAP Client - Nb. serialisation functions assume
78        AttributeQuery/Response'''
79        self.__client = None
80        self.serialise = serialise
81        self.deserialise = deserialise
82       
83        self.client = UrlLib2SOAPClient()
84       
85        # ElementTree based envelope class
86        self.requestEnvelopeClass = requestEnvelopeClass
87        self.client.responseEnvelopeClass = responseEnvelopeClass
88
89        if not SOAPBinding.isIterable(handlers):
90            raise TypeError('Expecting iterable for "handlers" keyword; got %r'
91                            % type(handlers))
92           
93        for handler in handlers:
94            self.client.openerDirector.add_handler(handler())
95
96    def _getSerialise(self):
97        return self.__serialise
98
99    def _setSerialise(self, value):
100        if not callable(value):
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 not callable(value):
113            raise TypeError('Expecting callable for "deserialise"; got %r' % 
114                            value)
115        self.__deserialise = value
116
117    deserialise = property(_getDeserialise, 
118                           _setDeserialise, 
119                           doc="callable to de-serialise response from XML "
120                               "type")
121
122    def _getRequestEnvelopeClass(self):
123        return self.__requestEnvelopeClass
124
125    def _setRequestEnvelopeClass(self, value):
126        if not issubclass(value, SOAPEnvelopeBase):
127            raise TypeError('Expecting %r for "requestEnvelopeClass"; got %r'% 
128                            (SOAPEnvelopeBase, value))
129       
130        self.__requestEnvelopeClass = value
131
132    requestEnvelopeClass = property(_getRequestEnvelopeClass, 
133                                    _setRequestEnvelopeClass, 
134                                    doc="SOAP Envelope Request Class")
135
136    def _getClient(self):
137        return self.__client
138
139    def _setClient(self, value):     
140        if not isinstance(value, UrlLib2SOAPClient):
141            raise TypeError('Expecting %r for "client"; got %r'% 
142                            (UrlLib2SOAPClient, type(value)))
143        self.__client = value
144
145    client = property(_getClient, _setClient, 
146                      doc="SOAP Client object")   
147
148    def send(self, samlObj, uri=None, request=None):
149        '''Make an request/query to a remote SAML service
150       
151        @type samlObj: saml.common.SAMLObject
152        @param samlObj: SAML query/request object
153        @type uri: basestring
154        @param uri: uri of service.  May be omitted if set from request.url
155        @type request: ndg.security.common.soap.UrlLib2SOAPRequest
156        @param request: SOAP request object to which query will be attached
157        defaults to ndg.security.common.soap.client.UrlLib2SOAPRequest
158        '''
159        if not isinstance(samlObj, SAMLObject):
160            raise TypeError('Expecting %r for input attribute query; got %r'
161                            % (SAMLObject, type(samlObj)))
162           
163        if request is None:
164            request = UrlLib2SOAPRequest()           
165            request.envelope = self.requestEnvelopeClass()
166            request.envelope.create()
167           
168        if uri is not None:
169            request.url = uri
170       
171        samlElem = self.serialise(samlObj)
172
173        # Attach query to SOAP body
174        request.envelope.body.elem.append(samlElem)
175           
176        response = self.client.send(request)
177       
178        if len(response.envelope.body.elem) != 1:
179            raise SOAPBindingInvalidResponse("Expecting single child element "
180                                             "is SOAP body")
181           
182        if QName.getLocalPart(response.envelope.body.elem[0].tag)!='Response':
183            raise SOAPBindingInvalidResponse('Expecting "Response" element in '
184                                             'SOAP body')
185           
186        response = self.deserialise(response.envelope.body.elem[0])
187       
188        return response
189
190    @classmethod
191    def fromConfig(cls, cfg, **kw):
192        '''Alternative constructor makes object from config file settings
193        @type cfg: basestring /ConfigParser derived type
194        @param cfg: configuration file path or ConfigParser type object
195        @rtype: ndg.security.common.credentialWallet.AttributeQuery
196        @return: new instance of this class
197        '''
198        obj = cls()
199        obj.parseConfig(cfg, **kw)
200       
201        return obj
202
203    def parseConfig(self, cfg, prefix='', section='DEFAULT'):
204        '''Read config file settings
205        @type cfg: basestring /ConfigParser derived type
206        @param cfg: configuration file path or ConfigParser type object
207        @type prefix: basestring
208        @param prefix: prefix for option names e.g. "attributeQuery."
209        @type section: baestring
210        @param section: configuration file section from which to extract
211        parameters.
212        ''' 
213        if isinstance(cfg, basestring):
214            cfgFilePath = path.expandvars(cfg)
215            _cfg = CaseSensitiveConfigParser()
216            _cfg.read(cfgFilePath)
217           
218        elif isinstance(cfg, ConfigParser):
219            _cfg = cfg   
220        else:
221            raise AttributeError('Expecting basestring or ConfigParser type '
222                                 'for "cfg" attribute; got %r type' % type(cfg))
223       
224        prefixLen = len(prefix)
225        for optName, val in _cfg.items(section):
226            if prefix:
227                # Filter attributes based on prefix
228                if optName.startswith(prefix):
229                    setattr(self, optName[prefixLen:], val)
230            else:
231                # No prefix set - attempt to set all attributes   
232                setattr(self, optName, val)
233       
234    def __getstate__(self):
235        '''Enable pickling for use with beaker.session'''
236        _dict = {}
237        for attrName in SOAPBinding.__slots__:
238            # Ugly hack to allow for derived classes setting private member
239            # variables
240            if attrName.startswith('__'):
241                attrName = "_SOAPBinding" + attrName
242               
243            _dict[attrName] = getattr(self, attrName)
244           
245        return _dict
246       
247    def __setstate__(self, attrDict):
248        '''Specific implementation needed with __slots__'''
249        for attr, val in attrDict.items():
250            setattr(self, attr, val)
251           
252
253class SubjectQueryResponseError(SOAPBindingInvalidResponse):
254    """SAML Response error from Subject Query"""
255    def __init__(self, *arg, **kw):
256        SOAPBindingInvalidResponse.__init__(self, *arg, **kw)
257        self.__response = None
258   
259    def _getResponse(self):
260        '''Gets the response corresponding to this error
261       
262        @return the response
263        '''
264        return self.__response
265
266    def _setResponse(self, value):
267        '''Sets the response corresponding to this error.
268       
269        @param value: the response
270        '''
271        if not isinstance(value, Response):
272            raise TypeError('"response" must be a %r, got %r' % (Response,
273                                                                 type(value)))
274        self.__response = value
275       
276    response = property(fget=_getResponse, fset=_setResponse, 
277                        doc="SAML Response associated with this exception")
Note: See TracBrowser for help on using the repository browser.