mirror of
https://github.com/bitwarden/server
synced 2026-02-12 14:33:49 +00:00
[PM-31684] Remove email hashing for send access (#6945)
* [PM-31684] Remove email hashing for send access * [PM-31684] switching the order of migration files * [PM-31684] adding more migrations * [PM-31684] Removing anon access emails field and reusing emails field * [PM-31684] cleanup before adding migrations back * [PM-31684] restore original snapshots * [PM-31684] restore original postgres snapshots * [PM-31684] adding migrations * [PM-31684] removing encryption attributes from emails request model * [PM-31684] adding missing stored proc alters * [PM-31684] Improved formatting for stored proc defs * [PM-31684] adding necessary comment back * [PM-31684] adding case-insensitive check on the server for send auth
This commit is contained in:
@@ -1,17 +0,0 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Bit.Test.Common.Helpers;
|
||||
|
||||
public class CryptographyHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a hex-encoded, SHA256 hash for the given string
|
||||
/// </summary>
|
||||
public static string HashAndEncode(string text)
|
||||
{
|
||||
var hashBytes = SHA256.HashData(Encoding.UTF8.GetBytes(text));
|
||||
var hashEncoded = Convert.ToHexString(hashBytes).ToUpperInvariant();
|
||||
return hashEncoded;
|
||||
}
|
||||
}
|
||||
@@ -43,12 +43,12 @@ public class SendAuthenticationQueryTests
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(EmailHashesParsingTestCases))]
|
||||
public async Task GetAuthenticationMethod_WithEmailHashes_ParsesEmailHashesCorrectly(string emailHashString, string[] expectedEmailHashes)
|
||||
[MemberData(nameof(EmailsParsingTestCases))]
|
||||
public async Task GetAuthenticationMethod_WithEmails_ParsesEmailsCorrectly(string emailString, string[] expectedEmails)
|
||||
{
|
||||
// Arrange
|
||||
var sendId = Guid.NewGuid();
|
||||
var send = CreateSend(accessCount: 0, maxAccessCount: 10, emailHashes: emailHashString, password: null, AuthType.Email);
|
||||
var send = CreateSend(accessCount: 0, maxAccessCount: 10, emails: emailString, password: null, AuthType.Email);
|
||||
_sendRepository.GetByIdAsync(sendId).Returns(send);
|
||||
|
||||
// Act
|
||||
@@ -56,15 +56,15 @@ public class SendAuthenticationQueryTests
|
||||
|
||||
// Assert
|
||||
var emailOtp = Assert.IsType<EmailOtp>(result);
|
||||
Assert.Equal(expectedEmailHashes, emailOtp.EmailHashes);
|
||||
Assert.Equal(expectedEmails, emailOtp.emails);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetAuthenticationMethod_WithBothEmailHashesAndPassword_ReturnsEmailOtp()
|
||||
public async Task GetAuthenticationMethod_WithBothEmailsAndPassword_ReturnsEmailOtp()
|
||||
{
|
||||
// Arrange
|
||||
var sendId = Guid.NewGuid();
|
||||
var send = CreateSend(accessCount: 0, maxAccessCount: 10, emailHashes: "hashedemail", password: "hashedpassword", AuthType.Email);
|
||||
var send = CreateSend(accessCount: 0, maxAccessCount: 10, emails: "person@company.com", 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, emailHashes: null, password: null, AuthType.None);
|
||||
var send = CreateSend(accessCount: 0, maxAccessCount: 10, emails: null, password: null, AuthType.None);
|
||||
_sendRepository.GetByIdAsync(sendId).Returns(send);
|
||||
|
||||
// Act
|
||||
@@ -106,11 +106,11 @@ public class SendAuthenticationQueryTests
|
||||
public static IEnumerable<object[]> AuthenticationMethodTestCases()
|
||||
{
|
||||
yield return new object[] { null, typeof(NeverAuthenticate) };
|
||||
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) };
|
||||
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: "person@company.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) };
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -123,7 +123,7 @@ public class SendAuthenticationQueryTests
|
||||
Id = sendId,
|
||||
AccessCount = 0,
|
||||
MaxAccessCount = 10,
|
||||
EmailHashes = "hashedemail",
|
||||
Emails = "person@company.com",
|
||||
Password = null,
|
||||
AuthType = AuthType.Email,
|
||||
Disabled = true,
|
||||
@@ -149,7 +149,7 @@ public class SendAuthenticationQueryTests
|
||||
Id = sendId,
|
||||
AccessCount = 0,
|
||||
MaxAccessCount = 10,
|
||||
EmailHashes = "hashedemail",
|
||||
Emails = "person@company.com",
|
||||
Password = null,
|
||||
AuthType = AuthType.Email,
|
||||
Disabled = false,
|
||||
@@ -175,7 +175,7 @@ public class SendAuthenticationQueryTests
|
||||
Id = sendId,
|
||||
AccessCount = 0,
|
||||
MaxAccessCount = 10,
|
||||
EmailHashes = "hashedemail",
|
||||
Emails = "person@company.com",
|
||||
Password = null,
|
||||
AuthType = AuthType.Email,
|
||||
Disabled = false,
|
||||
@@ -202,7 +202,7 @@ public class SendAuthenticationQueryTests
|
||||
Id = sendId,
|
||||
AccessCount = 0,
|
||||
MaxAccessCount = 10,
|
||||
EmailHashes = "hashedemail",
|
||||
Emails = "person@company.com",
|
||||
Password = null,
|
||||
AuthType = AuthType.Email,
|
||||
Disabled = false,
|
||||
@@ -228,7 +228,7 @@ public class SendAuthenticationQueryTests
|
||||
Id = sendId,
|
||||
AccessCount = 5,
|
||||
MaxAccessCount = 5,
|
||||
EmailHashes = "hashedemail",
|
||||
Emails = "person@company.com",
|
||||
Password = null,
|
||||
AuthType = AuthType.Email,
|
||||
Disabled = false,
|
||||
@@ -254,7 +254,7 @@ public class SendAuthenticationQueryTests
|
||||
Id = sendId,
|
||||
AccessCount = 1000,
|
||||
MaxAccessCount = null, // No limit
|
||||
EmailHashes = "hashedemail",
|
||||
Emails = "person@company.com",
|
||||
Password = null,
|
||||
AuthType = AuthType.Email,
|
||||
Disabled = false,
|
||||
@@ -280,7 +280,7 @@ public class SendAuthenticationQueryTests
|
||||
Id = sendId,
|
||||
AccessCount = 0,
|
||||
MaxAccessCount = 10,
|
||||
EmailHashes = "hashedemail",
|
||||
Emails = "person@company.com",
|
||||
Password = null,
|
||||
AuthType = AuthType.Email,
|
||||
Disabled = false,
|
||||
@@ -296,23 +296,23 @@ public class SendAuthenticationQueryTests
|
||||
Assert.IsType<EmailOtp>(result);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> EmailHashesParsingTestCases()
|
||||
public static IEnumerable<object[]> EmailsParsingTestCases()
|
||||
{
|
||||
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" } };
|
||||
yield return new object[] { "person@company.com", new[] { "person@company.com" } };
|
||||
yield return new object[] { "person1@company.com,person2@company.com", new[] { "person1@company.com", "person2@company.com" } };
|
||||
yield return new object[] { " person1@company.com , person2@company.com ", new[] { "person1@company.com", "person2@company.com" } };
|
||||
yield return new object[] { "person1@company.com,,person2@company.com", new[] { "person1@company.com", "person2@company.com" } };
|
||||
yield return new object[] { " , person1@company.com, ,person2@company.com, ", new[] { "person1@company.com", "person2@company.com" } };
|
||||
}
|
||||
|
||||
private static Send CreateSend(int accessCount, int? maxAccessCount, string? emailHashes, string? password, AuthType? authType)
|
||||
private static Send CreateSend(int accessCount, int? maxAccessCount, string? emails, string? password, AuthType? authType)
|
||||
{
|
||||
return new Send
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
AccessCount = accessCount,
|
||||
MaxAccessCount = maxAccessCount,
|
||||
EmailHashes = emailHashes,
|
||||
Emails = emails,
|
||||
Password = password,
|
||||
AuthType = authType,
|
||||
Disabled = false,
|
||||
|
||||
@@ -3,7 +3,6 @@ using Bit.Core.Services;
|
||||
using Bit.Core.Tools.Models.Data;
|
||||
using Bit.Core.Tools.SendFeatures.Queries.Interfaces;
|
||||
using Bit.IntegrationTestCommon.Factories;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using Duende.IdentityModel;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
@@ -61,7 +60,7 @@ public class SendEmailOtpRequestValidatorIntegrationTests(IdentityApplicationFac
|
||||
|
||||
var sendAuthQuery = Substitute.For<ISendAuthenticationQuery>();
|
||||
sendAuthQuery.GetAuthenticationMethod(sendId)
|
||||
.Returns(new EmailOtp([CryptographyHelper.HashAndEncode(email)]));
|
||||
.Returns(new EmailOtp([email]));
|
||||
services.AddSingleton(sendAuthQuery);
|
||||
|
||||
// Mock OTP token provider
|
||||
@@ -106,7 +105,7 @@ public class SendEmailOtpRequestValidatorIntegrationTests(IdentityApplicationFac
|
||||
|
||||
var sendAuthQuery = Substitute.For<ISendAuthenticationQuery>();
|
||||
sendAuthQuery.GetAuthenticationMethod(sendId)
|
||||
.Returns(new EmailOtp(new[] { CryptographyHelper.HashAndEncode(email) }));
|
||||
.Returns(new EmailOtp(new[] { email }));
|
||||
services.AddSingleton(sendAuthQuery);
|
||||
|
||||
// Mock OTP token provider to validate successfully
|
||||
@@ -150,7 +149,7 @@ public class SendEmailOtpRequestValidatorIntegrationTests(IdentityApplicationFac
|
||||
|
||||
var sendAuthQuery = Substitute.For<ISendAuthenticationQuery>();
|
||||
sendAuthQuery.GetAuthenticationMethod(sendId)
|
||||
.Returns(new EmailOtp(new[] { CryptographyHelper.HashAndEncode(email) }));
|
||||
.Returns(new EmailOtp(new[] { email }));
|
||||
services.AddSingleton(sendAuthQuery);
|
||||
|
||||
// Mock OTP token provider to validate as false
|
||||
@@ -192,7 +191,7 @@ public class SendEmailOtpRequestValidatorIntegrationTests(IdentityApplicationFac
|
||||
|
||||
var sendAuthQuery = Substitute.For<ISendAuthenticationQuery>();
|
||||
sendAuthQuery.GetAuthenticationMethod(sendId)
|
||||
.Returns(new EmailOtp(new[] { CryptographyHelper.HashAndEncode(email) }));
|
||||
.Returns(new EmailOtp(new[] { email }));
|
||||
services.AddSingleton(sendAuthQuery);
|
||||
|
||||
// Mock OTP token provider to fail generation
|
||||
|
||||
@@ -5,7 +5,6 @@ using Bit.Core.Tools.Models.Data;
|
||||
using Bit.Identity.IdentityServer.RequestValidators.SendAccess;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using Duende.IdentityModel;
|
||||
using Duende.IdentityServer.Validation;
|
||||
using NSubstitute;
|
||||
@@ -106,8 +105,7 @@ public class SendEmailOtpRequestValidatorTests
|
||||
expectedUniqueId)
|
||||
.Returns(generatedToken);
|
||||
|
||||
var emailHash = CryptographyHelper.HashAndEncode(email);
|
||||
emailOtp = emailOtp with { EmailHashes = [emailHash] };
|
||||
emailOtp = emailOtp with { emails = [email] };
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.ValidateRequestAsync(context, emailOtp, sendId);
|
||||
@@ -146,8 +144,7 @@ public class SendEmailOtpRequestValidatorTests
|
||||
Request = tokenRequest
|
||||
};
|
||||
|
||||
var emailHash = CryptographyHelper.HashAndEncode(email);
|
||||
emailOtp = emailOtp with { EmailHashes = [emailHash] };
|
||||
emailOtp = emailOtp with { emails = [email] };
|
||||
|
||||
sutProvider.GetDependency<IOtpTokenProvider<DefaultOtpTokenProviderOptions>>()
|
||||
.GenerateTokenAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>())
|
||||
@@ -182,8 +179,7 @@ public class SendEmailOtpRequestValidatorTests
|
||||
Request = tokenRequest
|
||||
};
|
||||
|
||||
var emailHash = CryptographyHelper.HashAndEncode(email);
|
||||
emailOtp = emailOtp with { EmailHashes = [emailHash] };
|
||||
emailOtp = emailOtp with { emails = [email] };
|
||||
|
||||
var expectedUniqueId = string.Format(SendAccessConstants.OtpToken.TokenUniqueIdentifier, sendId, email);
|
||||
|
||||
@@ -235,8 +231,7 @@ public class SendEmailOtpRequestValidatorTests
|
||||
Request = tokenRequest
|
||||
};
|
||||
|
||||
var emailHash = CryptographyHelper.HashAndEncode(email);
|
||||
emailOtp = emailOtp with { EmailHashes = [emailHash] };
|
||||
emailOtp = emailOtp with { emails = [email] };
|
||||
|
||||
var expectedUniqueId = string.Format(SendAccessConstants.OtpToken.TokenUniqueIdentifier, sendId, email);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user