From c3d1f7f0eb177079fdca1a87c76596157a2b82f6 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Wed, 19 Jun 2024 05:21:12 +0200 Subject: [PATCH] Return Forbidden when operation is inaccessible, to match resource endpoint status code --- .../BaseJsonApiOperationsController.cs | 4 ++-- ...stomConstrainedOperationsControllerTests.cs | 18 +++++++++--------- ...aultConstrainedOperationsControllerTests.cs | 16 ++++++++-------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/JsonApiDotNetCore/Controllers/BaseJsonApiOperationsController.cs b/src/JsonApiDotNetCore/Controllers/BaseJsonApiOperationsController.cs index 5485fad3e0..b169bdd005 100644 --- a/src/JsonApiDotNetCore/Controllers/BaseJsonApiOperationsController.cs +++ b/src/JsonApiDotNetCore/Controllers/BaseJsonApiOperationsController.cs @@ -140,7 +140,7 @@ protected virtual void ValidateEnabledOperations(IList opera { string operationCode = GetOperationCodeText(operationKind); - errors.Add(new ErrorObject(HttpStatusCode.UnprocessableEntity) + errors.Add(new ErrorObject(HttpStatusCode.Forbidden) { Title = "The requested operation is not accessible.", Detail = $"The '{operationCode}' relationship operation is not accessible for relationship '{operationRequest.Relationship}' " + @@ -155,7 +155,7 @@ protected virtual void ValidateEnabledOperations(IList opera { string operationCode = GetOperationCodeText(operationKind); - errors.Add(new ErrorObject(HttpStatusCode.UnprocessableEntity) + errors.Add(new ErrorObject(HttpStatusCode.Forbidden) { Title = "The requested operation is not accessible.", Detail = $"The '{operationCode}' resource operation is not accessible for resource type '{operationRequest.PrimaryResourceType}'.", diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/AtomicCustomConstrainedOperationsControllerTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/AtomicCustomConstrainedOperationsControllerTests.cs index 51cb1a53a2..099168b124 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/AtomicCustomConstrainedOperationsControllerTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/AtomicCustomConstrainedOperationsControllerTests.cs @@ -69,7 +69,7 @@ public async Task Can_create_resources_for_matching_resource_type() } [Fact] - public async Task Cannot_create_resource_for_mismatching_resource_type() + public async Task Cannot_create_resource_for_inaccessible_operation() { // Arrange var requestBody = new @@ -96,12 +96,12 @@ public async Task Cannot_create_resource_for_mismatching_resource_type() (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert - httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + httpResponse.ShouldHaveStatusCode(HttpStatusCode.Forbidden); responseDocument.Errors.ShouldHaveCount(1); ErrorObject error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.StatusCode.Should().Be(HttpStatusCode.Forbidden); error.Title.Should().Be("The requested operation is not accessible."); error.Detail.Should().Be("The 'add' resource operation is not accessible for resource type 'performers'."); error.Source.ShouldNotBeNull(); @@ -109,7 +109,7 @@ public async Task Cannot_create_resource_for_mismatching_resource_type() } [Fact] - public async Task Cannot_update_resource_for_matching_resource_type() + public async Task Cannot_update_resource_for_inaccessible_operation() { // Arrange MusicTrack existingTrack = _fakers.MusicTrack.Generate(); @@ -145,12 +145,12 @@ await _testContext.RunOnDatabaseAsync(async dbContext => (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert - httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + httpResponse.ShouldHaveStatusCode(HttpStatusCode.Forbidden); responseDocument.Errors.ShouldHaveCount(1); ErrorObject error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.StatusCode.Should().Be(HttpStatusCode.Forbidden); error.Title.Should().Be("The requested operation is not accessible."); error.Detail.Should().Be("The 'update' resource operation is not accessible for resource type 'musicTracks'."); error.Source.ShouldNotBeNull(); @@ -158,7 +158,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => } [Fact] - public async Task Cannot_add_to_ToMany_relationship_for_matching_resource_type() + public async Task Cannot_add_to_ToMany_relationship_for_inaccessible_operation() { // Arrange MusicTrack existingTrack = _fakers.MusicTrack.Generate(); @@ -201,12 +201,12 @@ await _testContext.RunOnDatabaseAsync(async dbContext => (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert - httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + httpResponse.ShouldHaveStatusCode(HttpStatusCode.Forbidden); responseDocument.Errors.ShouldHaveCount(1); ErrorObject error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.StatusCode.Should().Be(HttpStatusCode.Forbidden); error.Title.Should().Be("The requested operation is not accessible."); error.Detail.Should().Be("The 'add' relationship operation is not accessible for relationship 'performers' on resource type 'musicTracks'."); error.Source.ShouldNotBeNull(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/AtomicDefaultConstrainedOperationsControllerTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/AtomicDefaultConstrainedOperationsControllerTests.cs index 14dc1ab83b..caffc32e2b 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/AtomicDefaultConstrainedOperationsControllerTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/AtomicDefaultConstrainedOperationsControllerTests.cs @@ -20,7 +20,7 @@ public AtomicDefaultConstrainedOperationsControllerTests(IntegrationTestContext< } [Fact] - public async Task Cannot_delete_resource_for_disabled_resource_endpoint() + public async Task Cannot_delete_resource_for_inaccessible_operation() { // Arrange TextLanguage existingLanguage = _fakers.TextLanguage.Generate(); @@ -53,12 +53,12 @@ await _testContext.RunOnDatabaseAsync(async dbContext => (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert - httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + httpResponse.ShouldHaveStatusCode(HttpStatusCode.Forbidden); responseDocument.Errors.ShouldHaveCount(1); ErrorObject error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.StatusCode.Should().Be(HttpStatusCode.Forbidden); error.Title.Should().Be("The requested operation is not accessible."); error.Detail.Should().Be("The 'remove' resource operation is not accessible for resource type 'textLanguages'."); error.Source.ShouldNotBeNull(); @@ -66,7 +66,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => } [Fact] - public async Task Cannot_change_ToMany_relationship_for_disabled_resource_endpoints() + public async Task Cannot_change_ToMany_relationship_for_inaccessible_operations() { // Arrange TextLanguage existingLanguage = _fakers.TextLanguage.Generate(); @@ -145,26 +145,26 @@ await _testContext.RunOnDatabaseAsync(async dbContext => (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert - httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + httpResponse.ShouldHaveStatusCode(HttpStatusCode.Forbidden); responseDocument.Errors.ShouldHaveCount(3); ErrorObject error1 = responseDocument.Errors[0]; - error1.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error1.StatusCode.Should().Be(HttpStatusCode.Forbidden); error1.Title.Should().Be("The requested operation is not accessible."); error1.Detail.Should().Be("The 'update' relationship operation is not accessible for relationship 'lyrics' on resource type 'textLanguages'."); error1.Source.ShouldNotBeNull(); error1.Source.Pointer.Should().Be("/atomic:operations[0]"); ErrorObject error2 = responseDocument.Errors[1]; - error2.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error2.StatusCode.Should().Be(HttpStatusCode.Forbidden); error2.Title.Should().Be("The requested operation is not accessible."); error2.Detail.Should().Be("The 'add' relationship operation is not accessible for relationship 'lyrics' on resource type 'textLanguages'."); error2.Source.ShouldNotBeNull(); error2.Source.Pointer.Should().Be("/atomic:operations[1]"); ErrorObject error3 = responseDocument.Errors[2]; - error3.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error3.StatusCode.Should().Be(HttpStatusCode.Forbidden); error3.Title.Should().Be("The requested operation is not accessible."); error3.Detail.Should().Be("The 'remove' relationship operation is not accessible for relationship 'lyrics' on resource type 'textLanguages'."); error3.Source.ShouldNotBeNull();