From aff48ad8623f010981984313f35e22ef235d8fcc Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Fri, 19 Sep 2025 17:19:07 +0300 Subject: [PATCH 01/12] Add schemeName property to MinimalPermissionsPlugin config --- DevProxy.Plugins/Reporting/MinimalPermissionsPlugin.cs | 1 + schemas/v1.2.0/minimalpermissionsplugin.schema.json | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/DevProxy.Plugins/Reporting/MinimalPermissionsPlugin.cs b/DevProxy.Plugins/Reporting/MinimalPermissionsPlugin.cs index d4134458..0bebc221 100644 --- a/DevProxy.Plugins/Reporting/MinimalPermissionsPlugin.cs +++ b/DevProxy.Plugins/Reporting/MinimalPermissionsPlugin.cs @@ -17,6 +17,7 @@ namespace DevProxy.Plugins.Reporting; public sealed class MinimalPermissionsPluginConfiguration { public string? ApiSpecsFolderPath { get; set; } + public string? SchemeName { get; set; } } public sealed class MinimalPermissionsPlugin( diff --git a/schemas/v1.2.0/minimalpermissionsplugin.schema.json b/schemas/v1.2.0/minimalpermissionsplugin.schema.json index e70bc69e..84d1d24d 100644 --- a/schemas/v1.2.0/minimalpermissionsplugin.schema.json +++ b/schemas/v1.2.0/minimalpermissionsplugin.schema.json @@ -10,6 +10,10 @@ "apiSpecsFolderPath": { "type": "string", "description": "Relative or absolute path to the folder with API specs. Used to determine minimal permissions required for API calls." + }, + "schemeName": { + "type": "string", + "description": "The name of the security scheme definition. Used to determine minimal permissions required for API calls." } }, "required": [ From 763b4f707a25aab10853b1d8949d20613657fd0e Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Fri, 19 Sep 2025 17:25:04 +0300 Subject: [PATCH 02/12] Overload CheckMinimalPermissions() with schemeName param --- .../Extensions/OpenApiDocumentExtensions.cs | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/DevProxy.Plugins/Extensions/OpenApiDocumentExtensions.cs b/DevProxy.Plugins/Extensions/OpenApiDocumentExtensions.cs index 9458db4d..e61b82c1 100644 --- a/DevProxy.Plugins/Extensions/OpenApiDocumentExtensions.cs +++ b/DevProxy.Plugins/Extensions/OpenApiDocumentExtensions.cs @@ -16,6 +16,11 @@ namespace Microsoft.OpenApi.Models; static class OpenApiDocumentExtensions { public static ApiPermissionsInfo CheckMinimalPermissions(this OpenApiDocument openApiDocument, IEnumerable requests, ILogger logger) + { + return CheckMinimalPermissions(openApiDocument, requests, logger, null); + } + + public static ApiPermissionsInfo CheckMinimalPermissions(this OpenApiDocument openApiDocument, IEnumerable requests, ILogger logger, string? schemeName) { logger.LogInformation("Checking minimal permissions for API {ApiName}...", openApiDocument.Servers.First().Url); @@ -78,7 +83,7 @@ public static ApiPermissionsInfo CheckMinimalPermissions(this OpenApiDocument op continue; } - var scopes = operation.GetEffectiveScopes(openApiDocument, logger); + var scopes = operation.GetEffectiveScopes(openApiDocument, logger, schemeName); if (scopes.Length != 0) { operationsAndScopes[$"{method} {pathItem.Value.Key}"] = scopes; @@ -176,12 +181,19 @@ [.. operationsFromRequests return null; } - public static string[] GetEffectiveScopes(this OpenApiOperation operation, OpenApiDocument openApiDocument, ILogger logger) + public static string[] GetEffectiveScopes(this OpenApiOperation operation, OpenApiDocument openApiDocument, ILogger logger, string? schemeName) { - var oauth2Scheme = openApiDocument.GetOAuth2Schemes().FirstOrDefault(); + var oauth2Scheme = openApiDocument.GetOAuth2Schemes(schemeName).FirstOrDefault(); if (oauth2Scheme is null) { - logger.LogDebug("No OAuth2 schemes found in OpenAPI document"); + if (string.IsNullOrWhiteSpace(schemeName)) + { + logger.LogDebug("No OAuth2 scheme '{SchemeName}' found in OpenAPI document", schemeName); + } + else + { + logger.LogDebug("No OAuth2 schemes found in OpenAPI document"); + } return []; } @@ -210,11 +222,17 @@ public static string[] GetEffectiveScopes(this OpenApiOperation operation, OpenA return []; } - public static OpenApiSecurityScheme[] GetOAuth2Schemes(this OpenApiDocument openApiDocument) + public static OpenApiSecurityScheme[] GetOAuth2Schemes(this OpenApiDocument openApiDocument, string? schemeName) { - return [.. openApiDocument.Components.SecuritySchemes - .Where(s => s.Value.Type == SecuritySchemeType.OAuth2) - .Select(s => s.Value)]; + var schemes = openApiDocument.Components.SecuritySchemes + .Where(s => s.Value.Type == SecuritySchemeType.OAuth2); + + if (!string.IsNullOrWhiteSpace(schemeName)) + { + schemes = schemes.Where(s => string.Equals(schemeName, s.Key, StringComparison.Ordinal)); + } + + return [.. schemes.Select(s => s.Value)]; } private static bool UrlMatchesServerUrl(string absoluteUrl, string serverUrl) From 50a062a06bc70596a6530a5d0ed103cae2b5464e Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Fri, 19 Sep 2025 17:26:12 +0300 Subject: [PATCH 03/12] Use schemeName param in MinimalPermissionsPlugin --- DevProxy.Plugins/Reporting/MinimalPermissionsPlugin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DevProxy.Plugins/Reporting/MinimalPermissionsPlugin.cs b/DevProxy.Plugins/Reporting/MinimalPermissionsPlugin.cs index 0bebc221..12153781 100644 --- a/DevProxy.Plugins/Reporting/MinimalPermissionsPlugin.cs +++ b/DevProxy.Plugins/Reporting/MinimalPermissionsPlugin.cs @@ -93,7 +93,7 @@ public override async Task AfterRecordingStopAsync(RecordingArgs e, Cancellation foreach (var (apiSpec, requests) in requestsByApiSpec) { - var minimalPermissions = apiSpec.CheckMinimalPermissions(requests, Logger); + var minimalPermissions = apiSpec.CheckMinimalPermissions(requests, Logger, Configuration.SchemeName); var result = new MinimalPermissionsPluginReportApiResult { From dbf4f2a18bc044ef403869e4420643dbcac3454b Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Fri, 19 Sep 2025 17:27:02 +0300 Subject: [PATCH 04/12] Add logging of the scheme name for minimal permission --- DevProxy.Plugins/Reporting/MinimalPermissionsPlugin.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/DevProxy.Plugins/Reporting/MinimalPermissionsPlugin.cs b/DevProxy.Plugins/Reporting/MinimalPermissionsPlugin.cs index 12153781..00866977 100644 --- a/DevProxy.Plugins/Reporting/MinimalPermissionsPlugin.cs +++ b/DevProxy.Plugins/Reporting/MinimalPermissionsPlugin.cs @@ -133,7 +133,15 @@ public override async Task AfterRecordingStopAsync(RecordingArgs e, Cancellation ); } - Logger.LogInformation("Minimal permissions: {MinimalScopes}", string.Join(", ", result.MinimalPermissions)); + if (string.IsNullOrWhiteSpace(Configuration.SchemeName)) + { + Logger.LogInformation("Minimal permissions: {MinimalScopes}", string.Join(", ", result.MinimalPermissions)); + } + else + { + Logger.LogInformation("Minimal permissions of '{SchemeName}' scheme: {MinimalScopes}", + Configuration.SchemeName, string.Join(", ", result.MinimalPermissions)); + } } var report = new MinimalPermissionsPluginReport() From 46075d20ffe3249a6b824af79932054ecdc27d12 Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Fri, 19 Sep 2025 17:45:57 +0300 Subject: [PATCH 05/12] Add schema name to MinimalPermissionsPluginReport --- .../Reporting/MinimalPermissionsPlugin.cs | 5 +-- .../MinimalPermissionsPluginReport.cs | 32 +++++++++++++++---- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/DevProxy.Plugins/Reporting/MinimalPermissionsPlugin.cs b/DevProxy.Plugins/Reporting/MinimalPermissionsPlugin.cs index 00866977..87464525 100644 --- a/DevProxy.Plugins/Reporting/MinimalPermissionsPlugin.cs +++ b/DevProxy.Plugins/Reporting/MinimalPermissionsPlugin.cs @@ -103,7 +103,8 @@ public override async Task AfterRecordingStopAsync(RecordingArgs e, Cancellation .Select(o => $"{o.Method} {o.OriginalUrl}") .Distinct()], TokenPermissions = [.. minimalPermissions.TokenPermissions.Distinct()], - MinimalPermissions = minimalPermissions.MinimalScopes + MinimalPermissions = minimalPermissions.MinimalScopes, + SchemeName = Configuration.SchemeName }; results.Add(result); @@ -148,7 +149,7 @@ public override async Task AfterRecordingStopAsync(RecordingArgs e, Cancellation { Results = [.. results], UnmatchedRequests = [.. unmatchedRequests], - Errors = [.. errors] + Errors = [.. errors], }; StoreReport(report, e); diff --git a/DevProxy.Plugins/Reporting/MinimalPermissionsPluginReport.cs b/DevProxy.Plugins/Reporting/MinimalPermissionsPluginReport.cs index c42c8e50..71f86f94 100644 --- a/DevProxy.Plugins/Reporting/MinimalPermissionsPluginReport.cs +++ b/DevProxy.Plugins/Reporting/MinimalPermissionsPluginReport.cs @@ -15,6 +15,7 @@ public sealed class MinimalPermissionsPluginReportApiResult public required IEnumerable MinimalPermissions { get; init; } public required IEnumerable Requests { get; init; } public required IEnumerable TokenPermissions { get; init; } + public string? SchemeName { get; init; } } public sealed class MinimalPermissionsPluginReport : IMarkdownReport, IPlainTextReport @@ -26,7 +27,7 @@ public sealed class MinimalPermissionsPluginReport : IMarkdownReport, IPlainText public string? ToMarkdown() { var sb = new StringBuilder(); - _ = sb.AppendLine($"# Minimal permissions report"); + _ = sb.AppendLine("# Minimal permissions report"); foreach (var apiResult in Results) { @@ -36,9 +37,18 @@ public sealed class MinimalPermissionsPluginReport : IMarkdownReport, IPlainText .AppendLine("### Requests") .AppendLine() .AppendJoin(Environment.NewLine, apiResult.Requests.Select(r => $"- {r}")) - .AppendLine() + .AppendLine(); - .AppendLine() + if (!string.IsNullOrWhiteSpace(apiResult.SchemeName)) + { + _ = sb.AppendLine() + .AppendLine("### Scheme definition name") + .AppendLine() + .AppendLine(CultureInfo.InvariantCulture, $"- {apiResult.SchemeName}") + .AppendLine(); + } + + _ = sb.AppendLine() .AppendLine("### Minimal permissions") .AppendLine() .AppendJoin(Environment.NewLine, apiResult.MinimalPermissions.Select(p => $"- {p}")) @@ -65,7 +75,7 @@ public sealed class MinimalPermissionsPluginReport : IMarkdownReport, IPlainText { var sb = new StringBuilder(); - _ = sb.AppendLine($"Minimal permissions report"); + _ = sb.AppendLine("Minimal permissions report"); foreach (var apiResult in Results) { @@ -75,8 +85,18 @@ public sealed class MinimalPermissionsPluginReport : IMarkdownReport, IPlainText .AppendLine("Requests:") .AppendLine() .AppendJoin(Environment.NewLine, apiResult.Requests.Select(r => $"- {r}")) - .AppendLine() - .AppendLine() + .AppendLine(); + + if (!string.IsNullOrWhiteSpace(apiResult.SchemeName)) + { + _ = sb.AppendLine() + .AppendLine("Scheme definition name:") + .AppendLine() + .AppendLine(CultureInfo.InvariantCulture, $"- {apiResult.SchemeName}") + .AppendLine(); + } + + _ = sb.AppendLine() .AppendLine("Minimal permissions:") .AppendLine() .AppendJoin(Environment.NewLine, apiResult.MinimalPermissions.Select(p => $"- {p}")); From 7e8eca9e4de62a1882ecc4a9633b86bf8b2ebc1a Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Mon, 29 Sep 2025 09:55:03 +0300 Subject: [PATCH 06/12] Fix debug loggin message Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- DevProxy.Plugins/Extensions/OpenApiDocumentExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DevProxy.Plugins/Extensions/OpenApiDocumentExtensions.cs b/DevProxy.Plugins/Extensions/OpenApiDocumentExtensions.cs index e61b82c1..3fdf27da 100644 --- a/DevProxy.Plugins/Extensions/OpenApiDocumentExtensions.cs +++ b/DevProxy.Plugins/Extensions/OpenApiDocumentExtensions.cs @@ -188,11 +188,11 @@ public static string[] GetEffectiveScopes(this OpenApiOperation operation, OpenA { if (string.IsNullOrWhiteSpace(schemeName)) { - logger.LogDebug("No OAuth2 scheme '{SchemeName}' found in OpenAPI document", schemeName); + logger.LogDebug("No OAuth2 schemes found in OpenAPI document"); } else { - logger.LogDebug("No OAuth2 schemes found in OpenAPI document"); + logger.LogDebug("No OAuth2 scheme '{SchemeName}' found in OpenAPI document", schemeName); } return []; } From cf5e294806beb8059c6e358fb2b0500ab6feac48 Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Mon, 29 Sep 2025 10:25:08 +0300 Subject: [PATCH 07/12] fix: Add name check to main where-clause --- DevProxy.Plugins/Extensions/OpenApiDocumentExtensions.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/DevProxy.Plugins/Extensions/OpenApiDocumentExtensions.cs b/DevProxy.Plugins/Extensions/OpenApiDocumentExtensions.cs index 3fdf27da..7e9ebdad 100644 --- a/DevProxy.Plugins/Extensions/OpenApiDocumentExtensions.cs +++ b/DevProxy.Plugins/Extensions/OpenApiDocumentExtensions.cs @@ -225,12 +225,8 @@ public static string[] GetEffectiveScopes(this OpenApiOperation operation, OpenA public static OpenApiSecurityScheme[] GetOAuth2Schemes(this OpenApiDocument openApiDocument, string? schemeName) { var schemes = openApiDocument.Components.SecuritySchemes - .Where(s => s.Value.Type == SecuritySchemeType.OAuth2); - - if (!string.IsNullOrWhiteSpace(schemeName)) - { - schemes = schemes.Where(s => string.Equals(schemeName, s.Key, StringComparison.Ordinal)); - } + .Where(s => s.Value.Type == SecuritySchemeType.OAuth2 + && (string.IsNullOrWhiteSpace(schemeName) || string.Equals(schemeName, s.Key, StringComparison.Ordinal))); return [.. schemes.Select(s => s.Value)]; } From 304b63aae81dda8ad2d0114db01e576b5cf533ab Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Mon, 29 Sep 2025 10:26:41 +0300 Subject: [PATCH 08/12] Remove dangling comma --- DevProxy.Plugins/Reporting/MinimalPermissionsPlugin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DevProxy.Plugins/Reporting/MinimalPermissionsPlugin.cs b/DevProxy.Plugins/Reporting/MinimalPermissionsPlugin.cs index 87464525..d3d8c760 100644 --- a/DevProxy.Plugins/Reporting/MinimalPermissionsPlugin.cs +++ b/DevProxy.Plugins/Reporting/MinimalPermissionsPlugin.cs @@ -149,7 +149,7 @@ public override async Task AfterRecordingStopAsync(RecordingArgs e, Cancellation { Results = [.. results], UnmatchedRequests = [.. unmatchedRequests], - Errors = [.. errors], + Errors = [.. errors] }; StoreReport(report, e); From c418416b616e5cc45e4a5330460d2e2db5c3e6df Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Mon, 29 Sep 2025 10:45:50 +0300 Subject: [PATCH 09/12] fix: Include scheme name into permissions header in report --- .../MinimalPermissionsPluginReport.cs | 26 +++++-------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/DevProxy.Plugins/Reporting/MinimalPermissionsPluginReport.cs b/DevProxy.Plugins/Reporting/MinimalPermissionsPluginReport.cs index 71f86f94..8f46acb8 100644 --- a/DevProxy.Plugins/Reporting/MinimalPermissionsPluginReport.cs +++ b/DevProxy.Plugins/Reporting/MinimalPermissionsPluginReport.cs @@ -39,17 +39,10 @@ public sealed class MinimalPermissionsPluginReport : IMarkdownReport, IPlainText .AppendJoin(Environment.NewLine, apiResult.Requests.Select(r => $"- {r}")) .AppendLine(); - if (!string.IsNullOrWhiteSpace(apiResult.SchemeName)) - { - _ = sb.AppendLine() - .AppendLine("### Scheme definition name") - .AppendLine() - .AppendLine(CultureInfo.InvariantCulture, $"- {apiResult.SchemeName}") - .AppendLine(); - } - + var permissionsHeader = "### Minimal permissions" + (string.IsNullOrWhiteSpace(apiResult.SchemeName) + ? "" : $" for {apiResult.SchemeName} scheme"); _ = sb.AppendLine() - .AppendLine("### Minimal permissions") + .AppendLine(permissionsHeader) .AppendLine() .AppendJoin(Environment.NewLine, apiResult.MinimalPermissions.Select(p => $"- {p}")) .AppendLine(); @@ -87,17 +80,10 @@ public sealed class MinimalPermissionsPluginReport : IMarkdownReport, IPlainText .AppendJoin(Environment.NewLine, apiResult.Requests.Select(r => $"- {r}")) .AppendLine(); - if (!string.IsNullOrWhiteSpace(apiResult.SchemeName)) - { - _ = sb.AppendLine() - .AppendLine("Scheme definition name:") - .AppendLine() - .AppendLine(CultureInfo.InvariantCulture, $"- {apiResult.SchemeName}") - .AppendLine(); - } - + var permissionsHeader = "Minimal permissions" + (string.IsNullOrWhiteSpace(apiResult.SchemeName) + ? "" : $" for {apiResult.SchemeName} scheme") + ":"; _ = sb.AppendLine() - .AppendLine("Minimal permissions:") + .AppendLine(permissionsHeader) .AppendLine() .AppendJoin(Environment.NewLine, apiResult.MinimalPermissions.Select(p => $"- {p}")); } From bc178ead3a289c615540f14c110f9de43f62c9d2 Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Mon, 29 Sep 2025 10:55:23 +0300 Subject: [PATCH 10/12] Fix logging message --- DevProxy.Plugins/Extensions/OpenApiDocumentExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DevProxy.Plugins/Extensions/OpenApiDocumentExtensions.cs b/DevProxy.Plugins/Extensions/OpenApiDocumentExtensions.cs index 7e9ebdad..17c828c2 100644 --- a/DevProxy.Plugins/Extensions/OpenApiDocumentExtensions.cs +++ b/DevProxy.Plugins/Extensions/OpenApiDocumentExtensions.cs @@ -192,7 +192,7 @@ public static string[] GetEffectiveScopes(this OpenApiOperation operation, OpenA } else { - logger.LogDebug("No OAuth2 scheme '{SchemeName}' found in OpenAPI document", schemeName); + logger.LogDebug("No OAuth2 '{SchemeName}' scheme found in OpenAPI document", schemeName); } return []; } From fe41dd6dde15d377ffa76e4cbc1cc61f12d5ecc1 Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Thu, 2 Oct 2025 12:14:56 +0300 Subject: [PATCH 11/12] fix: Add optional param to CheckMinimalPermissions and remove unnecessary method --- DevProxy.Plugins/Extensions/OpenApiDocumentExtensions.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/DevProxy.Plugins/Extensions/OpenApiDocumentExtensions.cs b/DevProxy.Plugins/Extensions/OpenApiDocumentExtensions.cs index 17c828c2..a01800b4 100644 --- a/DevProxy.Plugins/Extensions/OpenApiDocumentExtensions.cs +++ b/DevProxy.Plugins/Extensions/OpenApiDocumentExtensions.cs @@ -15,12 +15,8 @@ namespace Microsoft.OpenApi.Models; static class OpenApiDocumentExtensions { - public static ApiPermissionsInfo CheckMinimalPermissions(this OpenApiDocument openApiDocument, IEnumerable requests, ILogger logger) - { - return CheckMinimalPermissions(openApiDocument, requests, logger, null); - } - - public static ApiPermissionsInfo CheckMinimalPermissions(this OpenApiDocument openApiDocument, IEnumerable requests, ILogger logger, string? schemeName) + public static ApiPermissionsInfo CheckMinimalPermissions(this OpenApiDocument openApiDocument, IEnumerable requests, + ILogger logger, string? schemeName = default) { logger.LogInformation("Checking minimal permissions for API {ApiName}...", openApiDocument.Servers.First().Url); From 757ab7bb935ae629b264622d2ebf7eb2ab9100ab Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Thu, 2 Oct 2025 12:29:09 +0300 Subject: [PATCH 12/12] Add schemeName property to MinimalPermissionsPlugin config v1.3 and remove from v1.2 --- schemas/v1.2.0/minimalpermissionsplugin.schema.json | 4 ---- schemas/v1.3.0/minimalpermissionsplugin.schema.json | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/schemas/v1.2.0/minimalpermissionsplugin.schema.json b/schemas/v1.2.0/minimalpermissionsplugin.schema.json index 84d1d24d..e70bc69e 100644 --- a/schemas/v1.2.0/minimalpermissionsplugin.schema.json +++ b/schemas/v1.2.0/minimalpermissionsplugin.schema.json @@ -10,10 +10,6 @@ "apiSpecsFolderPath": { "type": "string", "description": "Relative or absolute path to the folder with API specs. Used to determine minimal permissions required for API calls." - }, - "schemeName": { - "type": "string", - "description": "The name of the security scheme definition. Used to determine minimal permissions required for API calls." } }, "required": [ diff --git a/schemas/v1.3.0/minimalpermissionsplugin.schema.json b/schemas/v1.3.0/minimalpermissionsplugin.schema.json index e70bc69e..84d1d24d 100644 --- a/schemas/v1.3.0/minimalpermissionsplugin.schema.json +++ b/schemas/v1.3.0/minimalpermissionsplugin.schema.json @@ -10,6 +10,10 @@ "apiSpecsFolderPath": { "type": "string", "description": "Relative or absolute path to the folder with API specs. Used to determine minimal permissions required for API calls." + }, + "schemeName": { + "type": "string", + "description": "The name of the security scheme definition. Used to determine minimal permissions required for API calls." } }, "required": [