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

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