Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
53 changes: 40 additions & 13 deletions DevProxy.Plugins/Reporting/MinimalPermissionsGuidancePlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public sealed class MinimalPermissionsGuidancePluginConfiguration
{
public string? ApiSpecsFolderPath { get; set; }
public IEnumerable<string>? PermissionsToExclude { get; set; }
public string? SchemeName { get; set; }
}

public sealed class MinimalPermissionsGuidancePlugin(
Expand Down Expand Up @@ -83,7 +84,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);

IEnumerable<string> excessivePermissions = [.. minimalPermissions.TokenPermissions
.Except(Configuration.PermissionsToExclude ?? [])
Expand All @@ -100,7 +101,8 @@ public override async Task AfterRecordingStopAsync(RecordingArgs e, Cancellation
TokenPermissions = [.. minimalPermissions.TokenPermissions.Distinct()],
MinimalPermissions = minimalPermissions.MinimalScopes,
ExcessivePermissions = excessivePermissions,
UsesMinimalPermissions = !excessivePermissions.Any()
UsesMinimalPermissions = !excessivePermissions.Any(),
SchemeName = Configuration.SchemeName
};
results.Add(result);

Expand All @@ -112,20 +114,45 @@ public override async Task AfterRecordingStopAsync(RecordingArgs e, Cancellation

if (result.UsesMinimalPermissions)
{
Logger.LogInformation(
"API {ApiName} is called with minimal permissions: {MinimalPermissions}",
result.ApiName,
string.Join(", ", result.MinimalPermissions)
);
if (string.IsNullOrWhiteSpace(Configuration.SchemeName))
{
Logger.LogInformation(
"API {ApiName} is called with minimal permissions: {MinimalPermissions}",
result.ApiName,
string.Join(", ", result.MinimalPermissions)
);
}
else
{
Logger.LogInformation(
"API {ApiName} is called with minimal permissions of '{SchemeName}' scheme: {MinimalPermissions}",
result.ApiName,
Configuration.SchemeName,
string.Join(", ", result.MinimalPermissions)
);
}
}
else
{
Logger.LogWarning(
"Calling API {ApiName} with excessive permissions: {ExcessivePermissions}. Minimal permissions are: {MinimalPermissions}",
result.ApiName,
string.Join(", ", result.ExcessivePermissions),
string.Join(", ", result.MinimalPermissions)
);
if (string.IsNullOrWhiteSpace(Configuration.SchemeName))
{
Logger.LogWarning(
"Calling API {ApiName} with excessive permissions: {ExcessivePermissions}. Minimal permissions are: {MinimalPermissions}",
result.ApiName,
string.Join(", ", result.ExcessivePermissions),
string.Join(", ", result.MinimalPermissions)
);
}
else
{
Logger.LogWarning(
"Calling API {ApiName} with excessive permissions of '{SchemeName}' scheme: {ExcessivePermissions}. Minimal permissions are: {MinimalPermissions}",
result.ApiName,
Configuration.SchemeName,
string.Join(", ", result.ExcessivePermissions),
string.Join(", ", result.MinimalPermissions)
);
}
}

if (unmatchedApiRequests.Any())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public sealed class MinimalPermissionsGuidancePluginReportApiResult
public required IEnumerable<string> Requests { get; init; }
public required IEnumerable<string> TokenPermissions { get; init; }
public required bool UsesMinimalPermissions { get; init; }
public string? SchemeName { get; init; }
}

public sealed class MinimalPermissionsGuidancePluginReport : IMarkdownReport, IPlainTextReport
Expand All @@ -39,14 +40,17 @@ public sealed class MinimalPermissionsGuidancePluginReport : IMarkdownReport, IP

foreach (var result in Results)
{
var permissionsHeader = "### Minimal permissions" + (string.IsNullOrWhiteSpace(result.SchemeName)
? "" : $" for {result.SchemeName} scheme");

_ = sb.AppendLine(CultureInfo.InvariantCulture, $"## API: {result.ApiName}")
.AppendLine()
.AppendLine("### Requests")
.AppendLine()
.AppendJoin(Environment.NewLine, result.Requests.Select(r => $"- {r}"))
.AppendLine()
.AppendLine()
.AppendLine("### Minimal permissions")
.AppendLine(permissionsHeader)
.AppendLine()
.AppendJoin(Environment.NewLine, result.MinimalPermissions.Select(p => $"- `{p}`"))
.AppendLine()
Expand Down Expand Up @@ -120,6 +124,8 @@ public sealed class MinimalPermissionsGuidancePluginReport : IMarkdownReport, IP
foreach (var result in Results)
{
var apiTitle = $"API: {result.ApiName}";
var permissionsHeader = "Minimal permissions" + (string.IsNullOrWhiteSpace(result.SchemeName)
? "" : $" for {result.SchemeName} scheme") + ":";
_ = sb.AppendLine()
.AppendLine(apiTitle)
.AppendLine(new string('-', apiTitle.Length))
Expand All @@ -129,7 +135,7 @@ public sealed class MinimalPermissionsGuidancePluginReport : IMarkdownReport, IP
.AppendJoin(Environment.NewLine, result.Requests.Select(r => $"- {r}")).AppendLine()
.AppendLine();

_ = sb.AppendLine("Minimal permissions:")
_ = sb.AppendLine(permissionsHeader)
.AppendJoin(", ", result.MinimalPermissions).AppendLine()
.AppendLine();

Expand Down
4 changes: 4 additions & 0 deletions schemas/v1.3.0/minimalpermissionsguidanceplugin.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
},
"description": "The scopes to ignore and not include in the report. Default: ['profile', 'openid', 'offline_access', 'email'].",
"default": ["profile", "openid", "offline_access", "email"]
},
"schemeName": {
"type": "string",
"description": "The name of the security scheme definition. Used to determine minimal permissions required for API calls."
}
},
"required": [
Expand Down