From 5365417cf5eb37bfa144188ab5b4da5a29b628bd Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Fri, 25 Jul 2025 17:57:00 +0300 Subject: [PATCH 1/9] adjustments --- DevProxy.Plugins/Extensions/StringExtensions.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/DevProxy.Plugins/Extensions/StringExtensions.cs b/DevProxy.Plugins/Extensions/StringExtensions.cs index 780b83b4..3f0c713c 100644 --- a/DevProxy.Plugins/Extensions/StringExtensions.cs +++ b/DevProxy.Plugins/Extensions/StringExtensions.cs @@ -67,11 +67,9 @@ internal static string ToCamelCase(this string str) { if (string.IsNullOrEmpty(str)) { - return str; } - return char.ToLowerInvariant(str[0]) + str[1..]; } From de0ebe5f2615ec6ec754a70ee1c40cf129865d24 Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Fri, 25 Jul 2025 17:59:22 +0300 Subject: [PATCH 2/9] Add function to define the list based on config key presence and provided default --- DevProxy.Abstractions/Plugins/BasePlugin.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/DevProxy.Abstractions/Plugins/BasePlugin.cs b/DevProxy.Abstractions/Plugins/BasePlugin.cs index c440be55..36d7227c 100644 --- a/DevProxy.Abstractions/Plugins/BasePlugin.cs +++ b/DevProxy.Abstractions/Plugins/BasePlugin.cs @@ -99,6 +99,15 @@ public TConfiguration Configuration } public IConfigurationSection ConfigurationSection { get; } = pluginConfigurationSection; + protected virtual IEnumerable? GetConfigurationValue(string key, IEnumerable? list, IEnumerable? defaultList = default) + { + if (key is null) { return null; } + + var keyExists = ConfigurationSection.GetChildren().Any(f => string.Equals(key, f.Key, StringComparison.Ordinal)); + list = list?.Where(static p => !string.IsNullOrEmpty(p)).ToArray(); + return keyExists ? list ?? [] : defaultList; + } + public virtual void Register(IServiceCollection services, TConfiguration configuration) { } From f5785fcb04efd839d3fae4cfadbd0a2b7351a644 Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Fri, 25 Jul 2025 18:05:40 +0300 Subject: [PATCH 3/9] Add method to initialize permissions --- .../GraphMinimalPermissionsGuidancePlugin.cs | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/DevProxy.Plugins/Reporting/GraphMinimalPermissionsGuidancePlugin.cs b/DevProxy.Plugins/Reporting/GraphMinimalPermissionsGuidancePlugin.cs index dc11842a..0eb2a3df 100644 --- a/DevProxy.Plugins/Reporting/GraphMinimalPermissionsGuidancePlugin.cs +++ b/DevProxy.Plugins/Reporting/GraphMinimalPermissionsGuidancePlugin.cs @@ -61,20 +61,16 @@ public override async Task InitializeAsync(InitArgs e, CancellationToken cancell _graphUtils = ActivatorUtilities.CreateInstance(e.ServiceProvider); - // we need to do it this way because .NET doesn't distinguish between - // an empty array and a null value and we want to be able to tell - // if the user hasn't specified a value and we should use the default - // set or if they have specified an empty array and we shouldn't exclude - // any permissions - if (Configuration.PermissionsToExclude is null) - { - Configuration.PermissionsToExclude = ["profile", "openid", "offline_access", "email"]; - } - else - { - // remove empty strings - Configuration.PermissionsToExclude = Configuration.PermissionsToExclude.Where(p => !string.IsNullOrEmpty(p)); - } + InitializePermissionsToExclude(); + } + + private void InitializePermissionsToExclude() + { + var key = nameof(GraphMinimalPermissionsGuidancePluginConfiguration.PermissionsToExclude) + .ToCamelCase(); + + string[] defaultPermissionsToExclude = ["profile", "openid", "offline_access", "email"]; + Configuration.PermissionsToExclude = GetConfigurationValue(key, Configuration.PermissionsToExclude, defaultPermissionsToExclude); } public override async Task AfterRecordingStopAsync(RecordingArgs e, CancellationToken cancellationToken) From 7dc1abd45faeea590f4313eaa702943e76990bb1 Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Fri, 25 Jul 2025 18:06:44 +0300 Subject: [PATCH 4/9] Set initial PermissionsToExclude property to empty array --- .../Reporting/GraphMinimalPermissionsGuidancePlugin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DevProxy.Plugins/Reporting/GraphMinimalPermissionsGuidancePlugin.cs b/DevProxy.Plugins/Reporting/GraphMinimalPermissionsGuidancePlugin.cs index 0eb2a3df..3e93ae1a 100644 --- a/DevProxy.Plugins/Reporting/GraphMinimalPermissionsGuidancePlugin.cs +++ b/DevProxy.Plugins/Reporting/GraphMinimalPermissionsGuidancePlugin.cs @@ -33,7 +33,7 @@ public sealed class GraphMinimalPermissionsInfo public sealed class GraphMinimalPermissionsGuidancePluginConfiguration { - public IEnumerable? PermissionsToExclude { get; set; } + public IEnumerable? PermissionsToExclude { get; set; } = []; } public sealed class GraphMinimalPermissionsGuidancePlugin( From 477b00e97df542fd7c15e937396924ea418598bd Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Mon, 4 Aug 2025 11:41:11 +0300 Subject: [PATCH 5/9] fix: Throw ArgumentNullException if null --- DevProxy.Abstractions/Plugins/BasePlugin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DevProxy.Abstractions/Plugins/BasePlugin.cs b/DevProxy.Abstractions/Plugins/BasePlugin.cs index 36d7227c..8a1e076a 100644 --- a/DevProxy.Abstractions/Plugins/BasePlugin.cs +++ b/DevProxy.Abstractions/Plugins/BasePlugin.cs @@ -101,7 +101,7 @@ public TConfiguration Configuration protected virtual IEnumerable? GetConfigurationValue(string key, IEnumerable? list, IEnumerable? defaultList = default) { - if (key is null) { return null; } + ArgumentNullException.ThrowIfNull(key, nameof(key)); var keyExists = ConfigurationSection.GetChildren().Any(f => string.Equals(key, f.Key, StringComparison.Ordinal)); list = list?.Where(static p => !string.IsNullOrEmpty(p)).ToArray(); From c35a1ddb10d5331e889f4031eb92a0f5cd5a73a4 Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Mon, 4 Aug 2025 11:42:33 +0300 Subject: [PATCH 6/9] fix: Rename list to configuredList --- DevProxy.Abstractions/Plugins/BasePlugin.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DevProxy.Abstractions/Plugins/BasePlugin.cs b/DevProxy.Abstractions/Plugins/BasePlugin.cs index 8a1e076a..1843db4f 100644 --- a/DevProxy.Abstractions/Plugins/BasePlugin.cs +++ b/DevProxy.Abstractions/Plugins/BasePlugin.cs @@ -99,13 +99,13 @@ public TConfiguration Configuration } public IConfigurationSection ConfigurationSection { get; } = pluginConfigurationSection; - protected virtual IEnumerable? GetConfigurationValue(string key, IEnumerable? list, IEnumerable? defaultList = default) + protected virtual IEnumerable? GetConfigurationValue(string key, IEnumerable? configuredList, IEnumerable? defaultList = default) { ArgumentNullException.ThrowIfNull(key, nameof(key)); var keyExists = ConfigurationSection.GetChildren().Any(f => string.Equals(key, f.Key, StringComparison.Ordinal)); - list = list?.Where(static p => !string.IsNullOrEmpty(p)).ToArray(); - return keyExists ? list ?? [] : defaultList; + configuredList = configuredList?.Where(static p => !string.IsNullOrEmpty(p)).ToArray(); + return keyExists ? configuredList ?? [] : defaultList; } public virtual void Register(IServiceCollection services, TConfiguration configuration) From 4baa745bcd675d8f7c691c1140972517b7a20e0f Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Mon, 4 Aug 2025 11:58:59 +0300 Subject: [PATCH 7/9] fix: Revert intial value of PermissionsToExclude to null --- .../Reporting/GraphMinimalPermissionsGuidancePlugin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DevProxy.Plugins/Reporting/GraphMinimalPermissionsGuidancePlugin.cs b/DevProxy.Plugins/Reporting/GraphMinimalPermissionsGuidancePlugin.cs index 3e93ae1a..0eb2a3df 100644 --- a/DevProxy.Plugins/Reporting/GraphMinimalPermissionsGuidancePlugin.cs +++ b/DevProxy.Plugins/Reporting/GraphMinimalPermissionsGuidancePlugin.cs @@ -33,7 +33,7 @@ public sealed class GraphMinimalPermissionsInfo public sealed class GraphMinimalPermissionsGuidancePluginConfiguration { - public IEnumerable? PermissionsToExclude { get; set; } = []; + public IEnumerable? PermissionsToExclude { get; set; } } public sealed class GraphMinimalPermissionsGuidancePlugin( From 3dee7854fc60f06f6d27b6c84ebf40db36997bda Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Mon, 4 Aug 2025 13:25:50 +0300 Subject: [PATCH 8/9] fix: Add explanatory comment --- DevProxy.Abstractions/Plugins/BasePlugin.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/DevProxy.Abstractions/Plugins/BasePlugin.cs b/DevProxy.Abstractions/Plugins/BasePlugin.cs index 1843db4f..e3b49109 100644 --- a/DevProxy.Abstractions/Plugins/BasePlugin.cs +++ b/DevProxy.Abstractions/Plugins/BasePlugin.cs @@ -99,7 +99,22 @@ public TConfiguration Configuration } public IConfigurationSection ConfigurationSection { get; } = pluginConfigurationSection; - protected virtual IEnumerable? GetConfigurationValue(string key, IEnumerable? configuredList, IEnumerable? defaultList = default) + /// + /// Evaluates the array property. + /// If the property exists, the value is used; + /// otherwise, the default is applied. + /// If the property is null, it is interpreted as an empty array ([]). + /// Note: This is necessary because .NET configuration binding cannot differentiate between an empty array, + /// a null value, or a missing property in appsettings.json. + /// See at + /// + /// + /// The array property name + /// The configured list of string values + /// The default list of string values + /// Returns the result list of string values + protected virtual IEnumerable? GetConfigurationValue(string key, IEnumerable? configuredList, + IEnumerable? defaultList = default) { ArgumentNullException.ThrowIfNull(key, nameof(key)); From 000ff015b35bc99146237d5f0937d463fa7f38f6 Mon Sep 17 00:00:00 2001 From: Waldek Mastykarz Date: Tue, 5 Aug 2025 08:46:48 +0200 Subject: [PATCH 9/9] Cosmetic updates --- DevProxy.Abstractions/Plugins/BasePlugin.cs | 32 +++++++++---------- .../GraphMinimalPermissionsGuidancePlugin.cs | 18 +++++------ 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/DevProxy.Abstractions/Plugins/BasePlugin.cs b/DevProxy.Abstractions/Plugins/BasePlugin.cs index e3b49109..d093f9da 100644 --- a/DevProxy.Abstractions/Plugins/BasePlugin.cs +++ b/DevProxy.Abstractions/Plugins/BasePlugin.cs @@ -99,6 +99,21 @@ public TConfiguration Configuration } public IConfigurationSection ConfigurationSection { get; } = pluginConfigurationSection; + public virtual void Register(IServiceCollection services, TConfiguration configuration) + { + } + + public override async Task InitializeAsync(InitArgs e, CancellationToken cancellationToken) + { + await base.InitializeAsync(e, cancellationToken); + + var (IsValid, ValidationErrors) = await ValidatePluginConfigAsync(cancellationToken); + if (!IsValid) + { + Logger.LogError("Plugin configuration validation failed with the following errors: {Errors}", string.Join(", ", ValidationErrors)); + } + } + /// /// Evaluates the array property. /// If the property exists, the value is used; @@ -119,25 +134,10 @@ public TConfiguration Configuration ArgumentNullException.ThrowIfNull(key, nameof(key)); var keyExists = ConfigurationSection.GetChildren().Any(f => string.Equals(key, f.Key, StringComparison.Ordinal)); - configuredList = configuredList?.Where(static p => !string.IsNullOrEmpty(p)).ToArray(); + configuredList = configuredList?.Where(static p => !string.IsNullOrEmpty(p)); return keyExists ? configuredList ?? [] : defaultList; } - public virtual void Register(IServiceCollection services, TConfiguration configuration) - { - } - - public override async Task InitializeAsync(InitArgs e, CancellationToken cancellationToken) - { - await base.InitializeAsync(e, cancellationToken); - - var (IsValid, ValidationErrors) = await ValidatePluginConfigAsync(cancellationToken); - if (!IsValid) - { - Logger.LogError("Plugin configuration validation failed with the following errors: {Errors}", string.Join(", ", ValidationErrors)); - } - } - private async Task<(bool IsValid, IEnumerable ValidationErrors)> ValidatePluginConfigAsync(CancellationToken cancellationToken) { if (!ProxyConfiguration.ValidateSchemas) diff --git a/DevProxy.Plugins/Reporting/GraphMinimalPermissionsGuidancePlugin.cs b/DevProxy.Plugins/Reporting/GraphMinimalPermissionsGuidancePlugin.cs index 0eb2a3df..ca88be18 100644 --- a/DevProxy.Plugins/Reporting/GraphMinimalPermissionsGuidancePlugin.cs +++ b/DevProxy.Plugins/Reporting/GraphMinimalPermissionsGuidancePlugin.cs @@ -64,15 +64,6 @@ public override async Task InitializeAsync(InitArgs e, CancellationToken cancell InitializePermissionsToExclude(); } - private void InitializePermissionsToExclude() - { - var key = nameof(GraphMinimalPermissionsGuidancePluginConfiguration.PermissionsToExclude) - .ToCamelCase(); - - string[] defaultPermissionsToExclude = ["profile", "openid", "offline_access", "email"]; - Configuration.PermissionsToExclude = GetConfigurationValue(key, Configuration.PermissionsToExclude, defaultPermissionsToExclude); - } - public override async Task AfterRecordingStopAsync(RecordingArgs e, CancellationToken cancellationToken) { Logger.LogTrace("{Method} called", nameof(AfterRecordingStopAsync)); @@ -216,6 +207,15 @@ public override async Task AfterRecordingStopAsync(RecordingArgs e, Cancellation Logger.LogTrace("Left {Name}", nameof(AfterRecordingStopAsync)); } + private void InitializePermissionsToExclude() + { + var key = nameof(GraphMinimalPermissionsGuidancePluginConfiguration.PermissionsToExclude) + .ToCamelCase(); + + string[] defaultPermissionsToExclude = ["profile", "openid", "offline_access", "email"]; + Configuration.PermissionsToExclude = GetConfigurationValue(key, Configuration.PermissionsToExclude, defaultPermissionsToExclude); + } + private async Task EvaluateMinimalScopesAsync( IEnumerable<(string method, string url)> endpoints, IEnumerable permissionsFromAccessToken,