Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using SecurityService.Configuration;
using Shouldly;

namespace SecurityService.UnitTests.Configuration;

public sealed class DataProtectionPersistenceTests : IDisposable
{
private readonly string _temporaryDirectory = Path.Combine(
Path.GetTempPath(),
$"securityservice-keys-{Guid.NewGuid():N}");

[Fact]
public void PersistedKeys_CanUnprotectDataCreatedByAnotherProviderInstance()
{
Directory.CreateDirectory(this._temporaryDirectory);

var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string?>
{
["ServiceOptions:DataProtectionKeyDirectory"] = this._temporaryDirectory
})
.Build();

var firstServices = new ServiceCollection();
DataProtectionRegistration.Add(firstServices, configuration);
var firstProvider = firstServices.BuildServiceProvider().GetRequiredService<IDataProtectionProvider>();
var firstProtector = firstProvider.CreateProtector("SecurityService.Tests");
var protectedValue = firstProtector.Protect("sensitive-value");

var secondServices = new ServiceCollection();
DataProtectionRegistration.Add(secondServices, configuration);
var secondProvider = secondServices.BuildServiceProvider().GetRequiredService<IDataProtectionProvider>();
var secondProtector = secondProvider.CreateProtector("SecurityService.Tests");

secondProtector.Unprotect(protectedValue).ShouldBe("sensitive-value");
}

public void Dispose()
{
if (Directory.Exists(this._temporaryDirectory))
{
Directory.Delete(this._temporaryDirectory, recursive: true);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,89 @@ public async Task CreateClient_WhenGrantTypeIsUnsupported_ReturnsInvalid()
result.Message.ShouldContain("Unsupported grant types");
}

[Fact]
public async Task CreateClient_WithLegacyGrantType_IsRejectedUnlessClientIsConfigured()
{
using var provider = TestServiceProviderFactory.Create(
nameof(this.CreateClient_WithLegacyGrantType_IsRejectedUnlessClientIsConfigured),
configureOptions: options =>
{
options.OAuth.EnableLegacyGrantTypes = true;
options.OAuth.LegacyGrantTypeClients[OpenIddictConstants.GrantTypes.Password] = ["legacy-client"];
});
var mediator = provider.GetRequiredService<IMediator>();

var rejected = await mediator.Send(new SecurityServiceCommands.CreateClientCommand(
"other-client",
"secret",
"Other Client",
null,
[],
[OpenIddictConstants.GrantTypes.Password],
null,
[],
[],
false,
false));

rejected.IsFailed.ShouldBeTrue();
rejected.Status.ShouldBe(ResultStatus.Invalid);

var accepted = await mediator.Send(new SecurityServiceCommands.CreateClientCommand(
"legacy-client",
"secret",
"Legacy Client",
null,
[],
[OpenIddictConstants.GrantTypes.Password],
null,
[],
[],
false,
false));

accepted.IsSuccess.ShouldBeTrue();
}

[Fact]
public async Task CreateClient_WithPasswordGrant_PersistsOnlyPasswordGrantPermission()
{
using var provider = TestServiceProviderFactory.Create(
nameof(this.CreateClient_WithPasswordGrant_PersistsOnlyPasswordGrantPermission),
configureOptions: options =>
{
options.OAuth.EnableLegacyGrantTypes = true;
options.OAuth.LegacyGrantTypeClients[OpenIddictConstants.GrantTypes.Password] = ["legacy-client"];
});
var mediator = provider.GetRequiredService<IMediator>();
var applicationManager = provider.GetRequiredService<IOpenIddictApplicationManager>();

var result = await mediator.Send(new SecurityServiceCommands.CreateClientCommand(
"legacy-client",
"secret",
"Legacy Client",
null,
[],
[OpenIddictConstants.GrantTypes.Password],
null,
[],
[],
false,
false));

result.IsSuccess.ShouldBeTrue();

object application = (await applicationManager.FindByClientIdAsync("legacy-client"))!;
var permissions = (await applicationManager.GetPermissionsAsync(application)).ToHashSet(StringComparer.Ordinal);
var expectedPermissions = new HashSet<string>(StringComparer.Ordinal)
{
OpenIddictConstants.Permissions.Endpoints.Token,
OpenIddictConstants.Permissions.GrantTypes.Password
};

permissions.SetEquals(expectedPermissions).ShouldBeTrue();
}

[Fact]
public async Task CreateClient_WithInvalidRedirectUri_ReturnsInvalidWithoutPersisting()
{
Expand Down
18 changes: 18 additions & 0 deletions SecurityService/Configuration/DataProtectionRegistration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.DataProtection;

namespace SecurityService.Configuration;

public static class DataProtectionRegistration
{
public static void Add(IServiceCollection services, IConfiguration configuration)
{
var dataProtectionKeyDirectory = configuration["ServiceOptions:DataProtectionKeyDirectory"];
var dataProtectionBuilder = services.AddDataProtection();
if (string.IsNullOrWhiteSpace(dataProtectionKeyDirectory) == false)
{
dataProtectionBuilder.PersistKeysToFileSystem(new DirectoryInfo(dataProtectionKeyDirectory));
}
}
}
7 changes: 1 addition & 6 deletions SecurityService/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -207,12 +207,7 @@
});

builder.Services.AddHttpContextAccessor();
var dataProtectionKeyDirectory = builder.Configuration["ServiceOptions:DataProtectionKeyDirectory"];
var dataProtectionBuilder = builder.Services.AddDataProtection();
if (string.IsNullOrWhiteSpace(dataProtectionKeyDirectory) == false)
{
dataProtectionBuilder.PersistKeysToFileSystem(new DirectoryInfo(dataProtectionKeyDirectory));
}
DataProtectionRegistration.Add(builder.Services, builder.Configuration);
builder.Services.AddSingleton(serviceProvider => new ConsentTransactionProtector(
serviceProvider.GetRequiredService<IDataProtectionProvider>().CreateProtector("SecurityService.Consent")));
builder.Services.AddScoped<TenantContext>();
Expand Down
Loading