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
Binary file added .DS_Store
Binary file not shown.
Binary file added .github/.DS_Store
Binary file not shown.
Binary file added BackgroundProcessing/.DS_Store
Binary file not shown.
Binary file added Configuration/.DS_Store
Binary file not shown.
Binary file added Configuration/AzureBlobJson/.DS_Store
Binary file not shown.
Binary file added Configuration/AzureKeyVault/.DS_Store
Binary file not shown.
4 changes: 2 additions & 2 deletions Configuration/AzureKeyVault/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ This is often used to store secrets for multiple Environments and\or Systems in
"VaultUri": "https://MyVault.vault.azure.net", // either of the 'VaultXXX' properties can be used.
"TenantId": "XXXX", // required
"ClientId": "YYYY", // required
"Secret": "ZZZZ~~~~", // required
"Prefix": "Treasury-QA-" // optional~~~~
"Secret": "ZZZZ", // required
"Prefix": "Treasury-QA-" // optional
}
}
```
Expand Down
Binary file added Configuration/Tests/.DS_Store
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
using Azure.Core;
using Azure.Extensions.AspNetCore.Configuration.Secrets;
using Microsoft.Extensions.Configuration;
using Odin.Configuration;

namespace Tests.Odin.Configuration.AzureKeyVault;

public class ConfigurationBuilderExtensionsTests
{
[Fact]
public void AddOdinPrefixedAzureKeyVault_with_disabled_section_returns_the_same_builder_without_adding_a_source()
{
IConfigurationRoot config = CreateConfiguration(new Dictionary<string, string?>
{
["AzureKeyVault:Enabled"] = "false"
});
IConfigurationBuilder builder = new ConfigurationBuilder();

IConfigurationBuilder result = builder.AddOdinPrefixedAzureKeyVault(config.GetSection("AzureKeyVault"));

Assert.Same(builder, result);
Assert.Empty(builder.Sources);
}

[Fact]
public void AddOdinPrefixedAzureKeyVault_with_missing_required_section_values_throws()
{
IConfigurationRoot config = CreateConfiguration(new Dictionary<string, string?>
{
["AzureKeyVault:Enabled"] = "true",
["AzureKeyVault:VaultName"] = "odin-test-vault"
});
IConfigurationBuilder builder = new ConfigurationBuilder();

InvalidOperationException result = Assert.Throws<InvalidOperationException>(
() => builder.AddOdinPrefixedAzureKeyVault(config.GetSection("AzureKeyVault")));

Assert.Contains("TenantId, ClientId, Secret", result.Message);
Assert.Empty(builder.Sources);
}

[Fact]
public void AddOdinPrefixedAzureKeyVault_with_section_name_reads_configuration_from_the_builder()
{
AzureKeyVaultConfigurationOptions options = new();
IConfigurationBuilder builder = new ConfigurationBuilder()
.AddInMemoryCollection(CreateRequiredConfiguration(prefix: " MyApp-Production- "));

IConfigurationBuilder result = builder.AddOdinPrefixedAzureKeyVault(options: options);

Assert.Same(builder, result);
Assert.Single(builder.Sources.OfType<AzureKeyVaultConfigurationSource>());
PrefixedAzureKeyVaultSecretManager manager = Assert.IsType<PrefixedAzureKeyVaultSecretManager>(options.Manager);
Assert.True(manager.Load(new("MyApp-Production-ConnectionStrings-Default")));
Assert.False(manager.Load(new("OtherApp-Production-ConnectionStrings-Default")));
}

[Fact]
public void AddOdinPrefixedAzureKeyVault_with_section_uses_vault_uri_when_vault_name_is_not_configured()
{
AzureKeyVaultConfigurationOptions options = new();
IConfigurationRoot config = CreateConfiguration(CreateRequiredConfiguration(
vaultName: null,
vaultUri: "https://odin-test-vault.vault.azure.net/",
prefix: "MyApp-Production-"));
IConfigurationBuilder builder = new ConfigurationBuilder();

IConfigurationBuilder result = builder.AddOdinPrefixedAzureKeyVault(config.GetSection("AzureKeyVault"), options);

Assert.Same(builder, result);
Assert.Single(builder.Sources.OfType<AzureKeyVaultConfigurationSource>());
Assert.IsType<PrefixedAzureKeyVaultSecretManager>(options.Manager);
}

[Fact]
public void AddOdinPrefixedAzureKeyVault_with_uri_adds_key_vault_source_and_replaces_the_options_manager()
{
AzureKeyVaultConfigurationOptions options = new()
{
ReloadInterval = TimeSpan.FromMinutes(5)
};
IConfigurationBuilder builder = new ConfigurationBuilder();

IConfigurationBuilder result = builder.AddOdinPrefixedAzureKeyVault(
new Uri("https://odin-test-vault.vault.azure.net/"),
" MyApp-Production- ",
new TestTokenCredential(),
options);

Assert.Same(builder, result);
Assert.Single(builder.Sources.OfType<AzureKeyVaultConfigurationSource>());
Assert.Equal(TimeSpan.FromMinutes(5), options.ReloadInterval);
PrefixedAzureKeyVaultSecretManager manager = Assert.IsType<PrefixedAzureKeyVaultSecretManager>(options.Manager);
Assert.True(manager.Load(new("MyApp-Production-ConnectionStrings-Default")));
Assert.False(manager.Load(new("OtherApp-Production-ConnectionStrings-Default")));
}

[Fact]
public void AddOdinPrefixedAzureKeyVault_with_vault_name_adds_key_vault_source()
{
AzureKeyVaultConfigurationOptions options = new();
IConfigurationBuilder builder = new ConfigurationBuilder();

IConfigurationBuilder result = builder.AddOdinPrefixedAzureKeyVault(
" odin-test-vault ",
"MyApp-Production-",
new TestTokenCredential(),
options);

Assert.Same(builder, result);
Assert.Single(builder.Sources.OfType<AzureKeyVaultConfigurationSource>());
Assert.IsType<PrefixedAzureKeyVaultSecretManager>(options.Manager);
}

[Fact]
public void AddOdinPrefixedAzureKeyVault_with_section_throws_when_section_is_null()
{
IConfigurationBuilder builder = new ConfigurationBuilder();

Assert.Throws<ArgumentNullException>(
() => builder.AddOdinPrefixedAzureKeyVault(akvConfigSection: null!));
}

[Fact]
public void AddOdinPrefixedAzureKeyVault_with_vault_name_throws_for_invalid_arguments()
{
IConfigurationBuilder builder = new ConfigurationBuilder();
TestTokenCredential credential = new();

Assert.Throws<ArgumentNullException>(
() => Microsoft.Extensions.Configuration.ConfigurationBuilderExtensions.AddOdinPrefixedAzureKeyVault(
null!,
"odin-test-vault",
"MyApp-Production-",
credential));
Assert.Throws<ArgumentException>(
() => builder.AddOdinPrefixedAzureKeyVault(
" ",
"MyApp-Production-",
credential));
Assert.Throws<ArgumentNullException>(
() => builder.AddOdinPrefixedAzureKeyVault(
"odin-test-vault",
"MyApp-Production-",
credential: null!));
}

[Fact]
public void AddOdinPrefixedAzureKeyVault_with_uri_throws_for_invalid_arguments()
{
IConfigurationBuilder builder = new ConfigurationBuilder();
Uri vaultUri = new("https://odin-test-vault.vault.azure.net/");
TestTokenCredential credential = new();

Assert.Throws<ArgumentNullException>(
() => Microsoft.Extensions.Configuration.ConfigurationBuilderExtensions.AddOdinPrefixedAzureKeyVault(
null!,
vaultUri,
"MyApp-Production-",
credential));
Assert.Throws<ArgumentNullException>(
() => builder.AddOdinPrefixedAzureKeyVault(
azureKeyVaultUri: null!,
prefix: "MyApp-Production-",
credential: credential));
Assert.Throws<ArgumentNullException>(
() => builder.AddOdinPrefixedAzureKeyVault(
vaultUri,
"MyApp-Production-",
credential: null!));
}

private static IConfigurationRoot CreateConfiguration(IEnumerable<KeyValuePair<string, string?>> values)
{
return new ConfigurationBuilder()
.AddInMemoryCollection(values)
.Build();
}

private static Dictionary<string, string?> CreateRequiredConfiguration(
string? vaultName = "odin-test-vault",
string? vaultUri = null,
string? prefix = "MyApp-Production-")
{
Dictionary<string, string?> values = new()
{
["AzureKeyVault:TenantId"] = "11111111-1111-1111-1111-111111111111",
["AzureKeyVault:ClientId"] = "22222222-2222-2222-2222-222222222222",
["AzureKeyVault:Secret"] = "client-secret-value",
["AzureKeyVault:Prefix"] = prefix
};

if (vaultName is not null)
{
values["AzureKeyVault:VaultName"] = vaultName;
}

if (vaultUri is not null)
{
values["AzureKeyVault:VaultUri"] = vaultUri;
}

return values;
}

private sealed class TestTokenCredential : TokenCredential
{
public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken)
{
return new AccessToken("test-token", DateTimeOffset.UtcNow.AddHours(1));
}

public override ValueTask<AccessToken> GetTokenAsync(
TokenRequestContext requestContext,
CancellationToken cancellationToken)
{
return ValueTask.FromResult(GetToken(requestContext, cancellationToken));
}
}
}
Loading
Loading