Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 19 additions & 9 deletions DevProxy.Plugins/Extensions/OpenApiDocumentExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ namespace Microsoft.OpenApi.Models;

static class OpenApiDocumentExtensions
{
public static ApiPermissionsInfo CheckMinimalPermissions(this OpenApiDocument openApiDocument, IEnumerable<RequestLog> requests, ILogger logger)
public static ApiPermissionsInfo CheckMinimalPermissions(this OpenApiDocument openApiDocument, IEnumerable<RequestLog> requests,
ILogger logger, string? schemeName = default)
{
logger.LogInformation("Checking minimal permissions for API {ApiName}...", openApiDocument.Servers.First().Url);

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 [];
}

Expand Down Expand Up @@ -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)
Expand Down
16 changes: 13 additions & 3 deletions DevProxy.Plugins/Reporting/MinimalPermissionsPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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
{
Expand All @@ -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);

Expand Down Expand Up @@ -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()
Expand Down
22 changes: 14 additions & 8 deletions DevProxy.Plugins/Reporting/MinimalPermissionsPluginReport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public sealed class MinimalPermissionsPluginReportApiResult
public required IEnumerable<string> MinimalPermissions { get; init; }
public required IEnumerable<string> Requests { get; init; }
public required IEnumerable<string> TokenPermissions { get; init; }
public string? SchemeName { get; init; }
}

public sealed class MinimalPermissionsPluginReport : IMarkdownReport, IPlainTextReport
Expand All @@ -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)
{
Expand All @@ -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();
Expand All @@ -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)
{
Expand All @@ -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}"));
}
Expand Down
4 changes: 4 additions & 0 deletions schemas/v1.3.0/minimalpermissionsplugin.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down