mirror of
https://github.com/bitwarden/server
synced 2025-12-10 13:23:27 +00:00
* feat: add static enumeration helper class * test: add enumeration helper class unit tests * feat: implement NeverAuthenticateValidator * test: unit and integration tests SendNeverAuthenticateValidator * test: use static class for common integration test setup for Send Access unit and integration tests * test: update tests to use static helper
236 lines
9.0 KiB
C#
236 lines
9.0 KiB
C#
using Bit.Core;
|
|
using Bit.Core.Enums;
|
|
using Bit.Core.Services;
|
|
using Bit.Core.Tools.Models.Data;
|
|
using Bit.Core.Tools.SendFeatures.Queries.Interfaces;
|
|
using Bit.Identity.IdentityServer.Enums;
|
|
using Bit.Identity.IdentityServer.RequestValidators.SendAccess;
|
|
using Bit.IntegrationTestCommon.Factories;
|
|
using Duende.IdentityModel;
|
|
using Duende.IdentityServer.Validation;
|
|
using NSubstitute;
|
|
using Xunit;
|
|
|
|
namespace Bit.Identity.IntegrationTest.RequestValidation.SendAccess;
|
|
|
|
// in order to test the default case for the authentication method, we need to create a custom one so we can ensure the
|
|
// method throws as expected.
|
|
internal record AnUnknownAuthenticationMethod : SendAuthenticationMethod { }
|
|
|
|
public class SendAccessGrantValidatorIntegrationTests(IdentityApplicationFactory _factory) : IClassFixture<IdentityApplicationFactory>
|
|
{
|
|
[Fact]
|
|
public async Task SendAccessGrant_FeatureFlagDisabled_ReturnsUnsupportedGrantType()
|
|
{
|
|
// Arrange
|
|
var sendId = Guid.NewGuid();
|
|
var client = _factory.WithWebHostBuilder(builder =>
|
|
{
|
|
builder.ConfigureServices(services =>
|
|
{
|
|
// Mock feature service to return false
|
|
var featureService = Substitute.For<IFeatureService>();
|
|
featureService.IsEnabled(FeatureFlagKeys.SendAccess).Returns(false);
|
|
services.AddSingleton(featureService);
|
|
});
|
|
}).CreateClient();
|
|
|
|
var requestBody = SendAccessTestUtilities.CreateTokenRequestBody(sendId);
|
|
|
|
// Act
|
|
var response = await client.PostAsync("/connect/token", requestBody);
|
|
|
|
// Assert
|
|
var content = await response.Content.ReadAsStringAsync();
|
|
Assert.Contains("unsupported_grant_type", content);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SendAccessGrant_ValidNotAuthenticatedSend_ReturnsAccessToken()
|
|
{
|
|
// Arrange
|
|
var sendId = Guid.NewGuid();
|
|
var client = _factory.WithWebHostBuilder(builder =>
|
|
{
|
|
builder.ConfigureServices(services =>
|
|
{
|
|
// Mock feature service to return true
|
|
var featureService = Substitute.For<IFeatureService>();
|
|
featureService.IsEnabled(FeatureFlagKeys.SendAccess).Returns(true);
|
|
services.AddSingleton(featureService);
|
|
|
|
// Mock send authentication query
|
|
var sendAuthQuery = Substitute.For<ISendAuthenticationQuery>();
|
|
sendAuthQuery.GetAuthenticationMethod(sendId).Returns(new NotAuthenticated());
|
|
services.AddSingleton(sendAuthQuery);
|
|
});
|
|
}).CreateClient();
|
|
|
|
var requestBody = SendAccessTestUtilities.CreateTokenRequestBody(sendId);
|
|
|
|
// Act
|
|
var response = await client.PostAsync("/connect/token", requestBody);
|
|
|
|
// Assert
|
|
Assert.True(response.IsSuccessStatusCode);
|
|
var content = await response.Content.ReadAsStringAsync();
|
|
Assert.Contains("access_token", content);
|
|
Assert.Contains("bearer", content.ToLower());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SendAccessGrant_MissingSendId_ReturnsInvalidRequest()
|
|
{
|
|
// Arrange
|
|
var client = _factory.WithWebHostBuilder(builder =>
|
|
{
|
|
builder.ConfigureServices(services =>
|
|
{
|
|
var featureService = Substitute.For<IFeatureService>();
|
|
featureService.IsEnabled(FeatureFlagKeys.SendAccess).Returns(true);
|
|
services.AddSingleton(featureService);
|
|
});
|
|
}).CreateClient();
|
|
|
|
var requestBody = new FormUrlEncodedContent([
|
|
new KeyValuePair<string, string>(OidcConstants.TokenRequest.GrantType, CustomGrantTypes.SendAccess),
|
|
new KeyValuePair<string, string>(OidcConstants.TokenRequest.ClientId, BitwardenClient.Send)
|
|
]);
|
|
|
|
// 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.SendId} is required", content);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SendAccessGrant_EmptySendGuid_ReturnsInvalidGrant()
|
|
{
|
|
// Arrange
|
|
var sendId = Guid.Empty;
|
|
var client = _factory.WithWebHostBuilder(builder =>
|
|
{
|
|
builder.ConfigureServices(services =>
|
|
{
|
|
var featureService = Substitute.For<IFeatureService>();
|
|
featureService.IsEnabled(FeatureFlagKeys.SendAccess).Returns(true);
|
|
services.AddSingleton(featureService);
|
|
});
|
|
}).CreateClient();
|
|
|
|
var requestBody = SendAccessTestUtilities.CreateTokenRequestBody(sendId);
|
|
|
|
// Act
|
|
var response = await client.PostAsync("/connect/token", requestBody);
|
|
|
|
// Assert
|
|
var content = await response.Content.ReadAsStringAsync();
|
|
Assert.Contains("invalid_grant", content);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SendAccessGrant_NeverAuthenticateSend_ReturnsInvalidGrant()
|
|
{
|
|
// Arrange
|
|
var sendId = Guid.NewGuid();
|
|
var client = _factory.WithWebHostBuilder(builder =>
|
|
{
|
|
builder.ConfigureServices(services =>
|
|
{
|
|
var featureService = Substitute.For<IFeatureService>();
|
|
featureService.IsEnabled(FeatureFlagKeys.SendAccess).Returns(true);
|
|
services.AddSingleton(featureService);
|
|
|
|
var sendAuthQuery = Substitute.For<ISendAuthenticationQuery>();
|
|
sendAuthQuery.GetAuthenticationMethod(sendId).Returns(new NeverAuthenticate());
|
|
services.AddSingleton(sendAuthQuery);
|
|
});
|
|
}).CreateClient();
|
|
|
|
var requestBody = SendAccessTestUtilities.CreateTokenRequestBody(sendId);
|
|
|
|
// Act
|
|
var response = await client.PostAsync("/connect/token", requestBody);
|
|
|
|
// Assert
|
|
var content = await response.Content.ReadAsStringAsync();
|
|
Assert.Contains("invalid_grant", content);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SendAccessGrant_UnknownAuthenticationMethod_ThrowsInvalidOperation()
|
|
{
|
|
// Arrange
|
|
var sendId = Guid.NewGuid();
|
|
var client = _factory.WithWebHostBuilder(builder =>
|
|
{
|
|
builder.ConfigureServices(services =>
|
|
{
|
|
var featureService = Substitute.For<IFeatureService>();
|
|
featureService.IsEnabled(FeatureFlagKeys.SendAccess).Returns(true);
|
|
services.AddSingleton(featureService);
|
|
|
|
var sendAuthQuery = Substitute.For<ISendAuthenticationQuery>();
|
|
sendAuthQuery.GetAuthenticationMethod(sendId).Returns(new AnUnknownAuthenticationMethod());
|
|
services.AddSingleton(sendAuthQuery);
|
|
});
|
|
}).CreateClient();
|
|
|
|
var requestBody = SendAccessTestUtilities.CreateTokenRequestBody(sendId);
|
|
|
|
// Act
|
|
var error = await client.PostAsync("/connect/token", requestBody);
|
|
|
|
// Assert
|
|
// We want to parse the response and ensure we get the correct error from the server
|
|
var content = await error.Content.ReadAsStringAsync();
|
|
Assert.Contains("invalid_grant", content);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SendAccessGrant_PasswordProtectedSend_CallsPasswordValidator()
|
|
{
|
|
// Arrange
|
|
var sendId = Guid.NewGuid();
|
|
var resourcePassword = new ResourcePassword("test-password-hash");
|
|
var client = _factory.WithWebHostBuilder(builder =>
|
|
{
|
|
builder.ConfigureServices(services =>
|
|
{
|
|
var featureService = Substitute.For<IFeatureService>();
|
|
featureService.IsEnabled(FeatureFlagKeys.SendAccess).Returns(true);
|
|
services.AddSingleton(featureService);
|
|
|
|
var sendAuthQuery = Substitute.For<ISendAuthenticationQuery>();
|
|
sendAuthQuery.GetAuthenticationMethod(sendId).Returns(resourcePassword);
|
|
services.AddSingleton(sendAuthQuery);
|
|
|
|
// Mock password validator to return success
|
|
var passwordValidator = Substitute.For<ISendAuthenticationMethodValidator<ResourcePassword>>();
|
|
passwordValidator.ValidateRequestAsync(
|
|
Arg.Any<ExtensionGrantValidationContext>(),
|
|
Arg.Any<ResourcePassword>(),
|
|
Arg.Any<Guid>())
|
|
.Returns(new GrantValidationResult(
|
|
subject: sendId.ToString(),
|
|
authenticationMethod: CustomGrantTypes.SendAccess));
|
|
services.AddSingleton(passwordValidator);
|
|
});
|
|
}).CreateClient();
|
|
|
|
var requestBody = SendAccessTestUtilities.CreateTokenRequestBody(sendId, "password123");
|
|
|
|
// Act
|
|
var response = await client.PostAsync("/connect/token", requestBody);
|
|
|
|
// Assert
|
|
Assert.True(response.IsSuccessStatusCode);
|
|
var content = await response.Content.ReadAsStringAsync();
|
|
Assert.Contains("access_token", content);
|
|
Assert.Contains("Bearer", content);
|
|
}
|
|
}
|