From 429ec6ea6afa0cace87a1ab8abd07a09b1e2ba63 Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Fri, 2 Feb 2024 14:24:34 -0800 Subject: [PATCH 01/19] design questions for Id format pending --- .../FeatureManagement/FeatureManagementConstants.cs | 2 ++ .../FeatureManagement/FeatureManagementKeyValueAdapter.cs | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementConstants.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementConstants.cs index 24c3ed886..a60122730 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementConstants.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementConstants.cs @@ -31,5 +31,7 @@ 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"; } } diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs index 457a3a6ae..002da210b 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs @@ -200,6 +200,12 @@ public Task>> ProcessKeyValue(Configura keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Enabled}", telemetry.Enabled.ToString())); } + string featureFlagId = $"{setting.Key}\n{setting.Label}"; + + keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{FeatureManagementConstants.FeatureFlagId}", featureFlagId)); + + keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{FeatureManagementConstants.ETag}", setting.ETag.ToString())); + if (telemetry.Metadata != null) { foreach (KeyValuePair kvp in telemetry.Metadata) From 8ce64091c0594ddd3603fe72b17d12b1d9da9485 Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Thu, 8 Feb 2024 14:17:06 -0800 Subject: [PATCH 02/19] first draft adding ffid and ffref --- .../AzureAppConfigurationProvider.cs | 2 +- .../AzureKeyVaultKeyValueAdapter.cs | 2 +- .../FeatureManagementConstants.cs | 1 + .../FeatureManagementKeyValueAdapter.cs | 18 ++++++++++++++++-- .../IConfigurationRefresher.cs | 1 - .../IKeyValueAdapter.cs | 3 ++- .../JsonKeyValueAdapter.cs | 2 +- .../KeyVaultReferenceTests.cs | 2 +- 8 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs index a7638871b..4e3540980 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs @@ -916,7 +916,7 @@ private async Task>> ProcessAdapters(Co continue; } - IEnumerable> kvs = await adapter.ProcessKeyValue(setting, _logger, cancellationToken).ConfigureAwait(false); + IEnumerable> kvs = await adapter.ProcessKeyValue(setting, AppConfigurationEndpoint, _logger, cancellationToken).ConfigureAwait(false); if (kvs != null) { diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureKeyVaultReference/AzureKeyVaultKeyValueAdapter.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureKeyVaultReference/AzureKeyVaultKeyValueAdapter.cs index 21b5165f9..e49deb2cb 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureKeyVaultReference/AzureKeyVaultKeyValueAdapter.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureKeyVaultReference/AzureKeyVaultKeyValueAdapter.cs @@ -27,7 +27,7 @@ public AzureKeyVaultKeyValueAdapter(AzureKeyVaultSecretProvider secretProvider) /// Uses the Azure Key Vault secret provider to resolve Key Vault references retrieved from Azure App Configuration. /// inputs the IKeyValue /// returns the keyname and actual value - public async Task>> ProcessKeyValue(ConfigurationSetting setting, Logger logger, CancellationToken cancellationToken) + public async Task>> ProcessKeyValue(ConfigurationSetting setting, Uri endpoint, Logger logger, CancellationToken cancellationToken) { KeyVaultSecretReference secretRef; diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementConstants.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementConstants.cs index a60122730..578067314 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementConstants.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementConstants.cs @@ -33,5 +33,6 @@ internal class FeatureManagementConstants public const string Seed = "Seed"; public const string ETag = "ETag"; public const string FeatureFlagId = "FeatureFlagId"; + public const string FeatureFlagReference = "FeatureFlagReference"; } } diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs index 002da210b..27fa3d9e5 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs @@ -5,6 +5,9 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; +using System.Security.Cryptography; +using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -20,7 +23,7 @@ public FeatureManagementKeyValueAdapter(FeatureFilterTracing featureFilterTracin _featureFilterTracing = featureFilterTracing ?? throw new ArgumentNullException(nameof(featureFilterTracing)); } - public Task>> ProcessKeyValue(ConfigurationSetting setting, Logger logger, CancellationToken cancellationToken) + public Task>> ProcessKeyValue(ConfigurationSetting setting, Uri endpoint, Logger logger, CancellationToken cancellationToken) { FeatureFlag featureFlag; try @@ -200,10 +203,21 @@ public Task>> ProcessKeyValue(Configura keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Enabled}", telemetry.Enabled.ToString())); } - string featureFlagId = $"{setting.Key}\n{setting.Label}"; + byte[] featureFlagIdHash; + + using (HashAlgorithm hashAlgorithm = SHA256.Create()) + { + featureFlagIdHash = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes($"{setting.Key}\n{setting.Label}")); + } + + string featureFlagId = WebUtility.UrlEncode(Convert.ToBase64String(featureFlagIdHash)); + + string featureFlagReference = $"{endpoint.AbsoluteUri}kv/{setting.Key}{(setting.Label != null ? $"?label={setting.Label}" : "")}"; keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{FeatureManagementConstants.FeatureFlagId}", featureFlagId)); + keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{FeatureManagementConstants.FeatureFlagReference}", featureFlagReference)); + keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{FeatureManagementConstants.ETag}", setting.ETag.ToString())); if (telemetry.Metadata != null) diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/IConfigurationRefresher.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/IConfigurationRefresher.cs index b47184a29..78df71256 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/IConfigurationRefresher.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/IConfigurationRefresher.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. // using Azure; -using Microsoft.Extensions.Logging; using System; using System.Threading; using System.Threading.Tasks; diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/IKeyValueAdapter.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/IKeyValueAdapter.cs index 48fc85c5c..8da0ee55d 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/IKeyValueAdapter.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/IKeyValueAdapter.cs @@ -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; @@ -10,7 +11,7 @@ namespace Microsoft.Extensions.Configuration.AzureAppConfiguration { internal interface IKeyValueAdapter { - Task>> ProcessKeyValue(ConfigurationSetting setting, Logger logger, CancellationToken cancellationToken); + Task>> ProcessKeyValue(ConfigurationSetting setting, Uri endpoint, Logger logger, CancellationToken cancellationToken); bool CanProcess(ConfigurationSetting setting); diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/JsonKeyValueAdapter.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/JsonKeyValueAdapter.cs index 0a47923fc..fed06f11b 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/JsonKeyValueAdapter.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/JsonKeyValueAdapter.cs @@ -22,7 +22,7 @@ internal class JsonKeyValueAdapter : IKeyValueAdapter KeyVaultConstants.ContentType }; - public Task>> ProcessKeyValue(ConfigurationSetting setting, Logger logger, CancellationToken cancellationToken) + public Task>> ProcessKeyValue(ConfigurationSetting setting, Uri endpoint, Logger logger, CancellationToken cancellationToken) { if (setting == null) { diff --git a/tests/Tests.AzureAppConfiguration/KeyVaultReferenceTests.cs b/tests/Tests.AzureAppConfiguration/KeyVaultReferenceTests.cs index 0cccf3cd2..9a80a350e 100644 --- a/tests/Tests.AzureAppConfiguration/KeyVaultReferenceTests.cs +++ b/tests/Tests.AzureAppConfiguration/KeyVaultReferenceTests.cs @@ -422,7 +422,7 @@ public void DoesNotThrowKeyVaultExceptionWhenProviderIsOptional() var mockKeyValueAdapter = new Mock(MockBehavior.Strict); mockKeyValueAdapter.Setup(adapter => adapter.CanProcess(_kv)) .Returns(true); - mockKeyValueAdapter.Setup(adapter => adapter.ProcessKeyValue(_kv, It.IsAny(), It.IsAny())) + mockKeyValueAdapter.Setup(adapter => adapter.ProcessKeyValue(_kv, It.IsAny(), It.IsAny(), It.IsAny())) .Throws(new KeyVaultReferenceException("Key vault error", null)); mockKeyValueAdapter.Setup(adapter => adapter.InvalidateCache(null)); From e0681212c4fc1a4274e181286da91c7532eff0af Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Thu, 8 Feb 2024 14:26:18 -0800 Subject: [PATCH 03/19] fix telemetry test bug --- .../FeatureManagementKeyValueAdapter.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs index 27fa3d9e5..1af113610 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs @@ -212,11 +212,14 @@ public Task>> ProcessKeyValue(Configura string featureFlagId = WebUtility.UrlEncode(Convert.ToBase64String(featureFlagIdHash)); - string featureFlagReference = $"{endpoint.AbsoluteUri}kv/{setting.Key}{(setting.Label != null ? $"?label={setting.Label}" : "")}"; - keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{FeatureManagementConstants.FeatureFlagId}", featureFlagId)); - keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{FeatureManagementConstants.FeatureFlagReference}", featureFlagReference)); + if (endpoint != null) + { + string featureFlagReference = $"{endpoint.AbsoluteUri}kv/{setting.Key}{(setting.Label != null ? $"?label={setting.Label}" : "")}"; + + keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{FeatureManagementConstants.FeatureFlagReference}", featureFlagReference)); + } keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{FeatureManagementConstants.ETag}", setting.ETag.ToString())); From 9f5f126459231de6f0feef096bcfe4b1b3a73d20 Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Fri, 9 Feb 2024 15:36:04 -0800 Subject: [PATCH 04/19] fix telemetry test and only populate when telemetry is enabled --- .../AzureAppConfigurationProvider.cs | 8 ++++++ .../FeatureManagementKeyValueAdapter.cs | 28 +++++++++---------- .../FeatureManagementTests.cs | 16 +++++++++++ 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs index 4e3540980..c7abcbe3d 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs @@ -75,6 +75,14 @@ public Uri AppConfigurationEndpoint catch (FormatException) { } } + // This is used only when a test passes in a ConfigurationClientManager but no endpoint or connection string is provided. + ConfigurationClient client = _configClientManager.GetClients().FirstOrDefault(); + + if (client != null) + { + return _configClientManager.GetEndpointForClient(client); + } + return null; } } diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs index 1af113610..692803e22 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs @@ -201,27 +201,27 @@ public Task>> ProcessKeyValue(Configura if (telemetry.Enabled) { keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Enabled}", telemetry.Enabled.ToString())); - } - byte[] featureFlagIdHash; + byte[] featureFlagIdHash; - using (HashAlgorithm hashAlgorithm = SHA256.Create()) - { - featureFlagIdHash = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes($"{setting.Key}\n{setting.Label}")); - } + using (HashAlgorithm hashAlgorithm = SHA256.Create()) + { + featureFlagIdHash = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes($"{setting.Key}\n{setting.Label}")); + } - string featureFlagId = WebUtility.UrlEncode(Convert.ToBase64String(featureFlagIdHash)); + string featureFlagId = WebUtility.UrlEncode(Convert.ToBase64String(featureFlagIdHash)); - keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{FeatureManagementConstants.FeatureFlagId}", featureFlagId)); + keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{FeatureManagementConstants.FeatureFlagId}", featureFlagId)); - if (endpoint != null) - { - string featureFlagReference = $"{endpoint.AbsoluteUri}kv/{setting.Key}{(setting.Label != null ? $"?label={setting.Label}" : "")}"; + if (endpoint != null) + { + string featureFlagReference = $"{endpoint.AbsoluteUri}kv/{setting.Key}{(setting.Label != null ? $"?label={setting.Label}" : "")}"; - keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{FeatureManagementConstants.FeatureFlagReference}", featureFlagReference)); - } + keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{FeatureManagementConstants.FeatureFlagReference}", featureFlagReference)); + } - keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{FeatureManagementConstants.ETag}", setting.ETag.ToString())); + keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{FeatureManagementConstants.ETag}", setting.ETag.ToString())); + } if (telemetry.Metadata != null) { diff --git a/tests/Tests.AzureAppConfiguration/FeatureManagementTests.cs b/tests/Tests.AzureAppConfiguration/FeatureManagementTests.cs index 61707529d..459adbf8f 100644 --- a/tests/Tests.AzureAppConfiguration/FeatureManagementTests.cs +++ b/tests/Tests.AzureAppConfiguration/FeatureManagementTests.cs @@ -14,6 +14,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; @@ -1337,6 +1340,19 @@ public void WithTelemetry() 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\n")); + } + + string featureFlagId = WebUtility.UrlEncode(Convert.ToBase64String(featureFlagIdHash)); + + Assert.Equal(featureFlagId, config["FeatureManagement:TelemetryFeature:Telemetry:Metadata:FeatureFlagId"]); + Assert.Equal($"https://azure.azconfig.io/kv/{FeatureManagementConstants.FeatureFlagMarker}TelemetryFeature", config["FeatureManagement:TelemetryFeature:Telemetry:Metadata:FeatureFlagReference"]); } From 8ed359d356b3522e5a61c4b008ac51eed9bc4e3a Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Fri, 9 Feb 2024 15:37:40 -0800 Subject: [PATCH 05/19] give priority to provider populated metadata values --- .../FeatureManagementKeyValueAdapter.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs index 692803e22..66dab28f8 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs @@ -198,6 +198,14 @@ public Task>> ProcessKeyValue(Configura string telemetryPath = $"{featureFlagPath}:{FeatureManagementConstants.Telemetry}"; + if (telemetry.Metadata != null) + { + foreach (KeyValuePair kvp in telemetry.Metadata) + { + keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{kvp.Key}", kvp.Value)); + } + } + if (telemetry.Enabled) { keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Enabled}", telemetry.Enabled.ToString())); @@ -222,14 +230,6 @@ public Task>> ProcessKeyValue(Configura keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{FeatureManagementConstants.ETag}", setting.ETag.ToString())); } - - if (telemetry.Metadata != null) - { - foreach (KeyValuePair kvp in telemetry.Metadata) - { - keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{kvp.Key}", kvp.Value)); - } - } } return Task.FromResult>>(keyValues); From 1a95a530d6c3e72bfacff8c3dfb30b17a9a721ce Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Fri, 9 Feb 2024 16:28:17 -0800 Subject: [PATCH 06/19] test label in telemetry test --- tests/Tests.AzureAppConfiguration/FeatureManagementTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Tests.AzureAppConfiguration/FeatureManagementTests.cs b/tests/Tests.AzureAppConfiguration/FeatureManagementTests.cs index 459adbf8f..13f845324 100644 --- a/tests/Tests.AzureAppConfiguration/FeatureManagementTests.cs +++ b/tests/Tests.AzureAppConfiguration/FeatureManagementTests.cs @@ -291,7 +291,7 @@ public class FeatureManagementTests } } ", - label: default, + label: "label", contentType: FeatureManagementConstants.ContentType + ";charset=utf-8", eTag: new ETag("c3c231fd-39a0-4cb6-3237-4614474b92c1")); @@ -1346,13 +1346,13 @@ public void WithTelemetry() using (HashAlgorithm hashAlgorithm = SHA256.Create()) { - featureFlagIdHash = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes($"{FeatureManagementConstants.FeatureFlagMarker}TelemetryFeature\n")); + featureFlagIdHash = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes($"{FeatureManagementConstants.FeatureFlagMarker}TelemetryFeature\nlabel")); } string featureFlagId = WebUtility.UrlEncode(Convert.ToBase64String(featureFlagIdHash)); Assert.Equal(featureFlagId, config["FeatureManagement:TelemetryFeature:Telemetry:Metadata:FeatureFlagId"]); - Assert.Equal($"https://azure.azconfig.io/kv/{FeatureManagementConstants.FeatureFlagMarker}TelemetryFeature", config["FeatureManagement:TelemetryFeature:Telemetry:Metadata:FeatureFlagReference"]); + Assert.Equal($"{TestHelpers.PrimaryConfigStoreEndpoint}kv/{FeatureManagementConstants.FeatureFlagMarker}TelemetryFeature?label=label", config["FeatureManagement:TelemetryFeature:Telemetry:Metadata:FeatureFlagReference"]); } From 5e36fe3a2f0193eb128f507c6ea1da1488eefb73 Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Fri, 9 Feb 2024 16:34:35 -0800 Subject: [PATCH 07/19] keep metadata values together in output --- .../FeatureManagement/FeatureManagementKeyValueAdapter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs index 66dab28f8..5395d9bf0 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs @@ -208,8 +208,6 @@ public Task>> ProcessKeyValue(Configura if (telemetry.Enabled) { - keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Enabled}", telemetry.Enabled.ToString())); - byte[] featureFlagIdHash; using (HashAlgorithm hashAlgorithm = SHA256.Create()) @@ -229,6 +227,8 @@ public Task>> ProcessKeyValue(Configura } keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{FeatureManagementConstants.ETag}", setting.ETag.ToString())); + + keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Enabled}", telemetry.Enabled.ToString())); } } From 74553d815279e93cf5cb171af00961fd1e36e07d Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Mon, 12 Feb 2024 10:40:33 -0800 Subject: [PATCH 08/19] remove unnecessary code for checking client manager endpoint --- .../AzureAppConfigurationProvider.cs | 8 -------- .../Tests.AzureAppConfiguration/FeatureManagementTests.cs | 2 ++ 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs index c7abcbe3d..4e3540980 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs @@ -75,14 +75,6 @@ public Uri AppConfigurationEndpoint catch (FormatException) { } } - // This is used only when a test passes in a ConfigurationClientManager but no endpoint or connection string is provided. - ConfigurationClient client = _configClientManager.GetClients().FirstOrDefault(); - - if (client != null) - { - return _configClientManager.GetEndpointForClient(client); - } - return null; } } diff --git a/tests/Tests.AzureAppConfiguration/FeatureManagementTests.cs b/tests/Tests.AzureAppConfiguration/FeatureManagementTests.cs index 13f845324..2dbd21514 100644 --- a/tests/Tests.AzureAppConfiguration/FeatureManagementTests.cs +++ b/tests/Tests.AzureAppConfiguration/FeatureManagementTests.cs @@ -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; @@ -1333,6 +1334,7 @@ public void WithTelemetry() .AddAzureAppConfiguration(options => { options.ClientManager = TestHelpers.CreateMockedConfigurationClientManager(mockClient.Object); + options.Connect(TestHelpers.PrimaryConfigStoreEndpoint, new DefaultAzureCredential()); options.UseFeatureFlags(); }) .Build(); From d208f2eab04e592fb2c8bd52a4be1555af902efb Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Mon, 12 Feb 2024 13:26:21 -0800 Subject: [PATCH 09/19] fix base64url logic --- .../FeatureManagement/FeatureManagementKeyValueAdapter.cs | 6 ++++-- tests/Tests.AzureAppConfiguration/FeatureManagementTests.cs | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs index 5395d9bf0..e2acfe223 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net; using System.Security.Cryptography; using System.Text; using System.Text.Json; @@ -215,7 +214,10 @@ public Task>> ProcessKeyValue(Configura featureFlagIdHash = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes($"{setting.Key}\n{setting.Label}")); } - string featureFlagId = WebUtility.UrlEncode(Convert.ToBase64String(featureFlagIdHash)); + string featureFlagId = Convert.ToBase64String(featureFlagIdHash) + .TrimEnd('=') + .Replace('+', '-') + .Replace('/', '_'); keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{FeatureManagementConstants.FeatureFlagId}", featureFlagId)); diff --git a/tests/Tests.AzureAppConfiguration/FeatureManagementTests.cs b/tests/Tests.AzureAppConfiguration/FeatureManagementTests.cs index 2dbd21514..02a3d3c8c 100644 --- a/tests/Tests.AzureAppConfiguration/FeatureManagementTests.cs +++ b/tests/Tests.AzureAppConfiguration/FeatureManagementTests.cs @@ -1351,7 +1351,10 @@ public void WithTelemetry() featureFlagIdHash = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes($"{FeatureManagementConstants.FeatureFlagMarker}TelemetryFeature\nlabel")); } - string featureFlagId = WebUtility.UrlEncode(Convert.ToBase64String(featureFlagIdHash)); + 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"]); From 35e27411764559ec3a90f18f5746bc14ccad425a Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Mon, 12 Feb 2024 15:54:48 -0800 Subject: [PATCH 10/19] check for whitespace in label --- .../FeatureManagement/FeatureManagementKeyValueAdapter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs index e2acfe223..a46cdbc40 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs @@ -211,7 +211,7 @@ public Task>> ProcessKeyValue(Configura using (HashAlgorithm hashAlgorithm = SHA256.Create()) { - featureFlagIdHash = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes($"{setting.Key}\n{setting.Label}")); + featureFlagIdHash = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes($"{setting.Key}\n{(string.IsNullOrWhiteSpace(setting.Label) ? null : setting.Label)}")); } string featureFlagId = Convert.ToBase64String(featureFlagIdHash) From 07f7c7feabbf3ca8e5ffcc20bbe27e7ba900ebe0 Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Tue, 13 Feb 2024 12:44:55 -0800 Subject: [PATCH 11/19] use stringbuilder over replace and trim for performance --- .../FeatureManagementKeyValueAdapter.cs | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs index a46cdbc40..1e29194cb 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs @@ -214,12 +214,27 @@ public Task>> ProcessKeyValue(Configura featureFlagIdHash = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes($"{setting.Key}\n{(string.IsNullOrWhiteSpace(setting.Label) ? null : setting.Label)}")); } - string featureFlagId = Convert.ToBase64String(featureFlagIdHash) - .TrimEnd('=') - .Replace('+', '-') - .Replace('/', '_'); + string featureFlagIdBase64 = Convert.ToBase64String(featureFlagIdHash); - keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{FeatureManagementConstants.FeatureFlagId}", featureFlagId)); + StringBuilder featureFlagIdBuilder = new StringBuilder(featureFlagIdBase64[featureFlagIdBase64.Length - 1] == '=' ? featureFlagIdBase64.Length - 1 : featureFlagIdBase64.Length); + + for (int i = 0; i < featureFlagIdBuilder.Capacity; i++) + { + if (featureFlagIdBase64[i] == '+') + { + featureFlagIdBuilder.Append('-'); + } + else if (featureFlagIdBase64[i] == '/') + { + featureFlagIdBuilder.Append('_'); + } + else + { + featureFlagIdBuilder.Append(featureFlagIdBase64[i]); + } + } + + keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{FeatureManagementConstants.FeatureFlagId}", featureFlagIdBuilder.ToString())); if (endpoint != null) { From c1660811c2686a0b872a0b42e43f95a49d5387d7 Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Tue, 13 Feb 2024 15:06:08 -0800 Subject: [PATCH 12/19] use indexof to check for equals --- .../FeatureManagement/FeatureManagementKeyValueAdapter.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs index 1e29194cb..d4dc78a0f 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs @@ -216,7 +216,9 @@ public Task>> ProcessKeyValue(Configura string featureFlagIdBase64 = Convert.ToBase64String(featureFlagIdHash); - StringBuilder featureFlagIdBuilder = new StringBuilder(featureFlagIdBase64[featureFlagIdBase64.Length - 1] == '=' ? featureFlagIdBase64.Length - 1 : featureFlagIdBase64.Length); + int indexOfEquals = featureFlagIdBase64.IndexOf("="); + + StringBuilder featureFlagIdBuilder = new StringBuilder(indexOfEquals != -1 ? indexOfEquals : featureFlagIdBase64.Length); for (int i = 0; i < featureFlagIdBuilder.Capacity; i++) { From d916ad41a13bc472bd4b845d0737fb408e94ba0e Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Tue, 13 Feb 2024 17:46:06 -0800 Subject: [PATCH 13/19] use static value for capacity in loop --- .../FeatureManagement/FeatureManagementKeyValueAdapter.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs index d4dc78a0f..459dce082 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs @@ -218,9 +218,11 @@ public Task>> ProcessKeyValue(Configura int indexOfEquals = featureFlagIdBase64.IndexOf("="); - StringBuilder featureFlagIdBuilder = new StringBuilder(indexOfEquals != -1 ? indexOfEquals : featureFlagIdBase64.Length); + int stringBuilderCapacity = indexOfEquals != -1 ? indexOfEquals : featureFlagIdBase64.Length; - for (int i = 0; i < featureFlagIdBuilder.Capacity; i++) + StringBuilder featureFlagIdBuilder = new StringBuilder(stringBuilderCapacity); + + for (int i = 0; i < stringBuilderCapacity; i++) { if (featureFlagIdBase64[i] == '+') { From e7ba889ddb86dfa647b009a57ca459bc7a7a62b5 Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Wed, 14 Feb 2024 11:07:14 -0800 Subject: [PATCH 14/19] resolve comments --- .../Extensions/BytesExtensions.cs | 40 ++++++++++++++++++ .../FeatureManagementKeyValueAdapter.cs | 41 +++++-------------- 2 files changed, 50 insertions(+), 31 deletions(-) create mode 100644 src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Extensions/BytesExtensions.cs diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Extensions/BytesExtensions.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Extensions/BytesExtensions.cs new file mode 100644 index 000000000..256beac4a --- /dev/null +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Extensions/BytesExtensions.cs @@ -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) + { + string featureFlagIdBase64 = Convert.ToBase64String(bytes); + + 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(); + } + } +} diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs index 459dce082..021521611 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. // using Azure.Data.AppConfiguration; +using Microsoft.Extensions.Configuration.AzureAppConfiguration.Extensions; using System; using System.Collections.Generic; using System.Linq; @@ -197,16 +198,16 @@ public Task>> ProcessKeyValue(Configura string telemetryPath = $"{featureFlagPath}:{FeatureManagementConstants.Telemetry}"; - if (telemetry.Metadata != null) + if (telemetry.Enabled) { - foreach (KeyValuePair kvp in telemetry.Metadata) + if (telemetry.Metadata != null) { - keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{kvp.Key}", kvp.Value)); + foreach (KeyValuePair kvp in telemetry.Metadata) + { + keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{kvp.Key}", kvp.Value)); + } } - } - if (telemetry.Enabled) - { byte[] featureFlagIdHash; using (HashAlgorithm hashAlgorithm = SHA256.Create()) @@ -214,35 +215,13 @@ public Task>> ProcessKeyValue(Configura featureFlagIdHash = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes($"{setting.Key}\n{(string.IsNullOrWhiteSpace(setting.Label) ? null : setting.Label)}")); } - string featureFlagIdBase64 = Convert.ToBase64String(featureFlagIdHash); - - 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]); - } - } + string featureFlagId = featureFlagIdHash.ToBase64Url(); - keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{FeatureManagementConstants.FeatureFlagId}", featureFlagIdBuilder.ToString())); + keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{FeatureManagementConstants.FeatureFlagId}", featureFlagId)); if (endpoint != null) { - string featureFlagReference = $"{endpoint.AbsoluteUri}kv/{setting.Key}{(setting.Label != null ? $"?label={setting.Label}" : "")}"; + string featureFlagReference = $"{endpoint.AbsoluteUri}kv/{setting.Key}{(!string.IsNullOrWhiteSpace(setting.Label) ? $"?label={setting.Label}" : "")}"; keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{FeatureManagementConstants.FeatureFlagReference}", featureFlagReference)); } From c06849271676fe153fb75aa6892cfedbd1fe1977 Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Wed, 14 Feb 2024 11:43:33 -0800 Subject: [PATCH 15/19] fix extension --- .../Extensions/BytesExtensions.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Extensions/BytesExtensions.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Extensions/BytesExtensions.cs index 256beac4a..6e356d84d 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Extensions/BytesExtensions.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Extensions/BytesExtensions.cs @@ -10,31 +10,31 @@ internal static class BytesExtensions { public static string ToBase64Url(this byte[] bytes) { - string featureFlagIdBase64 = Convert.ToBase64String(bytes); + string bytesBase64 = Convert.ToBase64String(bytes); - int indexOfEquals = featureFlagIdBase64.IndexOf("="); + int indexOfEquals = bytesBase64.IndexOf("="); - int stringBuilderCapacity = indexOfEquals != -1 ? indexOfEquals : featureFlagIdBase64.Length; + int stringBuilderCapacity = indexOfEquals != -1 ? indexOfEquals : bytesBase64.Length; - StringBuilder featureFlagIdBuilder = new StringBuilder(stringBuilderCapacity); + StringBuilder stringBuilder = new StringBuilder(stringBuilderCapacity); for (int i = 0; i < stringBuilderCapacity; i++) { - if (featureFlagIdBase64[i] == '+') + if (bytesBase64[i] == '+') { - featureFlagIdBuilder.Append('-'); + stringBuilder.Append('-'); } - else if (featureFlagIdBase64[i] == '/') + else if (bytesBase64[i] == '/') { - featureFlagIdBuilder.Append('_'); + stringBuilder.Append('_'); } else { - featureFlagIdBuilder.Append(featureFlagIdBase64[i]); + stringBuilder.Append(bytesBase64[i]); } } - return featureFlagIdBuilder.ToString(); + return stringBuilder.ToString(); } } } From 5815b5c18b83975147dfd5c73e8bfc1063fc179c Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Wed, 14 Feb 2024 11:58:23 -0800 Subject: [PATCH 16/19] pull out id calculation to private method --- .../Extensions/BytesExtensions.cs | 2 ++ .../FeatureManagementKeyValueAdapter.cs | 23 ++++++++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Extensions/BytesExtensions.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Extensions/BytesExtensions.cs index 6e356d84d..15a49f1dd 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Extensions/BytesExtensions.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Extensions/BytesExtensions.cs @@ -14,10 +14,12 @@ public static string ToBase64Url(this byte[] bytes) int indexOfEquals = bytesBase64.IndexOf("="); + // Remove all instances of "=" at the end of the string that were added as padding int stringBuilderCapacity = indexOfEquals != -1 ? indexOfEquals : bytesBase64.Length; StringBuilder stringBuilder = new StringBuilder(stringBuilderCapacity); + // Construct Base64URL string by replacing characters in Base64 conversion that are not URL safe for (int i = 0; i < stringBuilderCapacity; i++) { if (bytesBase64[i] == '+') diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs index 021521611..e22437ba3 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs @@ -208,14 +208,7 @@ public Task>> ProcessKeyValue(Configura } } - byte[] featureFlagIdHash; - - using (HashAlgorithm hashAlgorithm = SHA256.Create()) - { - featureFlagIdHash = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes($"{setting.Key}\n{(string.IsNullOrWhiteSpace(setting.Label) ? null : setting.Label)}")); - } - - string featureFlagId = featureFlagIdHash.ToBase64Url(); + string featureFlagId = CalculateFeatureFlagId(setting.Key, setting.Label); keyValues.Add(new KeyValuePair($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{FeatureManagementConstants.FeatureFlagId}", featureFlagId)); @@ -252,5 +245,19 @@ public bool NeedsRefresh() { return false; } + + private static string CalculateFeatureFlagId(string key, string label) + { + byte[] featureFlagIdHash; + + // Convert the value consisting of key, newline character, and label to a byte array using UTF8 encoding to hash it using SHA 256 + using (HashAlgorithm hashAlgorithm = SHA256.Create()) + { + featureFlagIdHash = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes($"{key}\n{(string.IsNullOrWhiteSpace(label) ? null : label)}")); + } + + // Convert the hashed byte array to Base64Url + return featureFlagIdHash.ToBase64Url(); + } } } From 790e412b9457de732602b49df53d8cd47ddd7cb5 Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Wed, 14 Feb 2024 12:45:56 -0800 Subject: [PATCH 17/19] add summary comments --- .../Extensions/BytesExtensions.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Extensions/BytesExtensions.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Extensions/BytesExtensions.cs index 15a49f1dd..192680937 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Extensions/BytesExtensions.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Extensions/BytesExtensions.cs @@ -8,6 +8,9 @@ namespace Microsoft.Extensions.Configuration.AzureAppConfiguration.Extensions { internal static class BytesExtensions { + /// + /// Converts a byte array to Base64URL string and removes trailing '=' characters. + /// public static string ToBase64Url(this byte[] bytes) { string bytesBase64 = Convert.ToBase64String(bytes); From a47f14789fb5127be26bd02f743b2ab3f5784193 Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Wed, 14 Feb 2024 12:49:44 -0800 Subject: [PATCH 18/19] add base64 spec --- .../Extensions/BytesExtensions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Extensions/BytesExtensions.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Extensions/BytesExtensions.cs index 192680937..58f06f152 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Extensions/BytesExtensions.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Extensions/BytesExtensions.cs @@ -10,6 +10,7 @@ internal static class BytesExtensions { /// /// Converts a byte array to Base64URL string and removes trailing '=' characters. + /// Base64 description: https://datatracker.ietf.org/doc/html/rfc4648.html#section-4 /// public static string ToBase64Url(this byte[] bytes) { From 80178fbc6c871eff9f7bb0161460e7af9d86ddc4 Mon Sep 17 00:00:00 2001 From: Amer Jusupovic <32405726+amerjusupovic@users.noreply.github.com> Date: Wed, 14 Feb 2024 13:32:55 -0800 Subject: [PATCH 19/19] Update src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Extensions/BytesExtensions.cs Co-authored-by: Jimmy Campbell --- .../Extensions/BytesExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Extensions/BytesExtensions.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Extensions/BytesExtensions.cs index 58f06f152..faf20653d 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Extensions/BytesExtensions.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Extensions/BytesExtensions.cs @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.Configuration.AzureAppConfiguration.Extensions internal static class BytesExtensions { /// - /// Converts a byte array to Base64URL string and removes trailing '=' characters. + /// Converts a byte array to Base64URL string with optional padding ('=') characters removed. /// Base64 description: https://datatracker.ietf.org/doc/html/rfc4648.html#section-4 /// public static string ToBase64Url(this byte[] bytes)