From 94f7266feb9f4cb991aac1ad8c7c6eecc96d463c Mon Sep 17 00:00:00 2001 From: Alex Dragovich <46065570+itsadrago@users.noreply.github.com> Date: Thu, 12 Feb 2026 13:58:44 -0800 Subject: [PATCH] [PM-31483] adding guard for when email verification FF is disabled (#6927) * [PM-31483] adding guard for when email verification FF is disabled * [PM-31483] removing need for client fallback endpoint * [PM-31483] fixing test after main merge * [PM-31483] changing error when email protected send should not be allowed to be viewed --- src/Api/Startup.cs | 3 +- src/Api/Tools/Controllers/SendsController.cs | 28 +++++---- .../Tools/Controllers/SendsControllerTests.cs | 57 +++++++++++++++++++ 3 files changed, 77 insertions(+), 11 deletions(-) diff --git a/src/Api/Startup.cs b/src/Api/Startup.cs index 5b9015b71a..7ac9c28139 100644 --- a/src/Api/Startup.cs +++ b/src/Api/Startup.cs @@ -303,7 +303,8 @@ public class Startup { swaggerDoc.Servers = [ - new() { + new() + { Url = globalSettings.BaseServiceUri.Api, } ]; diff --git a/src/Api/Tools/Controllers/SendsController.cs b/src/Api/Tools/Controllers/SendsController.cs index af7fe8f12b..7c2402ea94 100644 --- a/src/Api/Tools/Controllers/SendsController.cs +++ b/src/Api/Tools/Controllers/SendsController.cs @@ -82,11 +82,9 @@ public class SendsController : Controller throw new BadRequestException("Could not locate send"); } - /* This guard can be removed once feature flag is retired*/ - var sendEmailOtpEnabled = _featureService.IsEnabled(FeatureFlagKeys.SendEmailOTP); - if (sendEmailOtpEnabled && send.AuthType == AuthType.Email && send.Emails is not null) + if (send.AuthType == AuthType.Email && send.Emails is not null) { - return new UnauthorizedResult(); + throw new NotFoundException(); } var sendAuthResult = @@ -137,11 +135,9 @@ public class SendsController : Controller throw new BadRequestException("Could not locate send"); } - /* This guard can be removed once feature flag is retired*/ - var sendEmailOtpEnabled = _featureService.IsEnabled(FeatureFlagKeys.SendEmailOTP); - if (sendEmailOtpEnabled && send.AuthType == AuthType.Email && send.Emails is not null) + if (send.AuthType == AuthType.Email && send.Emails is not null) { - return new UnauthorizedResult(); + throw new NotFoundException(); } var (url, result) = await _anonymousSendCommand.GetSendFileDownloadUrlAsync(send, fileId, @@ -229,7 +225,6 @@ public class SendsController : Controller } [Authorize(Policy = Policies.Send)] - // [RequireFeature(FeatureFlagKeys.SendEmailOTP)] /* Uncomment once client fallback re-try logic is added */ [HttpPost("access/")] public async Task AccessUsingAuth() { @@ -240,6 +235,13 @@ public class SendsController : Controller throw new BadRequestException("Could not locate send"); } + /* This guard can be removed once feature flag is retired*/ + var sendEmailOtpEnabled = _featureService.IsEnabled(FeatureFlagKeys.SendEmailOTP); + if (!sendEmailOtpEnabled && send.AuthType == AuthType.Email && send.Emails is not null) + { + throw new NotFoundException(); + } + if (!INonAnonymousSendCommand.SendCanBeAccessed(send)) { throw new NotFoundException(); @@ -270,7 +272,6 @@ public class SendsController : Controller } [Authorize(Policy = Policies.Send)] - // [RequireFeature(FeatureFlagKeys.SendEmailOTP)] /* Uncomment once client fallback re-try logic is added */ [HttpPost("access/file/{fileId}")] public async Task GetSendFileDownloadDataUsingAuth(string fileId) { @@ -282,6 +283,13 @@ public class SendsController : Controller throw new BadRequestException("Could not locate send"); } + /* This guard can be removed once feature flag is retired*/ + var sendEmailOtpEnabled = _featureService.IsEnabled(FeatureFlagKeys.SendEmailOTP); + if (!sendEmailOtpEnabled && send.AuthType == AuthType.Email && send.Emails is not null) + { + throw new NotFoundException(); + } + var (url, result) = await _nonAnonymousSendCommand.GetSendFileDownloadUrlAsync(send, fileId); if (result.Equals(SendAccessResult.Denied)) diff --git a/test/Api.Test/Tools/Controllers/SendsControllerTests.cs b/test/Api.Test/Tools/Controllers/SendsControllerTests.cs index a9c6c56854..6ca9aebc39 100644 --- a/test/Api.Test/Tools/Controllers/SendsControllerTests.cs +++ b/test/Api.Test/Tools/Controllers/SendsControllerTests.cs @@ -6,6 +6,7 @@ using Bit.Api.Tools.Controllers; using Bit.Api.Tools.Models; using Bit.Api.Tools.Models.Request; using Bit.Api.Tools.Models.Response; +using Bit.Core; using Bit.Core.Entities; using Bit.Core.Exceptions; using Bit.Core.Platform.Push; @@ -80,6 +81,8 @@ public class SendsControllerTests : IDisposable send.Id = default; send.Type = SendType.Text; send.Data = JsonSerializer.Serialize(new Dictionary()); + send.AuthType = AuthType.None; + send.Emails = null; send.HideEmail = true; _sendRepository.GetByIdAsync(Arg.Any()).Returns(send); @@ -793,6 +796,33 @@ public class SendsControllerTests : IDisposable await _userService.Received(1).GetUserByIdAsync(creator.Id); } + [Theory, AutoData] + public async Task AccessUsingAuth_WithEmailProtectedSend_WithFfDisabled_ReturnsUnauthorizedResult(Guid sendId, User creator) + { + var send = new Send + { + Id = sendId, + UserId = creator.Id, + Type = SendType.Text, + Data = JsonSerializer.Serialize(new SendTextData("Test", "Notes", "Text", false)), + HideEmail = false, + DeletionDate = DateTime.UtcNow.AddDays(7), + ExpirationDate = null, + Disabled = false, + AccessCount = 0, + AuthType = AuthType.Email, + Emails = "test@example.com", + MaxAccessCount = null + }; + var user = CreateUserWithSendIdClaim(sendId); + _sut.ControllerContext = CreateControllerContextWithUser(user); + _sendRepository.GetByIdAsync(sendId).Returns(send); + _userService.GetUserByIdAsync(creator.Id).Returns(creator); + _featureService.IsEnabled(FeatureFlagKeys.SendEmailOTP).Returns(false); + + await Assert.ThrowsAsync(() => _sut.AccessUsingAuth()); + } + [Theory, AutoData] public async Task AccessUsingAuth_WithHideEmail_DoesNotIncludeCreatorIdentifier(Guid sendId, User creator) { @@ -1036,6 +1066,33 @@ public class SendsControllerTests : IDisposable await _nonAnonymousSendCommand.Received(1).GetSendFileDownloadUrlAsync(send, fileId); } + [Theory, AutoData] + public async Task GetSendFileDownloadDataUsingAuth_WithEmailProtectedSend_WithFfDisabled_ReturnsUnauthorizedResult( + Guid sendId, string fileId, string expectedUrl) + { + var fileData = new SendFileData("Test File", "Notes", "document.pdf") { Id = fileId, Size = 2048 }; + var send = new Send + { + Id = sendId, + Type = SendType.File, + Data = JsonSerializer.Serialize(fileData), + DeletionDate = DateTime.UtcNow.AddDays(7), + ExpirationDate = null, + Disabled = false, + AccessCount = 0, + AuthType = AuthType.Email, + Emails = "test@example.com", + MaxAccessCount = null + }; + var user = CreateUserWithSendIdClaim(sendId); + _sut.ControllerContext = CreateControllerContextWithUser(user); + _sendRepository.GetByIdAsync(sendId).Returns(send); + _sendFileStorageService.GetSendFileDownloadUrlAsync(send, fileId).Returns(expectedUrl); + _featureService.IsEnabled(FeatureFlagKeys.SendEmailOTP).Returns(false); + + await Assert.ThrowsAsync(() => _sut.GetSendFileDownloadDataUsingAuth(fileId)); + } + [Theory, AutoData] public async Task GetSendFileDownloadDataUsingAuth_WithNonExistentSend_ThrowsBadRequestException( Guid sendId, string fileId)