mirror of
https://github.com/bitwarden/server.git
synced 2026-01-31 14:13:18 +08:00
[PM-31394] use email address hash for send access email verification (#6921)
* [PM-31394] use email address hash for send access email verification * [PM-31394] fixing identity server tests for send access * [PM-31394] fixing more identity server tests for send access
This commit is contained in:
@@ -44,7 +44,7 @@ public record ResourcePassword(string Hash) : SendAuthenticationMethod;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a send claim by requesting a one time password (OTP) confirmation code.
|
/// Create a send claim by requesting a one time password (OTP) confirmation code.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="Emails">
|
/// <param name="EmailHashes">
|
||||||
/// The list of email address **hashes** permitted access to the send.
|
/// The list of email address **hashes** permitted access to the send.
|
||||||
/// </param>
|
/// </param>
|
||||||
public record EmailOtp(string[] Emails) : SendAuthenticationMethod;
|
public record EmailOtp(string[] EmailHashes) : SendAuthenticationMethod;
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.Auth.Identity;
|
using Bit.Core.Auth.Identity;
|
||||||
using Bit.Core.Auth.Identity.TokenProviders;
|
using Bit.Core.Auth.Identity.TokenProviders;
|
||||||
@@ -40,8 +42,10 @@ public class SendEmailOtpRequestValidator(
|
|||||||
return BuildErrorResult(SendAccessConstants.EmailOtpValidatorResults.EmailRequired);
|
return BuildErrorResult(SendAccessConstants.EmailOtpValidatorResults.EmailRequired);
|
||||||
}
|
}
|
||||||
|
|
||||||
// email must be in the list of emails in the EmailOtp array
|
// email hash must be in the list of email hashes in the EmailOtp array
|
||||||
if (!authMethod.Emails.Contains(email))
|
byte[] hashBytes = SHA256.HashData(Encoding.UTF8.GetBytes(email));
|
||||||
|
string hashEmailHex = Convert.ToHexString(hashBytes).ToUpperInvariant();
|
||||||
|
if (!authMethod.EmailHashes.Contains(hashEmailHex))
|
||||||
{
|
{
|
||||||
return BuildErrorResult(SendAccessConstants.EmailOtpValidatorResults.EmailInvalid);
|
return BuildErrorResult(SendAccessConstants.EmailOtpValidatorResults.EmailInvalid);
|
||||||
}
|
}
|
||||||
|
|||||||
17
test/Common/Helpers/CryptographyHelper.cs
Normal file
17
test/Common/Helpers/CryptographyHelper.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -56,7 +56,7 @@ public class SendAuthenticationQueryTests
|
|||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var emailOtp = Assert.IsType<EmailOtp>(result);
|
var emailOtp = Assert.IsType<EmailOtp>(result);
|
||||||
Assert.Equal(expectedEmailHashes, emailOtp.Emails);
|
Assert.Equal(expectedEmailHashes, emailOtp.EmailHashes);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using Bit.Core.Services;
|
|||||||
using Bit.Core.Tools.Models.Data;
|
using Bit.Core.Tools.Models.Data;
|
||||||
using Bit.Core.Tools.SendFeatures.Queries.Interfaces;
|
using Bit.Core.Tools.SendFeatures.Queries.Interfaces;
|
||||||
using Bit.IntegrationTestCommon.Factories;
|
using Bit.IntegrationTestCommon.Factories;
|
||||||
|
using Bit.Test.Common.Helpers;
|
||||||
using Duende.IdentityModel;
|
using Duende.IdentityModel;
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
@@ -60,7 +61,7 @@ public class SendEmailOtpRequestValidatorIntegrationTests(IdentityApplicationFac
|
|||||||
|
|
||||||
var sendAuthQuery = Substitute.For<ISendAuthenticationQuery>();
|
var sendAuthQuery = Substitute.For<ISendAuthenticationQuery>();
|
||||||
sendAuthQuery.GetAuthenticationMethod(sendId)
|
sendAuthQuery.GetAuthenticationMethod(sendId)
|
||||||
.Returns(new EmailOtp([email]));
|
.Returns(new EmailOtp([CryptographyHelper.HashAndEncode(email)]));
|
||||||
services.AddSingleton(sendAuthQuery);
|
services.AddSingleton(sendAuthQuery);
|
||||||
|
|
||||||
// Mock OTP token provider
|
// Mock OTP token provider
|
||||||
@@ -75,6 +76,7 @@ public class SendEmailOtpRequestValidatorIntegrationTests(IdentityApplicationFac
|
|||||||
});
|
});
|
||||||
}).CreateClient();
|
}).CreateClient();
|
||||||
|
|
||||||
|
|
||||||
var requestBody = SendAccessTestUtilities.CreateTokenRequestBody(sendId, email: email); // Email but no OTP
|
var requestBody = SendAccessTestUtilities.CreateTokenRequestBody(sendId, email: email); // Email but no OTP
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
@@ -104,7 +106,7 @@ public class SendEmailOtpRequestValidatorIntegrationTests(IdentityApplicationFac
|
|||||||
|
|
||||||
var sendAuthQuery = Substitute.For<ISendAuthenticationQuery>();
|
var sendAuthQuery = Substitute.For<ISendAuthenticationQuery>();
|
||||||
sendAuthQuery.GetAuthenticationMethod(sendId)
|
sendAuthQuery.GetAuthenticationMethod(sendId)
|
||||||
.Returns(new EmailOtp(new[] { email }));
|
.Returns(new EmailOtp(new[] { CryptographyHelper.HashAndEncode(email) }));
|
||||||
services.AddSingleton(sendAuthQuery);
|
services.AddSingleton(sendAuthQuery);
|
||||||
|
|
||||||
// Mock OTP token provider to validate successfully
|
// Mock OTP token provider to validate successfully
|
||||||
@@ -148,7 +150,7 @@ public class SendEmailOtpRequestValidatorIntegrationTests(IdentityApplicationFac
|
|||||||
|
|
||||||
var sendAuthQuery = Substitute.For<ISendAuthenticationQuery>();
|
var sendAuthQuery = Substitute.For<ISendAuthenticationQuery>();
|
||||||
sendAuthQuery.GetAuthenticationMethod(sendId)
|
sendAuthQuery.GetAuthenticationMethod(sendId)
|
||||||
.Returns(new EmailOtp(new[] { email }));
|
.Returns(new EmailOtp(new[] { CryptographyHelper.HashAndEncode(email) }));
|
||||||
services.AddSingleton(sendAuthQuery);
|
services.AddSingleton(sendAuthQuery);
|
||||||
|
|
||||||
// Mock OTP token provider to validate as false
|
// Mock OTP token provider to validate as false
|
||||||
@@ -190,7 +192,7 @@ public class SendEmailOtpRequestValidatorIntegrationTests(IdentityApplicationFac
|
|||||||
|
|
||||||
var sendAuthQuery = Substitute.For<ISendAuthenticationQuery>();
|
var sendAuthQuery = Substitute.For<ISendAuthenticationQuery>();
|
||||||
sendAuthQuery.GetAuthenticationMethod(sendId)
|
sendAuthQuery.GetAuthenticationMethod(sendId)
|
||||||
.Returns(new EmailOtp(new[] { email }));
|
.Returns(new EmailOtp(new[] { CryptographyHelper.HashAndEncode(email) }));
|
||||||
services.AddSingleton(sendAuthQuery);
|
services.AddSingleton(sendAuthQuery);
|
||||||
|
|
||||||
// Mock OTP token provider to fail generation
|
// Mock OTP token provider to fail generation
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Bit.Core.Tools.Models.Data;
|
|||||||
using Bit.Identity.IdentityServer.RequestValidators.SendAccess;
|
using Bit.Identity.IdentityServer.RequestValidators.SendAccess;
|
||||||
using Bit.Test.Common.AutoFixture;
|
using Bit.Test.Common.AutoFixture;
|
||||||
using Bit.Test.Common.AutoFixture.Attributes;
|
using Bit.Test.Common.AutoFixture.Attributes;
|
||||||
|
using Bit.Test.Common.Helpers;
|
||||||
using Duende.IdentityModel;
|
using Duende.IdentityModel;
|
||||||
using Duende.IdentityServer.Validation;
|
using Duende.IdentityServer.Validation;
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
@@ -105,7 +106,8 @@ public class SendEmailOtpRequestValidatorTests
|
|||||||
expectedUniqueId)
|
expectedUniqueId)
|
||||||
.Returns(generatedToken);
|
.Returns(generatedToken);
|
||||||
|
|
||||||
emailOtp = emailOtp with { Emails = [email] };
|
var emailHash = CryptographyHelper.HashAndEncode(email);
|
||||||
|
emailOtp = emailOtp with { EmailHashes = [emailHash] };
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await sutProvider.Sut.ValidateRequestAsync(context, emailOtp, sendId);
|
var result = await sutProvider.Sut.ValidateRequestAsync(context, emailOtp, sendId);
|
||||||
@@ -144,7 +146,8 @@ public class SendEmailOtpRequestValidatorTests
|
|||||||
Request = tokenRequest
|
Request = tokenRequest
|
||||||
};
|
};
|
||||||
|
|
||||||
emailOtp = emailOtp with { Emails = [email] };
|
var emailHash = CryptographyHelper.HashAndEncode(email);
|
||||||
|
emailOtp = emailOtp with { EmailHashes = [emailHash] };
|
||||||
|
|
||||||
sutProvider.GetDependency<IOtpTokenProvider<DefaultOtpTokenProviderOptions>>()
|
sutProvider.GetDependency<IOtpTokenProvider<DefaultOtpTokenProviderOptions>>()
|
||||||
.GenerateTokenAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>())
|
.GenerateTokenAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>())
|
||||||
@@ -179,7 +182,8 @@ public class SendEmailOtpRequestValidatorTests
|
|||||||
Request = tokenRequest
|
Request = tokenRequest
|
||||||
};
|
};
|
||||||
|
|
||||||
emailOtp = emailOtp with { Emails = [email] };
|
var emailHash = CryptographyHelper.HashAndEncode(email);
|
||||||
|
emailOtp = emailOtp with { EmailHashes = [emailHash] };
|
||||||
|
|
||||||
var expectedUniqueId = string.Format(SendAccessConstants.OtpToken.TokenUniqueIdentifier, sendId, email);
|
var expectedUniqueId = string.Format(SendAccessConstants.OtpToken.TokenUniqueIdentifier, sendId, email);
|
||||||
|
|
||||||
@@ -231,7 +235,8 @@ public class SendEmailOtpRequestValidatorTests
|
|||||||
Request = tokenRequest
|
Request = tokenRequest
|
||||||
};
|
};
|
||||||
|
|
||||||
emailOtp = emailOtp with { Emails = [email] };
|
var emailHash = CryptographyHelper.HashAndEncode(email);
|
||||||
|
emailOtp = emailOtp with { EmailHashes = [emailHash] };
|
||||||
|
|
||||||
var expectedUniqueId = string.Format(SendAccessConstants.OtpToken.TokenUniqueIdentifier, sendId, email);
|
var expectedUniqueId = string.Format(SendAccessConstants.OtpToken.TokenUniqueIdentifier, sendId, email);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user