Browse Source
* feat: add Passwordvalidation * fix: update strings to constants * fix: add customResponse for rust consumption * test: add tests for SendPasswordValidator. fix: update tests for SendAccessGrantValidator * feat: update send access constants.pull/6243/head
10 changed files with 647 additions and 76 deletions
@ -1,3 +1,4 @@
@@ -1,3 +1,4 @@
|
||||
using System.Runtime.CompilerServices; |
||||
|
||||
[assembly: InternalsVisibleTo("Core.Test")] |
||||
[assembly: InternalsVisibleTo("Identity.IntegrationTest")] |
||||
|
||||
@ -1,11 +0,0 @@
@@ -1,11 +0,0 @@
|
||||
namespace Bit.Identity.IdentityServer.RequestValidators.SendAccess.Enums; |
||||
|
||||
/// <summary> |
||||
/// These control the results of the SendGrantValidator. <see cref="SendGrantValidator"/> |
||||
/// </summary> |
||||
internal enum SendGrantValidatorResultTypes |
||||
{ |
||||
ValidSendGuid, |
||||
MissingSendId, |
||||
InvalidSendId |
||||
} |
||||
@ -1,9 +0,0 @@
@@ -1,9 +0,0 @@
|
||||
namespace Bit.Identity.IdentityServer.RequestValidators.SendAccess.Enums; |
||||
|
||||
/// <summary> |
||||
/// These control the results of the SendPasswordValidator. <see cref="SendPasswordRequestValidator"/> |
||||
/// </summary> |
||||
internal enum SendPasswordValidatorResultTypes |
||||
{ |
||||
RequestPasswordDoesNotMatch |
||||
} |
||||
@ -0,0 +1,73 @@
@@ -0,0 +1,73 @@
|
||||
using Duende.IdentityServer.Validation; |
||||
|
||||
namespace Bit.Identity.IdentityServer.RequestValidators.SendAccess; |
||||
|
||||
/// <summary> |
||||
/// String constants for the Send Access user feature |
||||
/// </summary> |
||||
public static class SendAccessConstants |
||||
{ |
||||
/// <summary> |
||||
/// A catch all error type for send access related errors. Used mainly in the <see cref="GrantValidationResult.CustomResponse"/> |
||||
/// </summary> |
||||
public const string SendAccessError = "send_access_error_type"; |
||||
public static class TokenRequest |
||||
{ |
||||
/// <summary> |
||||
/// used to fetch Send from database. |
||||
/// </summary> |
||||
public const string SendId = "send_id"; |
||||
/// <summary> |
||||
/// used to validate Send protected passwords |
||||
/// </summary> |
||||
public const string ClientB64HashedPassword = "password_hash_b64"; |
||||
/// <summary> |
||||
/// email used to see if email is associated with the Send |
||||
/// </summary> |
||||
public const string Email = "email"; |
||||
/// <summary> |
||||
/// Otp code sent to email associated with the Send |
||||
/// </summary> |
||||
public const string Otp = "otp"; |
||||
} |
||||
|
||||
public static class GrantValidatorResults |
||||
{ |
||||
/// <summary> |
||||
/// The sendId is valid and the request is well formed. |
||||
/// </summary> |
||||
public const string ValidSendGuid = "valid_send_guid"; |
||||
/// <summary> |
||||
/// The sendId is missing from the request. |
||||
/// </summary> |
||||
public const string MissingSendId = "send_id_required"; |
||||
/// <summary> |
||||
/// The sendId is invalid, does not match a known send. |
||||
/// </summary> |
||||
public const string InvalidSendId = "send_id_invalid"; |
||||
} |
||||
|
||||
public static class PasswordValidatorResults |
||||
{ |
||||
/// <summary> |
||||
/// The passwordHashB64 does not match the send's password hash. |
||||
/// </summary> |
||||
public const string RequestPasswordDoesNotMatch = "password_hash_b64_invalid"; |
||||
/// <summary> |
||||
/// The passwordHashB64 is missing from the request. |
||||
/// </summary> |
||||
public const string RequestPasswordIsRequired = "password_hash_b64_required"; |
||||
} |
||||
|
||||
public static class EmailOtpValidatorResults |
||||
{ |
||||
/// <summary> |
||||
/// Represents the error code indicating that an email address is required. |
||||
/// </summary> |
||||
public const string EmailRequired = "email_required"; |
||||
/// <summary> |
||||
/// Represents the status indicating that both email and OTP are required, and the OTP has been sent. |
||||
/// </summary> |
||||
public const string EmailOtpSent = "email_and_otp_required_otp_sent"; |
||||
} |
||||
} |
||||
@ -0,0 +1,209 @@
@@ -0,0 +1,209 @@
|
||||
using Bit.Core.Enums; |
||||
using Bit.Core.IdentityServer; |
||||
using Bit.Core.KeyManagement.Sends; |
||||
using Bit.Core.Services; |
||||
using Bit.Core.Tools.Models.Data; |
||||
using Bit.Core.Tools.SendFeatures.Queries.Interfaces; |
||||
using Bit.Core.Utilities; |
||||
using Bit.Identity.IdentityServer.Enums; |
||||
using Bit.Identity.IdentityServer.RequestValidators.SendAccess; |
||||
using Bit.IntegrationTestCommon.Factories; |
||||
using Duende.IdentityModel; |
||||
using NSubstitute; |
||||
using Xunit; |
||||
|
||||
namespace Bit.Identity.IntegrationTest.RequestValidation; |
||||
|
||||
public class SendPasswordRequestValidatorIntegrationTests : IClassFixture<IdentityApplicationFactory> |
||||
{ |
||||
private readonly IdentityApplicationFactory _factory; |
||||
|
||||
public SendPasswordRequestValidatorIntegrationTests(IdentityApplicationFactory factory) |
||||
{ |
||||
_factory = factory; |
||||
} |
||||
|
||||
[Fact] |
||||
public async Task SendAccess_PasswordProtectedSend_ValidPassword_ReturnsAccessToken() |
||||
{ |
||||
// Arrange |
||||
var sendId = Guid.NewGuid(); |
||||
var passwordHash = "stored-password-hash"; |
||||
var clientPasswordHash = "client-password-hash"; |
||||
|
||||
var client = _factory.WithWebHostBuilder(builder => |
||||
{ |
||||
builder.ConfigureServices(services => |
||||
{ |
||||
// Enable feature flag |
||||
var featureService = Substitute.For<IFeatureService>(); |
||||
featureService.IsEnabled(Arg.Any<string>()).Returns(true); |
||||
services.AddSingleton(featureService); |
||||
|
||||
// Mock send authentication query |
||||
var sendAuthQuery = Substitute.For<ISendAuthenticationQuery>(); |
||||
sendAuthQuery.GetAuthenticationMethod(sendId) |
||||
.Returns(new ResourcePassword(passwordHash)); |
||||
services.AddSingleton(sendAuthQuery); |
||||
|
||||
// Mock password hasher to return true for matching passwords |
||||
var passwordHasher = Substitute.For<ISendPasswordHasher>(); |
||||
passwordHasher.PasswordHashMatches(passwordHash, clientPasswordHash) |
||||
.Returns(true); |
||||
services.AddSingleton(passwordHasher); |
||||
}); |
||||
}).CreateClient(); |
||||
|
||||
var requestBody = CreateTokenRequestBody(sendId, clientPasswordHash); |
||||
|
||||
// Act |
||||
var response = await client.PostAsync("/connect/token", requestBody); |
||||
|
||||
// Assert |
||||
Assert.True(response.IsSuccessStatusCode); |
||||
var content = await response.Content.ReadAsStringAsync(); |
||||
Assert.Contains(OidcConstants.TokenResponse.AccessToken, content); |
||||
Assert.Contains("bearer", content.ToLower()); |
||||
} |
||||
|
||||
[Fact] |
||||
public async Task SendAccess_PasswordProtectedSend_InvalidPassword_ReturnsInvalidGrant() |
||||
{ |
||||
// Arrange |
||||
var sendId = Guid.NewGuid(); |
||||
var passwordHash = "stored-password-hash"; |
||||
var wrongClientPasswordHash = "wrong-client-password-hash"; |
||||
|
||||
var client = _factory.WithWebHostBuilder(builder => |
||||
{ |
||||
builder.ConfigureServices(services => |
||||
{ |
||||
var featureService = Substitute.For<IFeatureService>(); |
||||
featureService.IsEnabled(Arg.Any<string>()).Returns(true); |
||||
services.AddSingleton(featureService); |
||||
|
||||
var sendAuthQuery = Substitute.For<ISendAuthenticationQuery>(); |
||||
sendAuthQuery.GetAuthenticationMethod(sendId) |
||||
.Returns(new ResourcePassword(passwordHash)); |
||||
services.AddSingleton(sendAuthQuery); |
||||
|
||||
// Mock password hasher to return false for wrong passwords |
||||
var passwordHasher = Substitute.For<ISendPasswordHasher>(); |
||||
passwordHasher.PasswordHashMatches(passwordHash, wrongClientPasswordHash) |
||||
.Returns(false); |
||||
services.AddSingleton(passwordHasher); |
||||
}); |
||||
}).CreateClient(); |
||||
|
||||
var requestBody = CreateTokenRequestBody(sendId, wrongClientPasswordHash); |
||||
|
||||
// Act |
||||
var response = await client.PostAsync("/connect/token", requestBody); |
||||
|
||||
// Assert |
||||
var content = await response.Content.ReadAsStringAsync(); |
||||
Assert.Contains(OidcConstants.TokenErrors.InvalidGrant, content); |
||||
Assert.Contains($"{SendAccessConstants.TokenRequest.ClientB64HashedPassword} is invalid", content); |
||||
} |
||||
|
||||
[Fact] |
||||
public async Task SendAccess_PasswordProtectedSend_MissingPassword_ReturnsInvalidRequest() |
||||
{ |
||||
// Arrange |
||||
var sendId = Guid.NewGuid(); |
||||
var passwordHash = "stored-password-hash"; |
||||
|
||||
var client = _factory.WithWebHostBuilder(builder => |
||||
{ |
||||
builder.ConfigureServices(services => |
||||
{ |
||||
var featureService = Substitute.For<IFeatureService>(); |
||||
featureService.IsEnabled(Arg.Any<string>()).Returns(true); |
||||
services.AddSingleton(featureService); |
||||
|
||||
var sendAuthQuery = Substitute.For<ISendAuthenticationQuery>(); |
||||
sendAuthQuery.GetAuthenticationMethod(sendId) |
||||
.Returns(new ResourcePassword(passwordHash)); |
||||
services.AddSingleton(sendAuthQuery); |
||||
|
||||
var passwordHasher = Substitute.For<ISendPasswordHasher>(); |
||||
services.AddSingleton(passwordHasher); |
||||
}); |
||||
}).CreateClient(); |
||||
|
||||
var requestBody = CreateTokenRequestBody(sendId); // No password |
||||
|
||||
// Act |
||||
var response = await client.PostAsync("/connect/token", requestBody); |
||||
|
||||
// Assert |
||||
var content = await response.Content.ReadAsStringAsync(); |
||||
Assert.Contains(OidcConstants.TokenErrors.InvalidRequest, content); |
||||
Assert.Contains($"{SendAccessConstants.TokenRequest.ClientB64HashedPassword} is required", content); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// When the password has is empty or whitespace it doesn't get passed to the server when the request is made. |
||||
/// This leads to an invalid request error since the absence of the password hash is considered a malformed request. |
||||
/// In the case that the passwordB64Hash _is_ empty or whitespace it would be an invalid grant since the request |
||||
/// has the correct shape. |
||||
/// </summary> |
||||
[Fact] |
||||
public async Task SendAccess_PasswordProtectedSend_EmptyPassword_ReturnsInvalidRequest() |
||||
{ |
||||
// Arrange |
||||
var sendId = Guid.NewGuid(); |
||||
var passwordHash = "stored-password-hash"; |
||||
|
||||
var client = _factory.WithWebHostBuilder(builder => |
||||
{ |
||||
builder.ConfigureServices(services => |
||||
{ |
||||
var featureService = Substitute.For<IFeatureService>(); |
||||
featureService.IsEnabled(Arg.Any<string>()).Returns(true); |
||||
services.AddSingleton(featureService); |
||||
|
||||
var sendAuthQuery = Substitute.For<ISendAuthenticationQuery>(); |
||||
sendAuthQuery.GetAuthenticationMethod(sendId) |
||||
.Returns(new ResourcePassword(passwordHash)); |
||||
services.AddSingleton(sendAuthQuery); |
||||
|
||||
// Mock password hasher to return false for empty passwords |
||||
var passwordHasher = Substitute.For<ISendPasswordHasher>(); |
||||
passwordHasher.PasswordHashMatches(passwordHash, string.Empty) |
||||
.Returns(false); |
||||
services.AddSingleton(passwordHasher); |
||||
}); |
||||
}).CreateClient(); |
||||
|
||||
var requestBody = CreateTokenRequestBody(sendId, string.Empty); |
||||
|
||||
// Act |
||||
var response = await client.PostAsync("/connect/token", requestBody); |
||||
|
||||
// Assert |
||||
var content = await response.Content.ReadAsStringAsync(); |
||||
Assert.Contains(OidcConstants.TokenErrors.InvalidRequest, content); |
||||
Assert.Contains($"{SendAccessConstants.TokenRequest.ClientB64HashedPassword} is required", content); |
||||
} |
||||
|
||||
private static FormUrlEncodedContent CreateTokenRequestBody(Guid sendId, string passwordHash = null) |
||||
{ |
||||
var sendIdBase64 = CoreHelpers.Base64UrlEncode(sendId.ToByteArray()); |
||||
var parameters = new List<KeyValuePair<string, string>> |
||||
{ |
||||
new(OidcConstants.TokenRequest.GrantType, CustomGrantTypes.SendAccess), |
||||
new(OidcConstants.TokenRequest.ClientId, BitwardenClient.Send), |
||||
new(SendAccessConstants.TokenRequest.SendId, sendIdBase64), |
||||
new(OidcConstants.TokenRequest.Scope, ApiScopes.ApiSendAccess), |
||||
new("deviceType", "10") |
||||
}; |
||||
|
||||
if (passwordHash != null) |
||||
{ |
||||
parameters.Add(new KeyValuePair<string, string>(SendAccessConstants.TokenRequest.ClientB64HashedPassword, passwordHash)); |
||||
} |
||||
|
||||
return new FormUrlEncodedContent(parameters); |
||||
} |
||||
} |
||||
@ -0,0 +1,297 @@
@@ -0,0 +1,297 @@
|
||||
using System.Collections.Specialized; |
||||
using Bit.Core.Auth.UserFeatures.SendAccess; |
||||
using Bit.Core.Enums; |
||||
using Bit.Core.Identity; |
||||
using Bit.Core.IdentityServer; |
||||
using Bit.Core.KeyManagement.Sends; |
||||
using Bit.Core.Tools.Models.Data; |
||||
using Bit.Core.Utilities; |
||||
using Bit.Identity.IdentityServer.Enums; |
||||
using Bit.Identity.IdentityServer.RequestValidators.SendAccess; |
||||
using Bit.Test.Common.AutoFixture; |
||||
using Bit.Test.Common.AutoFixture.Attributes; |
||||
using Duende.IdentityModel; |
||||
using Duende.IdentityServer.Validation; |
||||
using NSubstitute; |
||||
using Xunit; |
||||
|
||||
namespace Bit.Identity.Test.IdentityServer; |
||||
|
||||
[SutProviderCustomize] |
||||
public class SendPasswordRequestValidatorTests |
||||
{ |
||||
[Theory, BitAutoData] |
||||
public void ValidateSendPassword_MissingPasswordHash_ReturnsInvalidRequest( |
||||
SutProvider<SendPasswordRequestValidator> sutProvider, |
||||
[AutoFixture.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest, |
||||
ResourcePassword resourcePassword, |
||||
Guid sendId) |
||||
{ |
||||
// Arrange |
||||
tokenRequest.Raw = CreateValidatedTokenRequest(sendId); |
||||
|
||||
var context = new ExtensionGrantValidationContext |
||||
{ |
||||
Request = tokenRequest |
||||
}; |
||||
|
||||
// Act |
||||
var result = sutProvider.Sut.ValidateSendPassword(context, resourcePassword, sendId); |
||||
|
||||
// Assert |
||||
Assert.True(result.IsError); |
||||
Assert.Equal(OidcConstants.TokenErrors.InvalidRequest, result.Error); |
||||
Assert.Equal($"{SendAccessConstants.TokenRequest.ClientB64HashedPassword} is required.", result.ErrorDescription); |
||||
|
||||
// Verify password hasher was not called |
||||
sutProvider.GetDependency<ISendPasswordHasher>() |
||||
.DidNotReceive() |
||||
.PasswordHashMatches(Arg.Any<string>(), Arg.Any<string>()); |
||||
} |
||||
|
||||
[Theory, BitAutoData] |
||||
public void ValidateSendPassword_PasswordHashMismatch_ReturnsInvalidGrant( |
||||
SutProvider<SendPasswordRequestValidator> sutProvider, |
||||
[AutoFixture.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest, |
||||
ResourcePassword resourcePassword, |
||||
Guid sendId, |
||||
string clientPasswordHash) |
||||
{ |
||||
// Arrange |
||||
tokenRequest.Raw = CreateValidatedTokenRequest(sendId, clientPasswordHash); |
||||
|
||||
var context = new ExtensionGrantValidationContext |
||||
{ |
||||
Request = tokenRequest |
||||
}; |
||||
|
||||
sutProvider.GetDependency<ISendPasswordHasher>() |
||||
.PasswordHashMatches(resourcePassword.Hash, clientPasswordHash) |
||||
.Returns(false); |
||||
|
||||
// Act |
||||
var result = sutProvider.Sut.ValidateSendPassword(context, resourcePassword, sendId); |
||||
|
||||
// Assert |
||||
Assert.True(result.IsError); |
||||
Assert.Equal(OidcConstants.TokenErrors.InvalidGrant, result.Error); |
||||
Assert.Equal($"{SendAccessConstants.TokenRequest.ClientB64HashedPassword} is invalid.", result.ErrorDescription); |
||||
|
||||
// Verify password hasher was called with correct parameters |
||||
sutProvider.GetDependency<ISendPasswordHasher>() |
||||
.Received(1) |
||||
.PasswordHashMatches(resourcePassword.Hash, clientPasswordHash); |
||||
} |
||||
|
||||
[Theory, BitAutoData] |
||||
public void ValidateSendPassword_PasswordHashMatches_ReturnsSuccess( |
||||
SutProvider<SendPasswordRequestValidator> sutProvider, |
||||
[AutoFixture.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest, |
||||
ResourcePassword resourcePassword, |
||||
Guid sendId, |
||||
string clientPasswordHash) |
||||
{ |
||||
// Arrange |
||||
tokenRequest.Raw = CreateValidatedTokenRequest(sendId, clientPasswordHash); |
||||
|
||||
var context = new ExtensionGrantValidationContext |
||||
{ |
||||
Request = tokenRequest |
||||
}; |
||||
|
||||
sutProvider.GetDependency<ISendPasswordHasher>() |
||||
.PasswordHashMatches(resourcePassword.Hash, clientPasswordHash) |
||||
.Returns(true); |
||||
|
||||
// Act |
||||
var result = sutProvider.Sut.ValidateSendPassword(context, resourcePassword, sendId); |
||||
|
||||
// Assert |
||||
Assert.False(result.IsError); |
||||
|
||||
var sub = result.Subject; |
||||
Assert.Equal(sendId, sub.GetSendId()); |
||||
|
||||
// Verify claims |
||||
Assert.Contains(sub.Claims, c => c.Type == Claims.SendId && c.Value == sendId.ToString()); |
||||
Assert.Contains(sub.Claims, c => c.Type == Claims.Type && c.Value == IdentityClientType.Send.ToString()); |
||||
|
||||
// Verify password hasher was called |
||||
sutProvider.GetDependency<ISendPasswordHasher>() |
||||
.Received(1) |
||||
.PasswordHashMatches(resourcePassword.Hash, clientPasswordHash); |
||||
} |
||||
|
||||
[Theory, BitAutoData] |
||||
public void ValidateSendPassword_EmptyPasswordHash_CallsPasswordHasher( |
||||
SutProvider<SendPasswordRequestValidator> sutProvider, |
||||
[AutoFixture.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest, |
||||
ResourcePassword resourcePassword, |
||||
Guid sendId) |
||||
{ |
||||
// Arrange |
||||
tokenRequest.Raw = CreateValidatedTokenRequest(sendId, string.Empty); |
||||
|
||||
var context = new ExtensionGrantValidationContext |
||||
{ |
||||
Request = tokenRequest |
||||
}; |
||||
|
||||
sutProvider.GetDependency<ISendPasswordHasher>() |
||||
.PasswordHashMatches(resourcePassword.Hash, string.Empty) |
||||
.Returns(false); |
||||
|
||||
// Act |
||||
var result = sutProvider.Sut.ValidateSendPassword(context, resourcePassword, sendId); |
||||
|
||||
// Assert |
||||
Assert.True(result.IsError); |
||||
Assert.Equal(OidcConstants.TokenErrors.InvalidGrant, result.Error); |
||||
|
||||
// Verify password hasher was called with empty string |
||||
sutProvider.GetDependency<ISendPasswordHasher>() |
||||
.Received(1) |
||||
.PasswordHashMatches(resourcePassword.Hash, string.Empty); |
||||
} |
||||
|
||||
[Theory, BitAutoData] |
||||
public void ValidateSendPassword_WhitespacePasswordHash_CallsPasswordHasher( |
||||
SutProvider<SendPasswordRequestValidator> sutProvider, |
||||
[AutoFixture.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest, |
||||
ResourcePassword resourcePassword, |
||||
Guid sendId) |
||||
{ |
||||
// Arrange |
||||
var whitespacePassword = " "; |
||||
tokenRequest.Raw = CreateValidatedTokenRequest(sendId, whitespacePassword); |
||||
|
||||
var context = new ExtensionGrantValidationContext |
||||
{ |
||||
Request = tokenRequest |
||||
}; |
||||
|
||||
sutProvider.GetDependency<ISendPasswordHasher>() |
||||
.PasswordHashMatches(resourcePassword.Hash, whitespacePassword) |
||||
.Returns(false); |
||||
|
||||
// Act |
||||
var result = sutProvider.Sut.ValidateSendPassword(context, resourcePassword, sendId); |
||||
|
||||
// Assert |
||||
Assert.True(result.IsError); |
||||
|
||||
// Verify password hasher was called with whitespace string |
||||
sutProvider.GetDependency<ISendPasswordHasher>() |
||||
.Received(1) |
||||
.PasswordHashMatches(resourcePassword.Hash, whitespacePassword); |
||||
} |
||||
|
||||
[Theory, BitAutoData] |
||||
public void ValidateSendPassword_MultiplePasswordHashParameters_ReturnsInvalidGrant( |
||||
SutProvider<SendPasswordRequestValidator> sutProvider, |
||||
[AutoFixture.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest, |
||||
ResourcePassword resourcePassword, |
||||
Guid sendId) |
||||
{ |
||||
// Arrange |
||||
var firstPassword = "first-password"; |
||||
var secondPassword = "second-password"; |
||||
tokenRequest.Raw = CreateValidatedTokenRequest(sendId, firstPassword, secondPassword); |
||||
|
||||
var context = new ExtensionGrantValidationContext |
||||
{ |
||||
Request = tokenRequest |
||||
}; |
||||
|
||||
sutProvider.GetDependency<ISendPasswordHasher>() |
||||
.PasswordHashMatches(resourcePassword.Hash, firstPassword) |
||||
.Returns(true); |
||||
|
||||
// Act |
||||
var result = sutProvider.Sut.ValidateSendPassword(context, resourcePassword, sendId); |
||||
|
||||
// Assert |
||||
Assert.True(result.IsError); |
||||
Assert.Equal(OidcConstants.TokenErrors.InvalidGrant, result.Error); |
||||
|
||||
// Verify password hasher was called with first value |
||||
sutProvider.GetDependency<ISendPasswordHasher>() |
||||
.Received(1) |
||||
.PasswordHashMatches(resourcePassword.Hash, $"{firstPassword},{secondPassword}"); |
||||
} |
||||
|
||||
[Theory, BitAutoData] |
||||
public void ValidateSendPassword_SuccessResult_ContainsCorrectClaims( |
||||
SutProvider<SendPasswordRequestValidator> sutProvider, |
||||
[AutoFixture.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest, |
||||
ResourcePassword resourcePassword, |
||||
Guid sendId, |
||||
string clientPasswordHash) |
||||
{ |
||||
// Arrange |
||||
tokenRequest.Raw = CreateValidatedTokenRequest(sendId, clientPasswordHash); |
||||
|
||||
var context = new ExtensionGrantValidationContext |
||||
{ |
||||
Request = tokenRequest |
||||
}; |
||||
|
||||
sutProvider.GetDependency<ISendPasswordHasher>() |
||||
.PasswordHashMatches(Arg.Any<string>(), Arg.Any<string>()) |
||||
.Returns(true); |
||||
|
||||
// Act |
||||
var result = sutProvider.Sut.ValidateSendPassword(context, resourcePassword, sendId); |
||||
|
||||
// Assert |
||||
Assert.False(result.IsError); |
||||
var sub = result.Subject; |
||||
|
||||
var sendIdClaim = sub.Claims.FirstOrDefault(c => c.Type == Claims.SendId); |
||||
Assert.NotNull(sendIdClaim); |
||||
Assert.Equal(sendId.ToString(), sendIdClaim.Value); |
||||
|
||||
var typeClaim = sub.Claims.FirstOrDefault(c => c.Type == Claims.Type); |
||||
Assert.NotNull(typeClaim); |
||||
Assert.Equal(IdentityClientType.Send.ToString(), typeClaim.Value); |
||||
} |
||||
|
||||
[Fact] |
||||
public void Constructor_WithValidParameters_CreatesInstance() |
||||
{ |
||||
// Arrange |
||||
var sendPasswordHasher = Substitute.For<ISendPasswordHasher>(); |
||||
|
||||
// Act |
||||
var validator = new SendPasswordRequestValidator(sendPasswordHasher); |
||||
|
||||
// Assert |
||||
Assert.NotNull(validator); |
||||
} |
||||
|
||||
private static NameValueCollection CreateValidatedTokenRequest( |
||||
Guid sendId, |
||||
params string[] passwordHash) |
||||
{ |
||||
var sendIdBase64 = CoreHelpers.Base64UrlEncode(sendId.ToByteArray()); |
||||
|
||||
var rawRequestParameters = new NameValueCollection |
||||
{ |
||||
{ OidcConstants.TokenRequest.GrantType, CustomGrantTypes.SendAccess }, |
||||
{ OidcConstants.TokenRequest.ClientId, BitwardenClient.Send }, |
||||
{ OidcConstants.TokenRequest.Scope, ApiScopes.ApiSendAccess }, |
||||
{ "device_type", ((int)DeviceType.FirefoxBrowser).ToString() }, |
||||
{ SendAccessConstants.TokenRequest.SendId, sendIdBase64 } |
||||
}; |
||||
|
||||
if (passwordHash != null && passwordHash.Length > 0) |
||||
{ |
||||
foreach (var hash in passwordHash) |
||||
{ |
||||
rawRequestParameters.Add(SendAccessConstants.TokenRequest.ClientB64HashedPassword, hash); |
||||
} |
||||
} |
||||
|
||||
return rawRequestParameters; |
||||
} |
||||
} |
||||
Loading…
Reference in new issue