Skip to content

Commit 92cd7fc

Browse files
committed
[Bugfix] Ensure application/json is not acceptable media type
Closes #184
1 parent 6b7912c commit 92cd7fc

20 files changed

+163
-43
lines changed

CHANGELOG.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,16 @@ All notable changes to this project will be documented in this file. This projec
77

88
### Added
99

10-
- The `JsonApiException` class now has a `context()` method. Laravel's exception handler uses this to add log context if
11-
the exception is being logged. This means logging of JSON:API exceptions will now include the HTTP status code and the
10+
- The `JsonApiException` class now has a `context()` method. Laravel's exception handler uses this to add log context
11+
when the exception is logged. This means logging of JSON:API exceptions will now include the HTTP status code and the
1212
JSON:API errors.
1313

14+
### Fixed
15+
16+
- [#184](https://github.com/laravel-json-api/laravel/issues/184) Ensure that an `Accept` header with the media type
17+
`application/json` is rejected with a `406 Not Acceptable` response. Previously this media type worked, which is
18+
incorrect as the JSON:API specification requires the media type `application/vnd.api+json`.
19+
1420
## [2.3.0] - 2022-04-11
1521

1622
### Added

src/Http/Requests/ResourceQuery.php

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ class ResourceQuery extends FormRequest implements QueryParameters
4848
private static $queryOneResolver;
4949

5050
/**
51+
* The media types the resource accepts, in addition to JSON:API.
52+
*
5153
* @var string[]
5254
*/
53-
protected array $mediaTypes = [
54-
self::JSON_API_MEDIA_TYPE,
55-
];
55+
protected array $mediaTypes = [];
5656

5757
/**
5858
* The include paths to use if the client provides none.
@@ -304,10 +304,24 @@ protected function failedValidation(Validator $validator)
304304
*/
305305
protected function isAcceptableMediaType(): bool
306306
{
307+
/**
308+
* We expect the JSON:API media type to exactly match.
309+
*/
310+
foreach ($this->getAcceptableContentTypes() as $contentType) {
311+
if (self::JSON_API_MEDIA_TYPE === $contentType) {
312+
return true;
313+
}
314+
}
315+
316+
/**
317+
* Otherwise we check if any additional media types match.
318+
*/
307319
return $this->accepts($this->mediaTypes());
308320
}
309321

310322
/**
323+
* Get the media types the resource accepts, in addition to JSON:API.
324+
*
311325
* @return string[]
312326
*/
313327
protected function mediaTypes(): array

tests/dummy/tests/Api/V1/Posts/AttachMediaTest.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,12 +166,17 @@ public function testForbidden(): void
166166
$this->assertDatabaseCount('post_video', 1);
167167
}
168168

169-
public function testNotAcceptableMediaType(): void
169+
/**
170+
* @param string $mediaType
171+
* @return void
172+
* @dataProvider notAcceptableMediaTypeProvider
173+
*/
174+
public function testNotAcceptableMediaType(string $mediaType): void
170175
{
171176
$response = $this
172177
->actingAs($this->post->author)
173178
->jsonApi('posts')
174-
->accept('text/html')
179+
->accept($mediaType)
175180
->withData([])
176181
->post(url('/api/v1/posts', [$this->post, 'relationships', 'media']));
177182

tests/dummy/tests/Api/V1/Posts/AttachTagsTest.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,12 @@ public function testForbidden(): void
140140
$this->assertDatabaseCount('taggables', 2);
141141
}
142142

143-
public function testNotAcceptableMediaType(): void
143+
/**
144+
* @param string $mediaType
145+
* @return void
146+
* @dataProvider notAcceptableMediaTypeProvider
147+
*/
148+
public function testNotAcceptableMediaType(string $mediaType): void
144149
{
145150
$tag = Tag::factory()->create();
146151

@@ -154,7 +159,7 @@ public function testNotAcceptableMediaType(): void
154159
$response = $this
155160
->actingAs($this->post->author)
156161
->jsonApi('posts')
157-
->accept('text/html')
162+
->accept($mediaType)
158163
->withData($data)
159164
->post(url('/api/v1/posts', [$this->post, 'relationships', 'tags']));
160165

tests/dummy/tests/Api/V1/Posts/CreateTest.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,15 +162,20 @@ public function testUnauthorized(): void
162162
$this->assertDatabaseCount('posts', 0);
163163
}
164164

165-
public function testNotAcceptableMediaType(): void
165+
/**
166+
* @param string $mediaType
167+
* @return void
168+
* @dataProvider notAcceptableMediaTypeProvider
169+
*/
170+
public function testNotAcceptableMediaType(string $mediaType): void
166171
{
167172
$post = Post::factory()->make();
168173
$data = $this->serialize($post);
169174

170175
$response = $this
171176
->actingAs($post->author)
172177
->jsonApi('posts')
173-
->accept('text/html')
178+
->accept($mediaType)
174179
->withData($data)
175180
->post('/api/v1/posts');
176181

tests/dummy/tests/Api/V1/Posts/DetachMediaTest.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,12 +177,17 @@ public function testForbidden(): void
177177
$this->assertDatabaseCount('post_video', $existingVideos->count());
178178
}
179179

180-
public function testNotAcceptableMediaType(): void
180+
/**
181+
* @param string $mediaType
182+
* @return void
183+
* @dataProvider notAcceptableMediaTypeProvider
184+
*/
185+
public function testNotAcceptableMediaType(string $mediaType): void
181186
{
182187
$response = $this
183188
->actingAs($this->post->author)
184189
->jsonApi('posts')
185-
->accept('text/html')
190+
->accept($mediaType)
186191
->withData([])
187192
->delete(url('/api/v1/posts', [$this->post, 'relationships', 'media']));
188193

tests/dummy/tests/Api/V1/Posts/DetachTagsTest.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,12 @@ public function testForbidden(): void
143143
$this->assertDatabaseCount('taggables', 2);
144144
}
145145

146-
public function testNotAcceptableMediaType(): void
146+
/**
147+
* @param string $mediaType
148+
* @return void
149+
* @dataProvider notAcceptableMediaTypeProvider
150+
*/
151+
public function testNotAcceptableMediaType(string $mediaType): void
147152
{
148153
$tag = Tag::factory()->create();
149154

@@ -157,7 +162,7 @@ public function testNotAcceptableMediaType(): void
157162
$response = $this
158163
->actingAs($this->post->author)
159164
->jsonApi('posts')
160-
->accept('text/html')
165+
->accept($mediaType)
161166
->withData($data)
162167
->delete(url('/api/v1/posts', [$this->post, 'relationships', 'tags']));
163168

tests/dummy/tests/Api/V1/Posts/IndexTest.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -326,10 +326,16 @@ public function testWithCount(): void
326326
$response->assertFetchedManyExact($expected);
327327
}
328328

329-
public function testInvalidMediaType(): void
329+
/**
330+
* @param string $mediaType
331+
* @return void
332+
* @dataProvider notAcceptableMediaTypeProvider
333+
*/
334+
public function testNotAcceptableMediaType(string $mediaType): void
330335
{
331-
$this->jsonApi()
332-
->accept('text/html')
336+
$this
337+
->jsonApi()
338+
->accept($mediaType)
333339
->get('/api/v1/posts')
334340
->assertStatus(406);
335341
}

tests/dummy/tests/Api/V1/Posts/ReadAuthorIdentifierTest.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,16 @@ public function testDraftAsAuthor(): void
137137
);
138138
}
139139

140-
public function testInvalidMediaType(): void
140+
/**
141+
* @param string $mediaType
142+
* @return void
143+
* @dataProvider notAcceptableMediaTypeProvider
144+
*/
145+
public function testNotAcceptableMediaType(string $mediaType): void
141146
{
142-
$this->jsonApi()
143-
->accept('text/html')
147+
$this
148+
->jsonApi()
149+
->accept($mediaType)
144150
->get(url('/api/v1/posts', [$this->post, 'relationships', 'author']))
145151
->assertStatus(406);
146152
}

tests/dummy/tests/Api/V1/Posts/ReadAuthorTest.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,16 @@ public function testDraftAsAuthor(): void
132132
$response->assertFetchedOneExact($expected);
133133
}
134134

135-
public function testInvalidMediaType(): void
135+
/**
136+
* @param string $mediaType
137+
* @return void
138+
* @dataProvider notAcceptableMediaTypeProvider
139+
*/
140+
public function testNotAcceptableMediaType(string $mediaType): void
136141
{
137-
$this->jsonApi()
138-
->accept('text/html')
142+
$this
143+
->jsonApi()
144+
->accept($mediaType)
139145
->get(url('/api/v1/posts', [$this->post, 'author']))
140146
->assertStatus(406);
141147
}

0 commit comments

Comments
 (0)