diff --git a/src/Api/Tools/Models/Request/SendRequestModel.cs b/src/Api/Tools/Models/Request/SendRequestModel.cs index 4afb9ade68..00dcb6273f 100644 --- a/src/Api/Tools/Models/Request/SendRequestModel.cs +++ b/src/Api/Tools/Models/Request/SendRequestModel.cs @@ -110,7 +110,7 @@ public class SendRequestModel /// Comma-separated list of email **hashes** that may access the send using OTP /// authentication. Mutually exclusive with . /// - [StringLength(1000)] + [StringLength(4000)] public string EmailHashes { get; set; } /// diff --git a/test/Api.Test/Tools/Controllers/SendsControllerTests.cs b/test/Api.Test/Tools/Controllers/SendsControllerTests.cs index e3a9ba4435..9322948037 100644 --- a/test/Api.Test/Tools/Controllers/SendsControllerTests.cs +++ b/test/Api.Test/Tools/Controllers/SendsControllerTests.cs @@ -981,205 +981,6 @@ public class SendsControllerTests : IDisposable Assert.Equal(expectedUrl, response.Url); } - #region AccessUsingAuth Validation Tests - - [Theory, AutoData] - public async Task AccessUsingAuth_WithExpiredSend_ThrowsNotFoundException(Guid sendId) - { - var send = new Send - { - Id = sendId, - UserId = Guid.NewGuid(), - Type = SendType.Text, - Data = JsonSerializer.Serialize(new SendTextData("Test", "Notes", "Text", false)), - DeletionDate = DateTime.UtcNow.AddDays(7), - ExpirationDate = DateTime.UtcNow.AddDays(-1), // Expired yesterday - Disabled = false, - AccessCount = 0, - MaxAccessCount = null - }; - var user = CreateUserWithSendIdClaim(sendId); - _sut.ControllerContext = CreateControllerContextWithUser(user); - _sendRepository.GetByIdAsync(sendId).Returns(send); - - await Assert.ThrowsAsync(() => _sut.AccessUsingAuth()); - - await _sendRepository.Received(1).GetByIdAsync(sendId); - } - - [Theory, AutoData] - public async Task AccessUsingAuth_WithDeletedSend_ThrowsNotFoundException(Guid sendId) - { - var send = new Send - { - Id = sendId, - UserId = Guid.NewGuid(), - Type = SendType.Text, - Data = JsonSerializer.Serialize(new SendTextData("Test", "Notes", "Text", false)), - DeletionDate = DateTime.UtcNow.AddDays(-1), // Should have been deleted yesterday - ExpirationDate = null, - Disabled = false, - AccessCount = 0, - MaxAccessCount = null - }; - var user = CreateUserWithSendIdClaim(sendId); - _sut.ControllerContext = CreateControllerContextWithUser(user); - _sendRepository.GetByIdAsync(sendId).Returns(send); - - await Assert.ThrowsAsync(() => _sut.AccessUsingAuth()); - - await _sendRepository.Received(1).GetByIdAsync(sendId); - } - - [Theory, AutoData] - public async Task AccessUsingAuth_WithDisabledSend_ThrowsNotFoundException(Guid sendId) - { - var send = new Send - { - Id = sendId, - UserId = Guid.NewGuid(), - Type = SendType.Text, - Data = JsonSerializer.Serialize(new SendTextData("Test", "Notes", "Text", false)), - DeletionDate = DateTime.UtcNow.AddDays(7), - ExpirationDate = null, - Disabled = true, // Disabled - AccessCount = 0, - MaxAccessCount = null - }; - var user = CreateUserWithSendIdClaim(sendId); - _sut.ControllerContext = CreateControllerContextWithUser(user); - _sendRepository.GetByIdAsync(sendId).Returns(send); - - await Assert.ThrowsAsync(() => _sut.AccessUsingAuth()); - - await _sendRepository.Received(1).GetByIdAsync(sendId); - } - - [Theory, AutoData] - public async Task AccessUsingAuth_WithAccessCountExceeded_ThrowsNotFoundException(Guid sendId) - { - var send = new Send - { - Id = sendId, - UserId = Guid.NewGuid(), - Type = SendType.Text, - Data = JsonSerializer.Serialize(new SendTextData("Test", "Notes", "Text", false)), - DeletionDate = DateTime.UtcNow.AddDays(7), - ExpirationDate = null, - Disabled = false, - AccessCount = 5, - MaxAccessCount = 5 // Limit reached - }; - var user = CreateUserWithSendIdClaim(sendId); - _sut.ControllerContext = CreateControllerContextWithUser(user); - _sendRepository.GetByIdAsync(sendId).Returns(send); - - await Assert.ThrowsAsync(() => _sut.AccessUsingAuth()); - - await _sendRepository.Received(1).GetByIdAsync(sendId); - } - - #endregion - - #region GetSendFileDownloadDataUsingAuth Validation Tests - - [Theory, AutoData] - public async Task GetSendFileDownloadDataUsingAuth_WithExpiredSend_ThrowsNotFoundException( - Guid sendId, string fileId) - { - var send = new Send - { - Id = sendId, - Type = SendType.File, - Data = JsonSerializer.Serialize(new SendFileData("Test", "Notes", "file.pdf")), - DeletionDate = DateTime.UtcNow.AddDays(7), - ExpirationDate = DateTime.UtcNow.AddDays(-1), // Expired - Disabled = false, - AccessCount = 0, - MaxAccessCount = null - }; - var user = CreateUserWithSendIdClaim(sendId); - _sut.ControllerContext = CreateControllerContextWithUser(user); - _sendRepository.GetByIdAsync(sendId).Returns(send); - - await Assert.ThrowsAsync(() => _sut.GetSendFileDownloadDataUsingAuth(fileId)); - - await _sendRepository.Received(1).GetByIdAsync(sendId); - } - - [Theory, AutoData] - public async Task GetSendFileDownloadDataUsingAuth_WithDeletedSend_ThrowsNotFoundException( - Guid sendId, string fileId) - { - var send = new Send - { - Id = sendId, - Type = SendType.File, - Data = JsonSerializer.Serialize(new SendFileData("Test", "Notes", "file.pdf")), - DeletionDate = DateTime.UtcNow.AddDays(-1), // Deleted - ExpirationDate = null, - Disabled = false, - AccessCount = 0, - MaxAccessCount = null - }; - var user = CreateUserWithSendIdClaim(sendId); - _sut.ControllerContext = CreateControllerContextWithUser(user); - _sendRepository.GetByIdAsync(sendId).Returns(send); - - await Assert.ThrowsAsync(() => _sut.GetSendFileDownloadDataUsingAuth(fileId)); - - await _sendRepository.Received(1).GetByIdAsync(sendId); - } - - [Theory, AutoData] - public async Task GetSendFileDownloadDataUsingAuth_WithDisabledSend_ThrowsNotFoundException( - Guid sendId, string fileId) - { - var send = new Send - { - Id = sendId, - Type = SendType.File, - Data = JsonSerializer.Serialize(new SendFileData("Test", "Notes", "file.pdf")), - DeletionDate = DateTime.UtcNow.AddDays(7), - ExpirationDate = null, - Disabled = true, // Disabled - AccessCount = 0, - MaxAccessCount = null - }; - var user = CreateUserWithSendIdClaim(sendId); - _sut.ControllerContext = CreateControllerContextWithUser(user); - _sendRepository.GetByIdAsync(sendId).Returns(send); - - await Assert.ThrowsAsync(() => _sut.GetSendFileDownloadDataUsingAuth(fileId)); - - await _sendRepository.Received(1).GetByIdAsync(sendId); - } - - [Theory, AutoData] - public async Task GetSendFileDownloadDataUsingAuth_WithAccessCountExceeded_ThrowsNotFoundException( - Guid sendId, string fileId) - { - var send = new Send - { - Id = sendId, - Type = SendType.File, - Data = JsonSerializer.Serialize(new SendFileData("Test", "Notes", "file.pdf")), - DeletionDate = DateTime.UtcNow.AddDays(7), - ExpirationDate = null, - Disabled = false, - AccessCount = 10, - MaxAccessCount = 10 // Limit reached - }; - var user = CreateUserWithSendIdClaim(sendId); - _sut.ControllerContext = CreateControllerContextWithUser(user); - _sendRepository.GetByIdAsync(sendId).Returns(send); - - await Assert.ThrowsAsync(() => _sut.GetSendFileDownloadDataUsingAuth(fileId)); - - await _sendRepository.Received(1).GetByIdAsync(sendId); - } - - #endregion #endregion diff --git a/test/Core.Test/Tools/Services/SendAuthenticationQueryTests.cs b/test/Core.Test/Tools/Services/SendAuthenticationQueryTests.cs index 7901b3c5c0..56b0f306cb 100644 --- a/test/Core.Test/Tools/Services/SendAuthenticationQueryTests.cs +++ b/test/Core.Test/Tools/Services/SendAuthenticationQueryTests.cs @@ -43,12 +43,12 @@ public class SendAuthenticationQueryTests } [Theory] - [MemberData(nameof(EmailParsingTestCases))] - public async Task GetAuthenticationMethod_WithEmails_ParsesEmailsCorrectly(string emailString, string[] expectedEmails) + [MemberData(nameof(EmailHashesParsingTestCases))] + public async Task GetAuthenticationMethod_WithEmailHashes_ParsesEmailHashesCorrectly(string emailHashString, string[] expectedEmailHashes) { // Arrange var sendId = Guid.NewGuid(); - var send = CreateSend(accessCount: 0, maxAccessCount: 10, emails: emailString, password: null, AuthType.Email); + var send = CreateSend(accessCount: 0, maxAccessCount: 10, emailHashes: emailHashString, password: null, AuthType.Email); _sendRepository.GetByIdAsync(sendId).Returns(send); // Act @@ -56,15 +56,15 @@ public class SendAuthenticationQueryTests // Assert var emailOtp = Assert.IsType(result); - Assert.Equal(expectedEmails, emailOtp.Emails); + Assert.Equal(expectedEmailHashes, emailOtp.Emails); } [Fact] - public async Task GetAuthenticationMethod_WithBothEmailsAndPassword_ReturnsEmailOtp() + public async Task GetAuthenticationMethod_WithBothEmailHashesAndPassword_ReturnsEmailOtp() { // Arrange var sendId = Guid.NewGuid(); - var send = CreateSend(accessCount: 0, maxAccessCount: 10, emails: "test@example.com", password: "hashedpassword", AuthType.Email); + var send = CreateSend(accessCount: 0, maxAccessCount: 10, emailHashes: "hashedemail", password: "hashedpassword", AuthType.Email); _sendRepository.GetByIdAsync(sendId).Returns(send); // Act @@ -79,7 +79,7 @@ public class SendAuthenticationQueryTests { // Arrange var sendId = Guid.NewGuid(); - var send = CreateSend(accessCount: 0, maxAccessCount: 10, emails: null, password: null, AuthType.None); + var send = CreateSend(accessCount: 0, maxAccessCount: 10, emailHashes: null, password: null, AuthType.None); _sendRepository.GetByIdAsync(sendId).Returns(send); // Act @@ -106,32 +106,218 @@ public class SendAuthenticationQueryTests public static IEnumerable AuthenticationMethodTestCases() { yield return new object[] { null, typeof(NeverAuthenticate) }; - yield return new object[] { CreateSend(accessCount: 5, maxAccessCount: 5, emails: null, password: null, AuthType.None), typeof(NeverAuthenticate) }; - yield return new object[] { CreateSend(accessCount: 6, maxAccessCount: 5, emails: null, password: null, AuthType.None), typeof(NeverAuthenticate) }; - yield return new object[] { CreateSend(accessCount: 0, maxAccessCount: 10, emails: "test@example.com", password: null, AuthType.Email), typeof(EmailOtp) }; - yield return new object[] { CreateSend(accessCount: 0, maxAccessCount: 10, emails: null, password: "hashedpassword", AuthType.Password), typeof(ResourcePassword) }; - yield return new object[] { CreateSend(accessCount: 0, maxAccessCount: 10, emails: null, password: null, AuthType.None), typeof(NotAuthenticated) }; + yield return new object[] { CreateSend(accessCount: 5, maxAccessCount: 5, emailHashes: null, password: null, AuthType.None), typeof(NeverAuthenticate) }; + yield return new object[] { CreateSend(accessCount: 6, maxAccessCount: 5, emailHashes: null, password: null, AuthType.None), typeof(NeverAuthenticate) }; + yield return new object[] { CreateSend(accessCount: 0, maxAccessCount: 10, emailHashes: "hashedemail", password: null, AuthType.Email), typeof(EmailOtp) }; + yield return new object[] { CreateSend(accessCount: 0, maxAccessCount: 10, emailHashes: null, password: "hashedpassword", AuthType.Password), typeof(ResourcePassword) }; + yield return new object[] { CreateSend(accessCount: 0, maxAccessCount: 10, emailHashes: null, password: null, AuthType.None), typeof(NotAuthenticated) }; } - public static IEnumerable EmailParsingTestCases() + [Fact] + public async Task GetAuthenticationMethod_WithDisabledSend_ReturnsNeverAuthenticate() { - yield return new object[] { "test@example.com", new[] { "test@example.com" } }; - yield return new object[] { "test1@example.com,test2@example.com", new[] { "test1@example.com", "test2@example.com" } }; - yield return new object[] { " test@example.com , other@example.com ", new[] { "test@example.com", "other@example.com" } }; - yield return new object[] { "test@example.com,,other@example.com", new[] { "test@example.com", "other@example.com" } }; - yield return new object[] { " , test@example.com, ,other@example.com, ", new[] { "test@example.com", "other@example.com" } }; + // Arrange + var sendId = Guid.NewGuid(); + var send = new Send + { + Id = sendId, + AccessCount = 0, + MaxAccessCount = 10, + EmailHashes = "hashedemail", + Password = null, + AuthType = AuthType.Email, + Disabled = true, + DeletionDate = DateTime.UtcNow.AddDays(7), + ExpirationDate = null + }; + _sendRepository.GetByIdAsync(sendId).Returns(send); + + // Act + var result = await _sendAuthenticationQuery.GetAuthenticationMethod(sendId); + + // Assert + Assert.IsType(result); } - private static Send CreateSend(int accessCount, int? maxAccessCount, string? emails, string? password, AuthType? authType) + [Fact] + public async Task GetAuthenticationMethod_WithExpiredSend_ReturnsNeverAuthenticate() + { + // Arrange + var sendId = Guid.NewGuid(); + var send = new Send + { + Id = sendId, + AccessCount = 0, + MaxAccessCount = 10, + EmailHashes = "hashedemail", + Password = null, + AuthType = AuthType.Email, + Disabled = false, + DeletionDate = DateTime.UtcNow.AddDays(7), + ExpirationDate = DateTime.UtcNow.AddDays(-1) // Expired yesterday + }; + _sendRepository.GetByIdAsync(sendId).Returns(send); + + // Act + var result = await _sendAuthenticationQuery.GetAuthenticationMethod(sendId); + + // Assert + Assert.IsType(result); + } + + [Fact] + public async Task GetAuthenticationMethod_WithDeletionDatePassed_ReturnsNeverAuthenticate() + { + // Arrange + var sendId = Guid.NewGuid(); + var send = new Send + { + Id = sendId, + AccessCount = 0, + MaxAccessCount = 10, + EmailHashes = "hashedemail", + Password = null, + AuthType = AuthType.Email, + Disabled = false, + DeletionDate = DateTime.UtcNow.AddDays(-1), // Should have been deleted yesterday + ExpirationDate = null + }; + _sendRepository.GetByIdAsync(sendId).Returns(send); + + // Act + var result = await _sendAuthenticationQuery.GetAuthenticationMethod(sendId); + + // Assert + Assert.IsType(result); + } + + [Fact] + public async Task GetAuthenticationMethod_WithDeletionDateEqualToNow_ReturnsNeverAuthenticate() + { + // Arrange + var sendId = Guid.NewGuid(); + var now = DateTime.UtcNow; + var send = new Send + { + Id = sendId, + AccessCount = 0, + MaxAccessCount = 10, + EmailHashes = "hashedemail", + Password = null, + AuthType = AuthType.Email, + Disabled = false, + DeletionDate = now, // DeletionDate <= DateTime.UtcNow + ExpirationDate = null + }; + _sendRepository.GetByIdAsync(sendId).Returns(send); + + // Act + var result = await _sendAuthenticationQuery.GetAuthenticationMethod(sendId); + + // Assert + Assert.IsType(result); + } + + [Fact] + public async Task GetAuthenticationMethod_WithAccessCountEqualToMaxAccessCount_ReturnsNeverAuthenticate() + { + // Arrange + var sendId = Guid.NewGuid(); + var send = new Send + { + Id = sendId, + AccessCount = 5, + MaxAccessCount = 5, + EmailHashes = "hashedemail", + Password = null, + AuthType = AuthType.Email, + Disabled = false, + DeletionDate = DateTime.UtcNow.AddDays(7), + ExpirationDate = null + }; + _sendRepository.GetByIdAsync(sendId).Returns(send); + + // Act + var result = await _sendAuthenticationQuery.GetAuthenticationMethod(sendId); + + // Assert + Assert.IsType(result); + } + + [Fact] + public async Task GetAuthenticationMethod_WithNullMaxAccessCount_DoesNotRestrictAccess() + { + // Arrange + var sendId = Guid.NewGuid(); + var send = new Send + { + Id = sendId, + AccessCount = 1000, + MaxAccessCount = null, // No limit + EmailHashes = "hashedemail", + Password = null, + AuthType = AuthType.Email, + Disabled = false, + DeletionDate = DateTime.UtcNow.AddDays(7), + ExpirationDate = null + }; + _sendRepository.GetByIdAsync(sendId).Returns(send); + + // Act + var result = await _sendAuthenticationQuery.GetAuthenticationMethod(sendId); + + // Assert + Assert.IsType(result); + } + + [Fact] + public async Task GetAuthenticationMethod_WithNullExpirationDate_DoesNotExpire() + { + // Arrange + var sendId = Guid.NewGuid(); + var send = new Send + { + Id = sendId, + AccessCount = 0, + MaxAccessCount = 10, + EmailHashes = "hashedemail", + Password = null, + AuthType = AuthType.Email, + Disabled = false, + DeletionDate = DateTime.UtcNow.AddDays(7), + ExpirationDate = null // No expiration + }; + _sendRepository.GetByIdAsync(sendId).Returns(send); + + // Act + var result = await _sendAuthenticationQuery.GetAuthenticationMethod(sendId); + + // Assert + Assert.IsType(result); + } + + public static IEnumerable EmailHashesParsingTestCases() + { + yield return new object[] { "hash1", new[] { "hash1" } }; + yield return new object[] { "hash1,hash2", new[] { "hash1", "hash2" } }; + yield return new object[] { " hash1 , hash2 ", new[] { "hash1", "hash2" } }; + yield return new object[] { "hash1,,hash2", new[] { "hash1", "hash2" } }; + yield return new object[] { " , hash1, ,hash2, ", new[] { "hash1", "hash2" } }; + } + + private static Send CreateSend(int accessCount, int? maxAccessCount, string? emailHashes, string? password, AuthType? authType) { return new Send { Id = Guid.NewGuid(), AccessCount = accessCount, MaxAccessCount = maxAccessCount, - Emails = emails, + EmailHashes = emailHashes, Password = password, - AuthType = authType + AuthType = authType, + Disabled = false, + DeletionDate = DateTime.UtcNow.AddDays(7), + ExpirationDate = null }; } }