diff --git a/src/Api/Tools/Controllers/SendsController.cs b/src/Api/Tools/Controllers/SendsController.cs
index f9f71d076d..61002a0168 100644
--- a/src/Api/Tools/Controllers/SendsController.cs
+++ b/src/Api/Tools/Controllers/SendsController.cs
@@ -239,12 +239,6 @@ public class SendsController : Controller
{
throw new BadRequestException("Could not locate send");
}
- if (send.MaxAccessCount.GetValueOrDefault(int.MaxValue) <= send.AccessCount ||
- send.ExpirationDate.GetValueOrDefault(DateTime.MaxValue) < DateTime.UtcNow || send.Disabled ||
- send.DeletionDate < DateTime.UtcNow)
- {
- throw new NotFoundException();
- }
var sendResponse = new SendAccessResponseModel(send);
if (send.UserId.HasValue && !send.HideEmail.GetValueOrDefault())
@@ -272,12 +266,6 @@ public class SendsController : Controller
{
throw new BadRequestException("Could not locate send");
}
- if (send.MaxAccessCount.GetValueOrDefault(int.MaxValue) <= send.AccessCount ||
- send.ExpirationDate.GetValueOrDefault(DateTime.MaxValue) < DateTime.UtcNow || send.Disabled ||
- send.DeletionDate < DateTime.UtcNow)
- {
- throw new NotFoundException();
- }
var url = await _sendFileStorageService.GetSendFileDownloadUrlAsync(send, fileId);
diff --git a/src/Api/Tools/Models/Request/SendRequestModel.cs b/src/Api/Tools/Models/Request/SendRequestModel.cs
index f3308dbd5a..00dcb6273f 100644
--- a/src/Api/Tools/Models/Request/SendRequestModel.cs
+++ b/src/Api/Tools/Models/Request/SendRequestModel.cs
@@ -102,9 +102,17 @@ public class SendRequestModel
/// Comma-separated list of emails that may access the send using OTP
/// authentication. Mutually exclusive with .
///
- [StringLength(4000)]
+ [EncryptedString]
+ [EncryptedStringLength(4000)]
public string Emails { get; set; }
+ ///
+ /// Comma-separated list of email **hashes** that may access the send using OTP
+ /// authentication. Mutually exclusive with .
+ ///
+ [StringLength(4000)]
+ public string EmailHashes { get; set; }
+
///
/// When , send access is disabled.
/// Defaults to .
@@ -253,6 +261,7 @@ public class SendRequestModel
// normalize encoding
var emails = Emails.Split(',', RemoveEmptyEntries | TrimEntries);
existingSend.Emails = string.Join(",", emails);
+ existingSend.EmailHashes = EmailHashes;
existingSend.Password = null;
existingSend.AuthType = Core.Tools.Enums.AuthType.Email;
}
diff --git a/src/Core/Tools/Entities/Send.cs b/src/Core/Tools/Entities/Send.cs
index 52b439c41e..c4398e212c 100644
--- a/src/Core/Tools/Entities/Send.cs
+++ b/src/Core/Tools/Entities/Send.cs
@@ -81,6 +81,15 @@ public class Send : ITableObject
[MaxLength(4000)]
public string? Emails { get; set; }
+ ///
+ /// Comma-separated list of email **hashes** for OTP authentication.
+ ///
+ ///
+ /// This field is mutually exclusive with
+ ///
+ [MaxLength(4000)]
+ public string? EmailHashes { get; set; }
+
///
/// The send becomes unavailable to API callers when
/// >= .
diff --git a/src/Core/Tools/Models/Data/SendAuthenticationTypes.cs b/src/Core/Tools/Models/Data/SendAuthenticationTypes.cs
index 9ce477ed0c..c90dba43a8 100644
--- a/src/Core/Tools/Models/Data/SendAuthenticationTypes.cs
+++ b/src/Core/Tools/Models/Data/SendAuthenticationTypes.cs
@@ -45,6 +45,6 @@ public record ResourcePassword(string Hash) : SendAuthenticationMethod;
/// Create a send claim by requesting a one time password (OTP) confirmation code.
///
///
-/// The list of email addresses permitted access to the send.
+/// The list of email address **hashes** permitted access to the send.
///
public record EmailOtp(string[] Emails) : SendAuthenticationMethod;
diff --git a/src/Core/Tools/SendFeatures/Queries/SendAuthenticationQuery.cs b/src/Core/Tools/SendFeatures/Queries/SendAuthenticationQuery.cs
index 97c2e64dc5..a82c27d0c3 100644
--- a/src/Core/Tools/SendFeatures/Queries/SendAuthenticationQuery.cs
+++ b/src/Core/Tools/SendFeatures/Queries/SendAuthenticationQuery.cs
@@ -37,8 +37,11 @@ public class SendAuthenticationQuery : ISendAuthenticationQuery
SendAuthenticationMethod method = send switch
{
null => NEVER_AUTHENTICATE,
- var s when s.AccessCount >= s.MaxAccessCount => NEVER_AUTHENTICATE,
- var s when s.AuthType == AuthType.Email && s.Emails is not null => emailOtp(s.Emails),
+ var s when s.Disabled => NEVER_AUTHENTICATE,
+ var s when s.AccessCount >= s.MaxAccessCount.GetValueOrDefault(int.MaxValue) => NEVER_AUTHENTICATE,
+ var s when s.ExpirationDate.GetValueOrDefault(DateTime.MaxValue) < DateTime.UtcNow => NEVER_AUTHENTICATE,
+ var s when s.DeletionDate <= DateTime.UtcNow => NEVER_AUTHENTICATE,
+ var s when s.AuthType == AuthType.Email && s.EmailHashes is not null => EmailOtp(s.EmailHashes),
var s when s.AuthType == AuthType.Password && s.Password is not null => new ResourcePassword(s.Password),
_ => NOT_AUTHENTICATED
};
@@ -46,9 +49,13 @@ public class SendAuthenticationQuery : ISendAuthenticationQuery
return method;
}
- private EmailOtp emailOtp(string emails)
+ private static EmailOtp EmailOtp(string? emailHashes)
{
- var list = emails.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
+ if (string.IsNullOrWhiteSpace(emailHashes))
+ {
+ return new EmailOtp([]);
+ }
+ var list = emailHashes.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
return new EmailOtp(list);
}
}
diff --git a/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs b/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs
index 81a94f0f7c..4c5d70340f 100644
--- a/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs
+++ b/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs
@@ -1,6 +1,7 @@
#nullable enable
using System.Data;
+using Bit.Core;
using Bit.Core.KeyManagement.UserKey;
using Bit.Core.Settings;
using Bit.Core.Tools.Entities;
@@ -8,6 +9,7 @@ using Bit.Core.Tools.Repositories;
using Bit.Infrastructure.Dapper.Repositories;
using Bit.Infrastructure.Dapper.Tools.Helpers;
using Dapper;
+using Microsoft.AspNetCore.DataProtection;
using Microsoft.Data.SqlClient;
namespace Bit.Infrastructure.Dapper.Tools.Repositories;
@@ -15,13 +17,24 @@ namespace Bit.Infrastructure.Dapper.Tools.Repositories;
///
public class SendRepository : Repository, ISendRepository
{
- public SendRepository(GlobalSettings globalSettings)
- : this(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString)
+ private readonly IDataProtector _dataProtector;
+
+ public SendRepository(GlobalSettings globalSettings, IDataProtectionProvider dataProtectionProvider)
+ : this(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString, dataProtectionProvider)
{ }
- public SendRepository(string connectionString, string readOnlyConnectionString)
+ public SendRepository(string connectionString, string readOnlyConnectionString, IDataProtectionProvider dataProtectionProvider)
: base(connectionString, readOnlyConnectionString)
- { }
+ {
+ _dataProtector = dataProtectionProvider.CreateProtector(Constants.DatabaseFieldProtectorPurpose);
+ }
+
+ public override async Task GetByIdAsync(Guid id)
+ {
+ var send = await base.GetByIdAsync(id);
+ UnprotectData(send);
+ return send;
+ }
///
public async Task> GetManyByUserIdAsync(Guid userId)
@@ -33,7 +46,9 @@ public class SendRepository : Repository, ISendRepository
new { UserId = userId },
commandType: CommandType.StoredProcedure);
- return results.ToList();
+ var sends = results.ToList();
+ UnprotectData(sends);
+ return sends;
}
}
@@ -47,15 +62,35 @@ public class SendRepository : Repository, ISendRepository
new { DeletionDate = deletionDateBefore },
commandType: CommandType.StoredProcedure);
- return results.ToList();
+ var sends = results.ToList();
+ UnprotectData(sends);
+ return sends;
}
}
+ public override async Task CreateAsync(Send send)
+ {
+ await ProtectDataAndSaveAsync(send, async () => await base.CreateAsync(send));
+ return send;
+ }
+
+ public override async Task ReplaceAsync(Send send)
+ {
+ await ProtectDataAndSaveAsync(send, async () => await base.ReplaceAsync(send));
+ }
+
///
public UpdateEncryptedDataForKeyRotation UpdateForKeyRotation(Guid userId, IEnumerable sends)
{
return async (connection, transaction) =>
{
+ // Protect all sends before bulk update
+ var sendsList = sends.ToList();
+ foreach (var send in sendsList)
+ {
+ ProtectData(send);
+ }
+
// Create temp table
var sqlCreateTemp = @"
SELECT TOP 0 *
@@ -71,7 +106,7 @@ public class SendRepository : Repository, ISendRepository
using (var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity, transaction))
{
bulkCopy.DestinationTableName = "#TempSend";
- var sendsTable = sends.ToDataTable();
+ var sendsTable = sendsList.ToDataTable();
foreach (DataColumn col in sendsTable.Columns)
{
bulkCopy.ColumnMappings.Add(col.ColumnName, col.ColumnName);
@@ -101,6 +136,69 @@ public class SendRepository : Repository, ISendRepository
cmd.Parameters.Add("@UserId", SqlDbType.UniqueIdentifier).Value = userId;
cmd.ExecuteNonQuery();
}
+
+ // Unprotect after save
+ foreach (var send in sendsList)
+ {
+ UnprotectData(send);
+ }
};
}
+
+ private async Task ProtectDataAndSaveAsync(Send send, Func saveTask)
+ {
+ if (send == null)
+ {
+ await saveTask();
+ return;
+ }
+
+ // Capture original value
+ var originalEmailHashes = send.EmailHashes;
+
+ // Protect value
+ ProtectData(send);
+
+ // Save
+ await saveTask();
+
+ // Restore original value
+ send.EmailHashes = originalEmailHashes;
+ }
+
+ private void ProtectData(Send send)
+ {
+ if (!send.EmailHashes?.StartsWith(Constants.DatabaseFieldProtectedPrefix) ?? false)
+ {
+ send.EmailHashes = string.Concat(Constants.DatabaseFieldProtectedPrefix,
+ _dataProtector.Protect(send.EmailHashes!));
+ }
+ }
+
+ private void UnprotectData(Send? send)
+ {
+ if (send == null)
+ {
+ return;
+ }
+
+ if (send.EmailHashes?.StartsWith(Constants.DatabaseFieldProtectedPrefix) ?? false)
+ {
+ send.EmailHashes = _dataProtector.Unprotect(
+ send.EmailHashes.Substring(Constants.DatabaseFieldProtectedPrefix.Length));
+ }
+ }
+
+ private void UnprotectData(IEnumerable sends)
+ {
+ if (sends == null)
+ {
+ return;
+ }
+
+ foreach (var send in sends)
+ {
+ UnprotectData(send);
+ }
+ }
}
diff --git a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs
index a0ee0376c0..3f638f88e5 100644
--- a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs
+++ b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs
@@ -119,6 +119,7 @@ public class DatabaseContext : DbContext
var eOrganizationDomain = builder.Entity();
var aWebAuthnCredential = builder.Entity();
var eOrganizationMemberBaseDetail = builder.Entity();
+ var eSend = builder.Entity();
// Shadow property configurations go here
@@ -148,6 +149,7 @@ public class DatabaseContext : DbContext
var dataProtectionConverter = new DataProtectionConverter(dataProtector);
eUser.Property(c => c.Key).HasConversion(dataProtectionConverter);
eUser.Property(c => c.MasterPassword).HasConversion(dataProtectionConverter);
+ eSend.Property(c => c.EmailHashes).HasConversion(dataProtectionConverter);
if (Database.IsNpgsql())
{
diff --git a/src/Sql/dbo/Tools/Stored Procedures/Send_Create.sql b/src/Sql/dbo/Tools/Stored Procedures/Send_Create.sql
index 752f8fb496..e277174717 100644
--- a/src/Sql/dbo/Tools/Stored Procedures/Send_Create.sql
+++ b/src/Sql/dbo/Tools/Stored Procedures/Send_Create.sql
@@ -18,7 +18,8 @@
-- FIXME: remove null default value once this argument has been
-- in 2 server releases
@Emails NVARCHAR(4000) = NULL,
- @AuthType TINYINT = NULL
+ @AuthType TINYINT = NULL,
+ @EmailHashes NVARCHAR(4000) = NULL
AS
BEGIN
SET NOCOUNT ON
@@ -42,7 +43,8 @@ BEGIN
[HideEmail],
[CipherId],
[Emails],
- [AuthType]
+ [AuthType],
+ [EmailHashes]
)
VALUES
(
@@ -63,7 +65,8 @@ BEGIN
@HideEmail,
@CipherId,
@Emails,
- @AuthType
+ @AuthType,
+ @EmailHashes
)
IF @UserId IS NOT NULL
diff --git a/src/Sql/dbo/Tools/Stored Procedures/Send_Update.sql b/src/Sql/dbo/Tools/Stored Procedures/Send_Update.sql
index fba842d8d6..a2bcb0a24b 100644
--- a/src/Sql/dbo/Tools/Stored Procedures/Send_Update.sql
+++ b/src/Sql/dbo/Tools/Stored Procedures/Send_Update.sql
@@ -16,7 +16,8 @@
@HideEmail BIT,
@CipherId UNIQUEIDENTIFIER = NULL,
@Emails NVARCHAR(4000) = NULL,
- @AuthType TINYINT = NULL
+ @AuthType TINYINT = NULL,
+ @EmailHashes NVARCHAR(4000) = NULL
AS
BEGIN
SET NOCOUNT ON
@@ -40,7 +41,8 @@ BEGIN
[HideEmail] = @HideEmail,
[CipherId] = @CipherId,
[Emails] = @Emails,
- [AuthType] = @AuthType
+ [AuthType] = @AuthType,
+ [EmailHashes] = @EmailHashes
WHERE
[Id] = @Id
diff --git a/src/Sql/dbo/Tools/Tables/Send.sql b/src/Sql/dbo/Tools/Tables/Send.sql
index 94311d6328..59a42a2aa5 100644
--- a/src/Sql/dbo/Tools/Tables/Send.sql
+++ b/src/Sql/dbo/Tools/Tables/Send.sql
@@ -1,22 +1,24 @@
-CREATE TABLE [dbo].[Send] (
+CREATE TABLE [dbo].[Send]
+(
[Id] UNIQUEIDENTIFIER NOT NULL,
[UserId] UNIQUEIDENTIFIER NULL,
[OrganizationId] UNIQUEIDENTIFIER NULL,
[Type] TINYINT NOT NULL,
[Data] VARCHAR(MAX) NOT NULL,
- [Key] VARCHAR (MAX) NOT NULL,
- [Password] NVARCHAR (300) NULL,
- [Emails] NVARCHAR (4000) NULL,
+ [Key] VARCHAR(MAX) NOT NULL,
+ [Password] NVARCHAR(300) NULL,
+ [Emails] NVARCHAR(4000) NULL,
[MaxAccessCount] INT NULL,
[AccessCount] INT NOT NULL,
- [CreationDate] DATETIME2 (7) NOT NULL,
- [RevisionDate] DATETIME2 (7) NOT NULL,
- [ExpirationDate] DATETIME2 (7) NULL,
- [DeletionDate] DATETIME2 (7) NOT NULL,
+ [CreationDate] DATETIME2(7) NOT NULL,
+ [RevisionDate] DATETIME2(7) NOT NULL,
+ [ExpirationDate] DATETIME2(7) NULL,
+ [DeletionDate] DATETIME2(7) NOT NULL,
[Disabled] BIT NOT NULL,
[HideEmail] BIT NULL,
[CipherId] UNIQUEIDENTIFIER NULL,
[AuthType] TINYINT NULL,
+ [EmailHashes] NVARCHAR(4000) NULL,
CONSTRAINT [PK_Send] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_Send_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]),
CONSTRAINT [FK_Send_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]),
@@ -26,9 +28,9 @@
GO
CREATE NONCLUSTERED INDEX [IX_Send_UserId_OrganizationId]
- ON [dbo].[Send]([UserId] ASC, [OrganizationId] ASC);
+ ON [dbo].[Send] ([UserId] ASC, [OrganizationId] ASC);
GO
CREATE NONCLUSTERED INDEX [IX_Send_DeletionDate]
- ON [dbo].[Send]([DeletionDate] ASC);
+ ON [dbo].[Send] ([DeletionDate] ASC);
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