def get_etags_and_matchers(self, request): etags = None if_none_match = request.META.get(prepare_header_name("if-none-match")) if_match = request.META.get(prepare_header_name("if-match")) if if_none_match or if_match: # There can be more than one ETag in the request, so we # consider the list of values. try: etags = parse_etags(if_none_match or if_match) except ValueError: # In case of invalid etag ignore all ETag headers. # Apparently Opera sends invalidly quoted headers at times # (we should be returning a 400 response, but that's a # little extreme) -- this is Django bug #10681. if_none_match = None if_match = None return etags, if_none_match, if_match
def is_valid_bulk_operation(self): if extensions_api_settings.DEFAULT_BULK_OPERATION_HEADER_NAME: header_name = utils.prepare_header_name(extensions_api_settings.DEFAULT_BULK_OPERATION_HEADER_NAME) return bool(self.request.META.get(header_name, None)), { 'detail': 'Header \'{0}\' should be provided for bulk operation.'.format( extensions_api_settings.DEFAULT_BULK_OPERATION_HEADER_NAME ) } else: return True, {}
def setUp(self): self.user = User.objects.create( id=1, name='Gennady', age=24, last_name='Chibisov', email='*****@*****.**', password='******' ) self.headers = { utils.prepare_header_name(extensions_api_settings.DEFAULT_BULK_OPERATION_HEADER_NAME): 'true' }
def setUp(self): self.comments = [ Comment.objects.create( id=1, email='*****@*****.**' ), Comment.objects.create( id=2, email='*****@*****.**' ) ] self.protection_headers = { utils.prepare_header_name(extensions_api_settings.DEFAULT_BULK_OPERATION_HEADER_NAME): 'true' }
def run_for_methods(self, methods, condition_failed_status): for method in methods: for exp in self.experiments: headers = { prepare_header_name(self.header_name): exp['header_value'] } request = getattr(factory, method.lower())('', **headers) response = self.view_instance.get(request) base_msg = ( 'For "{method}" and {header_name} value {header_value} condition should' ).format( method=method, header_name=self.header_name, header_value=exp['header_value'], ) if exp['should_fail']: msg = base_msg + ( ' fail and response must be returned with {condition_failed_status} status. ' 'But it is {response_status}').format( condition_failed_status=condition_failed_status, response_status=response.status_code) self.assertEqual(response.status_code, condition_failed_status, msg=msg) msg = base_msg + ' fail and response must be empty' self.assertEqual(response.data, None, msg=msg) msg = ( 'If precondition failed, then Etag must always be added to response. But it is {0}' ).format(response.get('Etag')) self.assertEqual(response.get('Etag'), self.expected_etag_value, msg=msg) else: msg = base_msg + ( ' not fail and response must be returned with 200 status. ' 'But it is "{response_status}"').format( response_status=response.status_code) self.assertEqual(response.status_code, status.HTTP_200_OK, msg=msg) msg = base_msg + 'not fail and response must be filled' self.assertEqual(response.data, 'Response from method', msg=msg) self.assertEqual(response.get('Etag'), self.expected_etag_value, msg=msg)
def run_for_methods(self, methods, condition_failed_status, experiments=None): for method in methods: if experiments is None: experiments = self.experiments for exp in experiments: headers = { prepare_header_name(self.header_name): exp['header_value'] } request = getattr(factory, method.lower())('', **headers) response = getattr(self.view_instance, method.lower())(request) base_msg = ( 'For "{method}" and {header_name} value {header_value} condition should' ).format( method=method, header_name=self.header_name, header_value=exp['header_value'], ) if exp['should_fail']: msg = base_msg + ( ' fail and response must be returned with {condition_failed_status} status. ' 'But it is {response_status}' ).format(condition_failed_status=condition_failed_status, response_status=response.status_code) self.assertEqual(response.status_code, condition_failed_status, msg=msg) msg = base_msg + ' fail and response must be empty' self.assertEqual(response.data, None, msg=msg) msg = ( 'If precondition failed, then Etag must always be added to response. But it is {0}' ).format(response.get('Etag')) self.assertEqual(response.get('Etag'), self.expected_etag_value, msg=msg) else: if method.lower() == 'delete': success_status = status.HTTP_204_NO_CONTENT elif method.lower() == 'post': success_status = status.HTTP_201_CREATED else: success_status = status.HTTP_200_OK msg = base_msg + ( ' not fail and response must be returned with %s status. ' 'But it is "{response_status}"' ).format(success_status, response_status=response.status_code) self.assertEqual(response.status_code, success_status, msg=msg) msg = base_msg + 'not fail and response must be filled' self.assertEqual(response.data, 'Response from %s method' % method.upper(), msg=msg) self.assertEqual(response.get('Etag'), self.expected_etag_value, msg=msg)
def evaluate_preconditions(self, request): """Evaluate whether the precondition for the request is met.""" if request.method.upper() in self.precondition_map.keys(): required_headers = self.precondition_map.get(request.method.upper(), []) # check the required headers for header in required_headers: if not request.META.get(prepare_header_name(header)): # raise an error for each header that does not match logger.warning('Precondition required: %s', request.path, extra={ 'status_code': status.HTTP_428_PRECONDITION_REQUIRED, 'request': request } ) # raise an RFC 6585 compliant exception raise PreconditionRequiredException(detail='Precondition required. This "%s" request ' 'is required to be conditional. ' 'Try again using "%s".' % (request.method, header) ) return True
def evaluate_preconditions(self, request): """Evaluate whether the precondition for the request is met.""" if request.method.upper() in self.precondition_map.keys(): required_headers = self.precondition_map.get( request.method.upper(), []) # check the required headers for header in required_headers: if not request.META.get(prepare_header_name(header)): # raise an error for each header that does not match logger.warning('Precondition required: %s', request.path, extra={ 'status_code': status.HTTP_428_PRECONDITION_REQUIRED, 'request': request } ) # raise an RFC 6585 compliant exception raise PreconditionRequiredException(detail='Precondition required. This "%s" request ' 'is required to be conditional. ' 'Try again using "%s".' % ( request.method, header) ) return True
def test_precondition_decorator_unsafe_methods_if_none_match(self): def dummy_etag_func(**kwargs): return 'some_etag' class TestView(views.APIView): @api_etag(dummy_etag_func) def put(self, request, *args, **kwargs): return Response('Response from PUT method') @api_etag(dummy_etag_func) def patch(self, request, *args, **kwargs): return Response('Response from PATCH method') @api_etag(dummy_etag_func) def delete(self, request, *args, **kwargs): return Response('Response from DELETE method', status=status.HTTP_204_NO_CONTENT) headers = {prepare_header_name('if-none-match'): 'some_etag'} view_instance = TestView() with self.assertRaises(PreconditionRequiredException) as cm: view_instance.put(request=factory.put('', **headers)) self.assertEqual(cm.exception.status_code, status.HTTP_428_PRECONDITION_REQUIRED) self.assertIsNotNone(cm.exception.detail) with self.assertRaises(PreconditionRequiredException) as cm: view_instance.patch(request=factory.patch('', **headers)) self.assertEqual(cm.exception.status_code, status.HTTP_428_PRECONDITION_REQUIRED) self.assertIsNotNone(cm.exception.detail) with self.assertRaises(PreconditionRequiredException) as cm: view_instance.delete(request=factory.delete('', **headers)) self.assertEqual(cm.exception.status_code, status.HTTP_428_PRECONDITION_REQUIRED) self.assertIsNotNone(cm.exception.detail)
def test_precondition_decorator_unsafe_methods_if_none_match(self): def dummy_etag_func(**kwargs): return 'some_etag' class TestView(views.APIView): @api_etag(dummy_etag_func) def put(self, request, *args, **kwargs): return Response('Response from PUT method') @api_etag(dummy_etag_func) def patch(self, request, *args, **kwargs): return Response('Response from PATCH method') @api_etag(dummy_etag_func) def delete(self, request, *args, **kwargs): return Response('Response from DELETE method', status=status.HTTP_204_NO_CONTENT) headers = { prepare_header_name('if-none-match'): 'some_etag' } view_instance = TestView() with self.assertRaises(PreconditionRequiredException) as cm: view_instance.put(request=factory.put('', **headers)) self.assertEqual(cm.exception.status_code, status.HTTP_428_PRECONDITION_REQUIRED) self.assertIsNotNone(cm.exception.detail) with self.assertRaises(PreconditionRequiredException) as cm: view_instance.patch(request=factory.patch('', **headers)) self.assertEqual(cm.exception.status_code, status.HTTP_428_PRECONDITION_REQUIRED) self.assertIsNotNone(cm.exception.detail) with self.assertRaises(PreconditionRequiredException) as cm: view_instance.delete(request=factory.delete('', **headers)) self.assertEqual(cm.exception.status_code, status.HTTP_428_PRECONDITION_REQUIRED) self.assertIsNotNone(cm.exception.detail)
def prepare_key_for_value_retrieving(self, key): from rest_framework_extensions.utils import prepare_header_name return prepare_header_name(key.lower()) # Accept-Language => http_accept_language
def test_strips_whitespaces(self): self.assertEqual(prepare_header_name(' Accept-Language '), 'HTTP_ACCEPT_LANGUAGE')
def test_adds_http_prefix(self): self.assertEqual(prepare_header_name('Accept-Language'), 'HTTP_ACCEPT_LANGUAGE')
def test_upper(self): self.assertEqual(prepare_header_name('Accept'), 'HTTP_ACCEPT')
def test_replace_dash_with_underscores(self): self.assertEqual(prepare_header_name('Accept-Language'), 'HTTP_ACCEPT_LANGUAGE')
def test_replace_dash_with_underscores(self): self.assertEqual( prepare_header_name('Accept-Language'), 'HTTP_ACCEPT_LANGUAGE')
def test_strips_whitespaces(self): self.assertEqual( prepare_header_name(' Accept-Language '), 'HTTP_ACCEPT_LANGUAGE')
def test_adds_http_prefix(self): self.assertEqual( prepare_header_name('Accept-Language'), 'HTTP_ACCEPT_LANGUAGE')