From e1632bc3bf9e2709aba656390a944a11ace93597 Mon Sep 17 00:00:00 2001 From: Alexandre Brandizzi Date: Tue, 22 Sep 2026 23:35:07 -0300 Subject: [PATCH] fix(uplifts): expose only allowlisted denial copy --- .../VendorPortalControllerTests.cs | 32 +------------ .../WorkOrderUpliftControllerTests.cs | 47 +++++++++++++++++++ .../Controllers/VendorPortalController.cs | 10 ---- .../Controllers/WorkOrderDetailController.cs | 5 +- 4 files changed, 52 insertions(+), 42 deletions(-) diff --git a/Api.SeaHavenIndustries.Tests/VendorPortalControllerTests.cs b/Api.SeaHavenIndustries.Tests/VendorPortalControllerTests.cs index 0e48da5..dd9e40e 100644 --- a/Api.SeaHavenIndustries.Tests/VendorPortalControllerTests.cs +++ b/Api.SeaHavenIndustries.Tests/VendorPortalControllerTests.cs @@ -306,37 +306,7 @@ public class VendorPortalControllerTests var result = await controller.RequestUplift(10, new VendorPortalController.UpliftRequestBody { RequestedNTE = 100m }); - var badRequest = result.Should().BeOfType().Subject; - var response = badRequest.Value.Should().BeOfType().Subject; - response.Message.Should().Contain("reference"); - response.Message.Should().NotContain("Requested NTE must be greater than the current NTE"); - } - - [Fact] - public async Task RequestUplift_Forbidden_Returns403WithExactPermissionMessage() - { - var service = new Mock(); - service.Setup(x => x.ResolveSessionAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(Session); - service.Setup(x => x.RequestUpliftAsync( - Session, - 10, - 500m, - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .ThrowsAsync(new UpliftForbiddenException("internal permission detail")); - var controller = NewController(service); - - var result = await controller.RequestUplift( - 10, - new VendorPortalController.UpliftRequestBody { RequestedNTE = 500m }); - - var forbidden = result.Should().BeOfType().Subject; - forbidden.StatusCode.Should().Be(StatusCodes.Status403Forbidden); - var response = forbidden.Value.Should().BeOfType().Subject; - response.Message.Should().Be("Your role can't request uplifts on this work order."); + result.Should().BeOfType(); } [Fact] diff --git a/Api.SeaHavenIndustries.Tests/WorkOrderUpliftControllerTests.cs b/Api.SeaHavenIndustries.Tests/WorkOrderUpliftControllerTests.cs index b3cdbfc..8dabbcd 100644 --- a/Api.SeaHavenIndustries.Tests/WorkOrderUpliftControllerTests.cs +++ b/Api.SeaHavenIndustries.Tests/WorkOrderUpliftControllerTests.cs @@ -78,6 +78,53 @@ public sealed class WorkOrderUpliftControllerTests Assert.Equal(12, created.Id); } + [Fact] + public async Task CreateUplift_RequestPermissionDenied_ReturnsExact403Message() + { + var service = new Mock(); + service.Setup(x => x.CreateAsync( + 7, + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ThrowsAsync(new UpliftForbiddenException(UpliftForbiddenException.RequestUpliftsDeniedMessage)); + + var controller = NewController(service, "Dispatcher"); + var result = await controller.CreateUplift( + 7, + new CreateWorkOrderUpliftRequestDto { Amount = 750m }, + CancellationToken.None); + + var forbidden = result.Should().BeOfType().Subject; + forbidden.StatusCode.Should().Be(StatusCodes.Status403Forbidden); + var response = forbidden.Value.Should().BeOfType().Subject; + response.Message.Should().Be(UpliftForbiddenException.RequestUpliftsDeniedMessage); + } + + [Fact] + public async Task CreateUplift_UnrelatedForbiddenException_ReturnsSanitized403() + { + var service = new Mock(); + service.Setup(x => x.CreateAsync( + 7, + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ThrowsAsync(new UpliftForbiddenException("SECRET-internal-tier-detail")); + + var controller = NewController(service, "Dispatcher"); + var result = await controller.CreateUplift( + 7, + new CreateWorkOrderUpliftRequestDto { Amount = 750m }, + CancellationToken.None); + + var forbidden = result.Should().BeOfType().Subject; + forbidden.StatusCode.Should().Be(StatusCodes.Status403Forbidden); + var response = forbidden.Value.Should().BeOfType().Subject; + response.Message.Should().NotContain("SECRET-internal-tier-detail"); + response.Message.Should().Contain("reference"); + } + [Fact] public async Task CancelUplift_NotFound_Returns404() { diff --git a/Api.SeaHavenIndustries/Controllers/VendorPortalController.cs b/Api.SeaHavenIndustries/Controllers/VendorPortalController.cs index ec48a68..39dd90b 100644 --- a/Api.SeaHavenIndustries/Controllers/VendorPortalController.cs +++ b/Api.SeaHavenIndustries/Controllers/VendorPortalController.cs @@ -221,16 +221,6 @@ namespace Api.SeaHavenIndustries.Controllers { return NotFound(new Response { Status = "Error", Message = "Dispatch not found" }); } - catch (UpliftForbiddenException) - { - return StatusCode( - StatusCodes.Status403Forbidden, - new Response - { - Status = "Error", - Message = UpliftForbiddenException.RequestUpliftsDeniedMessage - }); - } catch (InvalidOperationException ex) { return BadRequest(new Response { Status = "Error", Message = _logger.Sanitize(ex, "The requested action could not be completed for this dispatch") }); diff --git a/Api.SeaHavenIndustries/Controllers/WorkOrderDetailController.cs b/Api.SeaHavenIndustries/Controllers/WorkOrderDetailController.cs index d975b32..be0a708 100644 --- a/Api.SeaHavenIndustries/Controllers/WorkOrderDetailController.cs +++ b/Api.SeaHavenIndustries/Controllers/WorkOrderDetailController.cs @@ -170,7 +170,10 @@ namespace Api.SeaHavenIndustries.Controllers } catch (UpliftForbiddenException ex) { - return StatusCode(StatusCodes.Status403Forbidden, new Response { Status = "Error", Message = _logger.Sanitize(ex, "You are not authorized to perform this action") }); + var message = ex.Message == UpliftForbiddenException.RequestUpliftsDeniedMessage + ? UpliftForbiddenException.RequestUpliftsDeniedMessage + : _logger.Sanitize(ex, "You are not authorized to perform this action"); + return StatusCode(StatusCodes.Status403Forbidden, new Response { Status = "Error", Message = message }); } catch (InvalidOperationException ex) {