diff --git a/DevProxy.Plugins/Extensions/OpenApiDocumentExtensions.cs b/DevProxy.Plugins/Extensions/OpenApiDocumentExtensions.cs index 9458db4d..a01800b4 100644 --- a/DevProxy.Plugins/Extensions/OpenApiDocumentExtensions.cs +++ b/DevProxy.Plugins/Extensions/OpenApiDocumentExtensions.cs @@ -15,7 +15,8 @@ namespace Microsoft.OpenApi.Models; static class OpenApiDocumentExtensions { - public static ApiPermissionsInfo CheckMinimalPermissions(this OpenApiDocument openApiDocument, IEnumerable requests, ILogger logger) + 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); @@ -78,7 +79,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 +177,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 schemes found in OpenAPI document"); + } + else + { + logger.LogDebug("No OAuth2 '{SchemeName}' scheme found in OpenAPI document", schemeName); + } return []; } @@ -210,11 +218,13 @@ 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 + && (string.IsNullOrWhiteSpace(schemeName) || string.Equals(schemeName, s.Key, StringComparison.Ordinal))); + + return [.. schemes.Select(s => s.Value)]; } private static bool UrlMatchesServerUrl(string absoluteUrl, string serverUrl) diff --git a/DevProxy.Plugins/Reporting/MinimalPermissionsPlugin.cs b/DevProxy.Plugins/Reporting/MinimalPermissionsPlugin.cs index d4134458..d3d8c760 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( @@ -92,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 { @@ -102,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); @@ -132,7 +134,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() diff --git a/DevProxy.Plugins/Reporting/MinimalPermissionsPluginReport.cs b/DevProxy.Plugins/Reporting/MinimalPermissionsPluginReport.cs index c42c8e50..8f46acb8 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,10 +37,12 @@ public sealed class MinimalPermissionsPluginReport : IMarkdownReport, IPlainText .AppendLine("### Requests") .AppendLine() .AppendJoin(Environment.NewLine, apiResult.Requests.Select(r => $"- {r}")) - .AppendLine() + .AppendLine(); - .AppendLine() - .AppendLine("### Minimal permissions") + var permissionsHeader = "### Minimal permissions" + (string.IsNullOrWhiteSpace(apiResult.SchemeName) + ? "" : $" for {apiResult.SchemeName} scheme"); + _ = sb.AppendLine() + .AppendLine(permissionsHeader) .AppendLine() .AppendJoin(Environment.NewLine, apiResult.MinimalPermissions.Select(p => $"- {p}")) .AppendLine(); @@ -65,7 +68,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,9 +78,12 @@ public sealed class MinimalPermissionsPluginReport : IMarkdownReport, IPlainText .AppendLine("Requests:") .AppendLine() .AppendJoin(Environment.NewLine, apiResult.Requests.Select(r => $"- {r}")) - .AppendLine() - .AppendLine() - .AppendLine("Minimal permissions:") + .AppendLine(); + + var permissionsHeader = "Minimal permissions" + (string.IsNullOrWhiteSpace(apiResult.SchemeName) + ? "" : $" for {apiResult.SchemeName} scheme") + ":"; + _ = sb.AppendLine() + .AppendLine(permissionsHeader) .AppendLine() .AppendJoin(Environment.NewLine, apiResult.MinimalPermissions.Select(p => $"- {p}")); } 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": [