source: TI12-security/trunk/NDGSecurity/python/ndg_security_common/ndg/security/common/saml_utils/binding/soap/subjectquery.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/subjectquery.py@6567
Revision 6567, 10.4 KB checked in by pjkersha, 11 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 subject query
2
3NERC DataGrid Project
4"""
5__author__ = "P J Kershaw"
6__date__ = "12/02/10"
7__copyright__ = "(C) 2010 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 datetime import datetime, timedelta
15
16from saml.common import SAMLObject
17from saml.utils import SAMLDateTime
18from saml.saml2.core import (Attribute, AttributeQuery, StatusCode, Response,
19                             Issuer, Subject, SAMLVersion, NameID)
20
21from ndg.security.common.saml_utils.binding.soap import (SOAPBinding,
22    SOAPBindingInvalidResponse)
23
24
25class SubjectQueryResponseError(SOAPBindingInvalidResponse):
26    """SAML Response error from Subject Query"""
27    def __init__(self, *arg, **kw):
28        SOAPBindingInvalidResponse.__init__(self, *arg, **kw)
29        self.__response = None
30   
31    def _getResponse(self):
32        '''Gets the response corresponding to this error
33       
34        @return the response
35        '''
36        return self.__response
37
38    def _setResponse(self, value):
39        '''Sets the response corresponding to this error.
40       
41        @param value: the response
42        '''
43        if not isinstance(value, Response):
44            raise TypeError('"response" must be a %r, got %r' % (Response,
45                                                                 type(value)))
46        self.__response = value
47       
48    response = property(fget=_getResponse, fset=_setResponse, 
49                        doc="SAML Response associated with this exception")
50   
51   
52class SubjectQuerySOAPBinding(SOAPBinding): 
53    """SAML Subject Query SOAP Binding
54    """
55    SUBJECT_ID_OPTNAME = 'subjectID'
56    ISSUER_NAME_OPTNAME = 'issuerName'
57    CLOCK_SKEW_OPTNAME = 'clockSkewTolerance'
58    VERIFY_TIME_CONDITIONS_OPTNAME = 'verifyTimeConditions'
59   
60    CONFIG_FILE_OPTNAMES = (
61        SUBJECT_ID_OPTNAME,
62        ISSUER_NAME_OPTNAME,                 
63        CLOCK_SKEW_OPTNAME,
64        VERIFY_TIME_CONDITIONS_OPTNAME           
65    )
66   
67    __PRIVATE_ATTR_PREFIX = "__"
68    __slots__ = tuple([__PRIVATE_ATTR_PREFIX + i
69                       for i in CONFIG_FILE_OPTNAMES])
70    del i
71   
72    def __init__(self, **kw):
73        '''Create SOAP Client for SAML Attribute Query'''
74        self.__issuerName = None
75        self.__issuerFormat = Issuer.X509_SUBJECT
76        self.__nameIdFormat = NameID.UNSPECIFIED
77        self.__clockSkewTolerance = timedelta(seconds=0.)
78        self.__verifyTimeConditions = True
79       
80        super(SubjectQuerySOAPBinding, self).__init__(**kw)
81
82    def _getNameIdFormat(self):
83        return self.__nameIdFormat
84
85    def _setNameIdFormat(self, value):
86        self.__nameIdFormat = value
87
88    nameIdFormat = property(_getNameIdFormat, _setNameIdFormat, 
89                            doc="Subject Name ID format")
90
91    def _getIssuerFormat(self):
92        return self.__issuerFormat
93
94    def _setIssuerFormat(self, value):
95        if not isinstance(value, basestring):
96            raise TypeError('Expecting string type for "issuerFormat"; got %r '
97                            'instead' % type(value))
98        self.__issuerFormat = value
99
100    issuerFormat = property(_getIssuerFormat, _setIssuerFormat, 
101                            doc="Issuer format")
102
103    def _getVerifyTimeConditions(self):
104        return self.__verifyTimeConditions
105
106    def _setVerifyTimeConditions(self, value):
107        if isinstance(value, bool):
108            self.__verifyTimeConditions = value
109           
110        if isinstance(value, basestring):
111            self.__verifyTimeConditions = str2Bool(value)
112        else:
113            raise TypeError('Expecting bool or string type for '
114                            '"verifyTimeConditions"; got %r instead' % 
115                            type(value))
116
117    verifyTimeConditions = property(_getVerifyTimeConditions, 
118                                    _setVerifyTimeConditions, 
119                                    doc='Set to True to verify any time '
120                                        'Conditions set in the returned '
121                                        'response assertions')
122       
123    def _getSubjectID(self):
124        return self.__subjectID
125
126    def _setSubjectID(self, value):
127        if not isinstance(value, basestring):
128            raise TypeError('Expecting string type for "subjectID"; got %r '
129                            'instead' % type(value))
130        self.__subjectID = value
131
132    subjectID = property(_getSubjectID, _setSubjectID, 
133                         doc="ID to be sent as query subject") 
134
135    def _getIssuerName(self):
136        return self.__issuerName
137
138    def _setIssuerName(self, value):
139        if not isinstance(value, basestring):
140            raise TypeError('Expecting string type for "issuerName"; '
141                            'got %r instead' % type(value))
142           
143        self.__issuerName = value
144
145    issuerName = property(_getIssuerName, _setIssuerName, 
146                        doc="Distinguished Name of issuer of SAML Attribute "
147                            "Query to Attribute Authority")
148
149    def _getClockSkewTolerance(self):
150        return self.__clockSkewTolerance
151
152    def _setClockSkewTolerance(self, value):
153        if isinstance(value, (float, int, long)):
154            self.__clockSkewTolerance = timedelta(seconds=value)
155           
156        elif isinstance(value, basestring):
157            self.__clockSkewTolerance = timedelta(seconds=float(value))
158        else:
159            raise TypeError('Expecting float, int, long or string type for '
160                            '"clockSkewTolerance"; got %r' % type(value))
161
162    clockSkewTolerance = property(fget=_getClockSkewTolerance, 
163                                  fset=_setClockSkewTolerance, 
164                                  doc="Allow a tolerance in seconds for SAML "
165                                      "Query issueInstant parameter check and "
166                                      "assertion condition notBefore and "
167                                      "notOnOrAfter times to allow for clock "
168                                      "skew") 
169
170    def _createQuery(self, queryClass=SubjectQuery):
171        """ Create a SAML SubjectQuery derived type instance
172        @param queryClass: query type to create - must be
173        saml.saml2.core.SubjectQuery type
174        @type queryClass: type
175        @return: query instance
176        @rtype: saml.saml2.core.SubjectQuery
177        """
178        if not isinstance(queryClass, SubjectQuery):
179            raise TypeError('Query class %r is not a SubjectQuery derived type'
180                            % queryClass)
181           
182        query = queryClass()
183        query.version = SAMLVersion(SAMLVersion.VERSION_20)
184        query.id = str(uuid4())
185        query.issueInstant = datetime.utcnow()
186       
187        if self.issuerName is None:
188            raise AttributeError('No issuer DN has been set for SAML Query')
189       
190        query.issuer = Issuer()
191        query.issuer.format = self.issuerFormat
192        query.issuer.value = self.issuerName
193                       
194        query.subject = Subject() 
195        query.subject.nameID = NameID()
196        query.subject.nameID.format = self.nameIdFormat
197        query.subject.nameID.value = self.subjectID
198           
199        return query
200
201    def send(self, **kw):
202        '''Make an attribute query to a remote SAML service
203       
204        @type uri: basestring
205        @param uri: uri of service.  May be omitted if set from request.url
206        @type request: ndg.security.common.soap.UrlLib2SOAPRequest
207        @param request: SOAP request object to which query will be attached
208        defaults to ndg.security.common.soap.client.UrlLib2SOAPRequest
209        '''
210        query = self._createQuery()
211           
212        response = super(SubjectQuerySOAPBinding, self).send(query, **kw)
213
214        # Perform validation
215        if response.status.statusCode.value != StatusCode.SUCCESS_URI:
216            msg = ('Return status code flagged an error.  The message is: %r' %
217                   response.status.statusMessage.value)
218            samlRespError = SubjectQueryResponseError(msg)
219            samlRespError.response = response
220            raise samlRespError
221       
222        # Check Query ID matches the query ID the service received
223        if response.inResponseTo != query.id:
224            msg = ('Response in-response-to ID %r, doesn\'t match the original '
225                   'query ID, %r' % (response.inResponseTo, query.id))
226           
227            samlRespError = SubjectQueryResponseError(msg)
228            samlRespError.response = response
229            raise samlRespError
230       
231        utcNow = datetime.utcnow() + self.clockSkewTolerance
232        if response.issueInstant > utcNow:
233            msg = ('SAML Attribute Response issueInstant [%s] is after '
234                   'the current clock time [%s]' % 
235                   (query.issueInstant, SAMLDateTime.toString(utcNow)))
236           
237            samlRespError = SubjectQueryResponseError(msg)                 
238            samlRespError.response = response
239            raise samlRespError
240       
241        for assertion in response.assertions:
242            if self.verifyTimeConditions and assertion.conditions is not None:
243                if utcNow < assertion.conditions.notBefore:           
244                    msg = ('The current clock time [%s] is before the SAML '
245                           'Attribute Response assertion conditions not before '
246                           'time [%s]' % 
247                           (SAMLDateTime.toString(utcNow),
248                            assertion.conditions.notBefore))
249                             
250                    samlRespError = SubjectQueryResponseError(msg)
251                    samlRespError.response = response
252                    raise samlRespError
253                 
254                if utcNow >= assertion.conditions.notOnOrAfter:           
255                    msg = ('The current clock time [%s] is on or after the '
256                           'SAML Attribute Response assertion conditions not '
257                           'on or after time [%s]' % 
258                           (SAMLDateTime.toString(utcNow),
259                            response.assertion.conditions.notOnOrAfter))
260                   
261                    samlRespError = SubjectQueryResponseError(msg) 
262                    samlRespError.response = response
263                    raise samlRespError   
264           
265        return response
Note: See TracBrowser for help on using the repository browser.