From 5bd575de7b4967f000cf9d4692d7f6c3c5091ab7 Mon Sep 17 00:00:00 2001 From: Boris Pleshakov Date: Thu, 16 Jan 2020 19:27:27 +0300 Subject: [PATCH 1/8] Validate that body and url id match. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A server MUST return 409 Conflict when processing a PATCH request in which the resource object’s type and id do not match the server’s endpoint. --- rest_framework_json_api/parsers.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rest_framework_json_api/parsers.py b/rest_framework_json_api/parsers.py index 2742302c..de69ea63 100644 --- a/rest_framework_json_api/parsers.py +++ b/rest_framework_json_api/parsers.py @@ -139,6 +139,14 @@ def parse(self, stream, media_type=None, parser_context=None): if not data.get('id') and request.method in ('PATCH', 'PUT'): raise ParseError("The resource identifier object must contain an 'id' member") + if request.method in ('PATCH', 'PUT') and data.get('id') != view.kwargs[view.lookup_field]: + raise exceptions.Conflict( + "The resource object's id ({data_id}) does not match url ({url_id})}".format( + data_id=data.get('id'), + url_id=view.kwargs[view.lookup_field] + ) + ) + # Construct the return data serializer_class = getattr(view, 'serializer_class', None) parsed_data = {'id': data.get('id')} if 'id' in data else {} From 6bec3a5d737c6aafede26bfb593363e65be3cfb1 Mon Sep 17 00:00:00 2001 From: Boris Pleshakov Date: Thu, 16 Jan 2020 19:37:15 +0300 Subject: [PATCH 2/8] f typo --- rest_framework_json_api/parsers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework_json_api/parsers.py b/rest_framework_json_api/parsers.py index de69ea63..f98d3574 100644 --- a/rest_framework_json_api/parsers.py +++ b/rest_framework_json_api/parsers.py @@ -141,7 +141,7 @@ def parse(self, stream, media_type=None, parser_context=None): if request.method in ('PATCH', 'PUT') and data.get('id') != view.kwargs[view.lookup_field]: raise exceptions.Conflict( - "The resource object's id ({data_id}) does not match url ({url_id})}".format( + "The resource object's id ({data_id}) does not match url ({url_id})".format( data_id=data.get('id'), url_id=view.kwargs[view.lookup_field] ) From 2b937b59ecdc57ac4398c6e1148a7c22bbd466da Mon Sep 17 00:00:00 2001 From: Boris Pleshakov Date: Fri, 17 Jan 2020 16:29:40 +0300 Subject: [PATCH 3/8] f kwarg name --- rest_framework_json_api/parsers.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/rest_framework_json_api/parsers.py b/rest_framework_json_api/parsers.py index f98d3574..781f571f 100644 --- a/rest_framework_json_api/parsers.py +++ b/rest_framework_json_api/parsers.py @@ -139,13 +139,15 @@ def parse(self, stream, media_type=None, parser_context=None): if not data.get('id') and request.method in ('PATCH', 'PUT'): raise ParseError("The resource identifier object must contain an 'id' member") - if request.method in ('PATCH', 'PUT') and data.get('id') != view.kwargs[view.lookup_field]: - raise exceptions.Conflict( - "The resource object's id ({data_id}) does not match url ({url_id})".format( - data_id=data.get('id'), - url_id=view.kwargs[view.lookup_field] + if request.method in ('PATCH', 'PUT'): + kwarg_name = view.lookup_url_kwarg if view.lookup_url_kwarg else view.lookup_field + if str(data.get('id')) != str(view.kwargs[kwarg_name]): + raise exceptions.Conflict( + "The resource object's id ({data_id}) does not match url ({url_id})".format( + data_id=data.get('id'), + url_id=view.kwargs[view.lookup_field] + ) ) - ) # Construct the return data serializer_class = getattr(view, 'serializer_class', None) From c3271c08dce7b7842b65f28ac0a300ec165db120 Mon Sep 17 00:00:00 2001 From: Boris Pleshakov Date: Fri, 17 Jan 2020 16:51:57 +0300 Subject: [PATCH 4/8] + test --- example/tests/test_model_viewsets.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/example/tests/test_model_viewsets.py b/example/tests/test_model_viewsets.py index e1a16206..d1e0ed6c 100644 --- a/example/tests/test_model_viewsets.py +++ b/example/tests/test_model_viewsets.py @@ -185,6 +185,24 @@ def test_patch_requires_id(self): self.assertEqual(response.status_code, 400) + def test_patch_requires_correct_id(self): + """ + Verify that 'id' is required to be passed in an update request. + """ + data = { + 'data': { + 'type': 'users', + 'id': 3, + 'attributes': { + 'first-name': 'DifferentName' + } + } + } + + response = self.client.patch(self.detail_url, data=data) + + self.assertEqual(response.status_code, 409) + def test_key_in_post(self): """ Ensure a key is in the post. From 7767ae71ed0839f3aa3c5f1ecdde7fca2a1b3812 Mon Sep 17 00:00:00 2001 From: Boris Pleshakov Date: Mon, 20 Jan 2020 21:12:40 +0300 Subject: [PATCH 5/8] f requested changes as @sliverc advised --- AUTHORS | 1 + example/tests/test_model_viewsets.py | 4 ++-- rest_framework_json_api/parsers.py | 6 +++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/AUTHORS b/AUTHORS index 0e2e1902..1619377a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -2,6 +2,7 @@ Adam Wróbel Adam Ziolkowski Alan Crosswell Anton Shutik +Boris Pleshakov Christian Zosel David Vogt Greg Aker diff --git a/example/tests/test_model_viewsets.py b/example/tests/test_model_viewsets.py index d1e0ed6c..0afe0b7a 100644 --- a/example/tests/test_model_viewsets.py +++ b/example/tests/test_model_viewsets.py @@ -187,12 +187,12 @@ def test_patch_requires_id(self): def test_patch_requires_correct_id(self): """ - Verify that 'id' is required to be passed in an update request. + Verify that 'id' is the same then in url """ data = { 'data': { 'type': 'users', - 'id': 3, + 'id': self.miles.pk + 1, 'attributes': { 'first-name': 'DifferentName' } diff --git a/rest_framework_json_api/parsers.py b/rest_framework_json_api/parsers.py index 781f571f..4e8d1ec6 100644 --- a/rest_framework_json_api/parsers.py +++ b/rest_framework_json_api/parsers.py @@ -140,10 +140,10 @@ def parse(self, stream, media_type=None, parser_context=None): raise ParseError("The resource identifier object must contain an 'id' member") if request.method in ('PATCH', 'PUT'): - kwarg_name = view.lookup_url_kwarg if view.lookup_url_kwarg else view.lookup_field - if str(data.get('id')) != str(view.kwargs[kwarg_name]): + lookup_url_kwarg = view.lookup_url_kwarg or view.lookup_field + if str(data.get('id')) != str(view.kwargs[lookup_url_kwarg]): raise exceptions.Conflict( - "The resource object's id ({data_id}) does not match url ({url_id})".format( + "The resource object's id ({data_id}) does not match url's lookup id ({url_id})".format( data_id=data.get('id'), url_id=view.kwargs[view.lookup_field] ) From 683b8db9bbf0cae3322928861974ee5440e474e2 Mon Sep 17 00:00:00 2001 From: Boris Pleshakov Date: Mon, 20 Jan 2020 21:15:02 +0300 Subject: [PATCH 6/8] + changelog entry --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb93ab14..864703d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,9 @@ any parts of the framework not mentioned in the documentation should generally b * Added support for Django REST framework 3.11 * Added support for Django 3.0 +### Changed +* Changed behaviour when body id doesn't match url id for `PATCH` request. + ## [3.0.0] - 2019-10-14 This release is not backwards compatible. For easy migration best upgrade first to version From c51f6b9015a4410588bf54721dc37850d11ddbde Mon Sep 17 00:00:00 2001 From: Boris Pleshakov Date: Mon, 20 Jan 2020 22:00:53 +0300 Subject: [PATCH 7/8] f flake --- rest_framework_json_api/parsers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rest_framework_json_api/parsers.py b/rest_framework_json_api/parsers.py index 4e8d1ec6..5b024499 100644 --- a/rest_framework_json_api/parsers.py +++ b/rest_framework_json_api/parsers.py @@ -143,7 +143,8 @@ def parse(self, stream, media_type=None, parser_context=None): lookup_url_kwarg = view.lookup_url_kwarg or view.lookup_field if str(data.get('id')) != str(view.kwargs[lookup_url_kwarg]): raise exceptions.Conflict( - "The resource object's id ({data_id}) does not match url's lookup id ({url_id})".format( + "The resource object's id ({data_id}) does not match url's " + "lookup id ({url_id})".format( data_id=data.get('id'), url_id=view.kwargs[view.lookup_field] ) From 7977c005349a49f9d9805b10c6cc877495f3546b Mon Sep 17 00:00:00 2001 From: Oliver Sauder Date: Thu, 23 Jan 2020 21:14:11 +0400 Subject: [PATCH 8/8] Update CHANGELOG.md --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 864703d0..57cef3d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,8 +16,9 @@ any parts of the framework not mentioned in the documentation should generally b * Added support for Django REST framework 3.11 * Added support for Django 3.0 -### Changed -* Changed behaviour when body id doesn't match url id for `PATCH` request. +### Fixed + +* Ensure that `409 Conflict` is returned when processing a `PATCH` request in which the resource object’s type and id do not match the server’s endpoint properly as outlined in [JSON:API](https://jsonapi.org/format/#crud-updating-responses-409) spec. ## [3.0.0] - 2019-10-14