Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 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
Expand Up @@ -916,7 +916,7 @@ private async Task<IEnumerable<KeyValuePair<string, string>>> ProcessAdapters(Co
continue;
}

IEnumerable<KeyValuePair<string, string>> kvs = await adapter.ProcessKeyValue(setting, _logger, cancellationToken).ConfigureAwait(false);
IEnumerable<KeyValuePair<string, string>> kvs = await adapter.ProcessKeyValue(setting, AppConfigurationEndpoint, _logger, cancellationToken).ConfigureAwait(false);

if (kvs != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public AzureKeyVaultKeyValueAdapter(AzureKeyVaultSecretProvider secretProvider)
/// <summary> Uses the Azure Key Vault secret provider to resolve Key Vault references retrieved from Azure App Configuration. </summary>
/// <param KeyValue ="IKeyValue"> inputs the IKeyValue </param>
/// returns the keyname and actual value
public async Task<IEnumerable<KeyValuePair<string, string>>> ProcessKeyValue(ConfigurationSetting setting, Logger logger, CancellationToken cancellationToken)
public async Task<IEnumerable<KeyValuePair<string, string>>> ProcessKeyValue(ConfigurationSetting setting, Uri endpoint, Logger logger, CancellationToken cancellationToken)
{
KeyVaultSecretReference secretRef;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
using System.Text;
using System;

namespace Microsoft.Extensions.Configuration.AzureAppConfiguration.Extensions
{
internal static class BytesExtensions
{
public static string ToBase64Url(this byte[] bytes)

@jimmyca15 jimmyca15 Feb 14, 2024

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you put a summary that mentions it converts byte array to b64 URL. Using the trim trailing = characters strategy and also link to b64 spec.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
string featureFlagIdBase64 = Convert.ToBase64String(bytes);
Comment thread
amerjusupovic marked this conversation as resolved.
Outdated

int indexOfEquals = featureFlagIdBase64.IndexOf("=");

int stringBuilderCapacity = indexOfEquals != -1 ? indexOfEquals : featureFlagIdBase64.Length;

StringBuilder featureFlagIdBuilder = new StringBuilder(stringBuilderCapacity);

for (int i = 0; i < stringBuilderCapacity; i++)
{
if (featureFlagIdBase64[i] == '+')
{
featureFlagIdBuilder.Append('-');
}
else if (featureFlagIdBase64[i] == '/')
{
featureFlagIdBuilder.Append('_');
}
else
{
featureFlagIdBuilder.Append(featureFlagIdBase64[i]);
}
}

return featureFlagIdBuilder.ToString();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,8 @@ internal class FeatureManagementConstants
public const string From = "From";
public const string To = "To";
public const string Seed = "Seed";
public const string ETag = "ETag";
public const string FeatureFlagId = "FeatureFlagId";
public const string FeatureFlagReference = "FeatureFlagReference";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
// Licensed under the MIT license.
//
using Azure.Data.AppConfiguration;
using Microsoft.Extensions.Configuration.AzureAppConfiguration.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
Expand All @@ -20,7 +23,7 @@ public FeatureManagementKeyValueAdapter(FeatureFilterTracing featureFilterTracin
_featureFilterTracing = featureFilterTracing ?? throw new ArgumentNullException(nameof(featureFilterTracing));
}

public Task<IEnumerable<KeyValuePair<string, string>>> ProcessKeyValue(ConfigurationSetting setting, Logger logger, CancellationToken cancellationToken)
public Task<IEnumerable<KeyValuePair<string, string>>> ProcessKeyValue(ConfigurationSetting setting, Uri endpoint, Logger logger, CancellationToken cancellationToken)
{
FeatureFlag featureFlag;
try
Expand Down Expand Up @@ -197,15 +200,35 @@ public Task<IEnumerable<KeyValuePair<string, string>>> ProcessKeyValue(Configura

if (telemetry.Enabled)
{
keyValues.Add(new KeyValuePair<string, string>($"{telemetryPath}:{FeatureManagementConstants.Enabled}", telemetry.Enabled.ToString()));
}
if (telemetry.Metadata != null)
{
foreach (KeyValuePair<string, string> kvp in telemetry.Metadata)
{
keyValues.Add(new KeyValuePair<string, string>($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{kvp.Key}", kvp.Value));
}
}

if (telemetry.Metadata != null)
{
foreach (KeyValuePair<string, string> kvp in telemetry.Metadata)
byte[] featureFlagIdHash;

using (HashAlgorithm hashAlgorithm = SHA256.Create())
{
keyValues.Add(new KeyValuePair<string, string>($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{kvp.Key}", kvp.Value));
featureFlagIdHash = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes($"{setting.Key}\n{(string.IsNullOrWhiteSpace(setting.Label) ? null : setting.Label)}"));
}

string featureFlagId = featureFlagIdHash.ToBase64Url();

keyValues.Add(new KeyValuePair<string, string>($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{FeatureManagementConstants.FeatureFlagId}", featureFlagId));

if (endpoint != null)
{
string featureFlagReference = $"{endpoint.AbsoluteUri}kv/{setting.Key}{(!string.IsNullOrWhiteSpace(setting.Label) ? $"?label={setting.Label}" : "")}";

keyValues.Add(new KeyValuePair<string, string>($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{FeatureManagementConstants.FeatureFlagReference}", featureFlagReference));
}

keyValues.Add(new KeyValuePair<string, string>($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{FeatureManagementConstants.ETag}", setting.ETag.ToString()));

keyValues.Add(new KeyValuePair<string, string>($"{telemetryPath}:{FeatureManagementConstants.Enabled}", telemetry.Enabled.ToString()));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT license.
//
using Azure;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT license.
//
using Azure.Data.AppConfiguration;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
Expand All @@ -10,7 +11,7 @@ namespace Microsoft.Extensions.Configuration.AzureAppConfiguration
{
internal interface IKeyValueAdapter
{
Task<IEnumerable<KeyValuePair<string, string>>> ProcessKeyValue(ConfigurationSetting setting, Logger logger, CancellationToken cancellationToken);
Task<IEnumerable<KeyValuePair<string, string>>> ProcessKeyValue(ConfigurationSetting setting, Uri endpoint, Logger logger, CancellationToken cancellationToken);

bool CanProcess(ConfigurationSetting setting);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ internal class JsonKeyValueAdapter : IKeyValueAdapter
KeyVaultConstants.ContentType
};

public Task<IEnumerable<KeyValuePair<string, string>>> ProcessKeyValue(ConfigurationSetting setting, Logger logger, CancellationToken cancellationToken)
public Task<IEnumerable<KeyValuePair<string, string>>> ProcessKeyValue(ConfigurationSetting setting, Uri endpoint, Logger logger, CancellationToken cancellationToken)
{
if (setting == null)
{
Expand Down
23 changes: 22 additions & 1 deletion tests/Tests.AzureAppConfiguration/FeatureManagementTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Azure.Core.Testing;
using Azure.Data.AppConfiguration;
using Azure.Data.AppConfiguration.Tests;
using Azure.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.AzureAppConfiguration;
using Microsoft.Extensions.Configuration.AzureAppConfiguration.FeatureManagement;
Expand All @@ -14,6 +15,9 @@
using System.Collections.Generic;
using System.Diagnostics.Tracing;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
Expand Down Expand Up @@ -288,7 +292,7 @@ public class FeatureManagementTests
}
}
",
label: default,
label: "label",
contentType: FeatureManagementConstants.ContentType + ";charset=utf-8",
eTag: new ETag("c3c231fd-39a0-4cb6-3237-4614474b92c1"));

Expand Down Expand Up @@ -1330,13 +1334,30 @@ public void WithTelemetry()
.AddAzureAppConfiguration(options =>
{
options.ClientManager = TestHelpers.CreateMockedConfigurationClientManager(mockClient.Object);
options.Connect(TestHelpers.PrimaryConfigStoreEndpoint, new DefaultAzureCredential());
options.UseFeatureFlags();
})
.Build();

Assert.Equal("True", config["FeatureManagement:TelemetryFeature:Telemetry:Enabled"]);
Assert.Equal("Tag1Value", config["FeatureManagement:TelemetryFeature:Telemetry:Metadata:Tags.Tag1"]);
Assert.Equal("Tag2Value", config["FeatureManagement:TelemetryFeature:Telemetry:Metadata:Tags.Tag2"]);
Assert.Equal("c3c231fd-39a0-4cb6-3237-4614474b92c1", config["FeatureManagement:TelemetryFeature:Telemetry:Metadata:ETag"]);

byte[] featureFlagIdHash;

using (HashAlgorithm hashAlgorithm = SHA256.Create())
{
featureFlagIdHash = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes($"{FeatureManagementConstants.FeatureFlagMarker}TelemetryFeature\nlabel"));
}

string featureFlagId = Convert.ToBase64String(featureFlagIdHash)
.TrimEnd('=')
.Replace('+', '-')
.Replace('/', '_');

Assert.Equal(featureFlagId, config["FeatureManagement:TelemetryFeature:Telemetry:Metadata:FeatureFlagId"]);
Assert.Equal($"{TestHelpers.PrimaryConfigStoreEndpoint}kv/{FeatureManagementConstants.FeatureFlagMarker}TelemetryFeature?label=label", config["FeatureManagement:TelemetryFeature:Telemetry:Metadata:FeatureFlagReference"]);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ public void DoesNotThrowKeyVaultExceptionWhenProviderIsOptional()
var mockKeyValueAdapter = new Mock<IKeyValueAdapter>(MockBehavior.Strict);
mockKeyValueAdapter.Setup(adapter => adapter.CanProcess(_kv))
.Returns(true);
mockKeyValueAdapter.Setup(adapter => adapter.ProcessKeyValue(_kv, It.IsAny<Logger>(), It.IsAny<CancellationToken>()))
mockKeyValueAdapter.Setup(adapter => adapter.ProcessKeyValue(_kv, It.IsAny<Uri>(), It.IsAny<Logger>(), It.IsAny<CancellationToken>()))
.Throws(new KeyVaultReferenceException("Key vault error", null));
mockKeyValueAdapter.Setup(adapter => adapter.InvalidateCache(null));

Expand Down