diff --git a/DevProxy.Abstractions/Plugins/BasePlugin.cs b/DevProxy.Abstractions/Plugins/BasePlugin.cs index c440be55..d093f9da 100644 --- a/DevProxy.Abstractions/Plugins/BasePlugin.cs +++ b/DevProxy.Abstractions/Plugins/BasePlugin.cs @@ -114,6 +114,30 @@ public override async Task InitializeAsync(InitArgs e, CancellationToken cancell } } + /// + /// 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)); + + var keyExists = ConfigurationSection.GetChildren().Any(f => string.Equals(key, f.Key, StringComparison.Ordinal)); + configuredList = configuredList?.Where(static p => !string.IsNullOrEmpty(p)); + return keyExists ? configuredList ?? [] : defaultList; + } + private async Task<(bool IsValid, IEnumerable ValidationErrors)> ValidatePluginConfigAsync(CancellationToken cancellationToken) { if (!ProxyConfiguration.ValidateSchemas) 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..]; } diff --git a/DevProxy.Plugins/Reporting/GraphMinimalPermissionsGuidancePlugin.cs b/DevProxy.Plugins/Reporting/GraphMinimalPermissionsGuidancePlugin.cs index dc11842a..ca88be18 100644 --- a/DevProxy.Plugins/Reporting/GraphMinimalPermissionsGuidancePlugin.cs +++ b/DevProxy.Plugins/Reporting/GraphMinimalPermissionsGuidancePlugin.cs @@ -61,20 +61,7 @@ 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(); } public override async Task AfterRecordingStopAsync(RecordingArgs e, CancellationToken cancellationToken) @@ -220,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,