Skip to content

Commit f41b169

Browse files
bartizanCopilotwaldekmastykarz
authored
Extend MinimalPermissionsPlugin by introducing the schemeName property (#1391)
* Add schemeName property to MinimalPermissionsPlugin config * Overload CheckMinimalPermissions() with schemeName param * Use schemeName param in MinimalPermissionsPlugin * Add logging of the scheme name for minimal permission * Add schema name to MinimalPermissionsPluginReport * Fix debug loggin message Co-authored-by: Copilot <[email protected]> * fix: Add name check to main where-clause * Remove dangling comma * fix: Include scheme name into permissions header in report * Fix logging message * fix: Add optional param to CheckMinimalPermissions and remove unnecessary method * Add schemeName property to MinimalPermissionsPlugin config v1.3 and remove from v1.2 --------- Co-authored-by: Copilot <[email protected]> Co-authored-by: Waldek Mastykarz <[email protected]>
1 parent 6af73eb commit f41b169

File tree

4 files changed

+50
-20
lines changed

4 files changed

+50
-20
lines changed

DevProxy.Plugins/Extensions/OpenApiDocumentExtensions.cs

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ namespace Microsoft.OpenApi.Models;
1515

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

@@ -78,7 +79,7 @@ public static ApiPermissionsInfo CheckMinimalPermissions(this OpenApiDocument op
7879
continue;
7980
}
8081

81-
var scopes = operation.GetEffectiveScopes(openApiDocument, logger);
82+
var scopes = operation.GetEffectiveScopes(openApiDocument, logger, schemeName);
8283
if (scopes.Length != 0)
8384
{
8485
operationsAndScopes[$"{method} {pathItem.Value.Key}"] = scopes;
@@ -176,12 +177,19 @@ [.. operationsFromRequests
176177
return null;
177178
}
178179

179-
public static string[] GetEffectiveScopes(this OpenApiOperation operation, OpenApiDocument openApiDocument, ILogger logger)
180+
public static string[] GetEffectiveScopes(this OpenApiOperation operation, OpenApiDocument openApiDocument, ILogger logger, string? schemeName)
180181
{
181-
var oauth2Scheme = openApiDocument.GetOAuth2Schemes().FirstOrDefault();
182+
var oauth2Scheme = openApiDocument.GetOAuth2Schemes(schemeName).FirstOrDefault();
182183
if (oauth2Scheme is null)
183184
{
184-
logger.LogDebug("No OAuth2 schemes found in OpenAPI document");
185+
if (string.IsNullOrWhiteSpace(schemeName))
186+
{
187+
logger.LogDebug("No OAuth2 schemes found in OpenAPI document");
188+
}
189+
else
190+
{
191+
logger.LogDebug("No OAuth2 '{SchemeName}' scheme found in OpenAPI document", schemeName);
192+
}
185193
return [];
186194
}
187195

@@ -210,11 +218,13 @@ public static string[] GetEffectiveScopes(this OpenApiOperation operation, OpenA
210218
return [];
211219
}
212220

213-
public static OpenApiSecurityScheme[] GetOAuth2Schemes(this OpenApiDocument openApiDocument)
221+
public static OpenApiSecurityScheme[] GetOAuth2Schemes(this OpenApiDocument openApiDocument, string? schemeName)
214222
{
215-
return [.. openApiDocument.Components.SecuritySchemes
216-
.Where(s => s.Value.Type == SecuritySchemeType.OAuth2)
217-
.Select(s => s.Value)];
223+
var schemes = openApiDocument.Components.SecuritySchemes
224+
.Where(s => s.Value.Type == SecuritySchemeType.OAuth2
225+
&& (string.IsNullOrWhiteSpace(schemeName) || string.Equals(schemeName, s.Key, StringComparison.Ordinal)));
226+
227+
return [.. schemes.Select(s => s.Value)];
218228
}
219229

220230
private static bool UrlMatchesServerUrl(string absoluteUrl, string serverUrl)

DevProxy.Plugins/Reporting/MinimalPermissionsPlugin.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ namespace DevProxy.Plugins.Reporting;
1717
public sealed class MinimalPermissionsPluginConfiguration
1818
{
1919
public string? ApiSpecsFolderPath { get; set; }
20+
public string? SchemeName { get; set; }
2021
}
2122

2223
public sealed class MinimalPermissionsPlugin(
@@ -92,7 +93,7 @@ public override async Task AfterRecordingStopAsync(RecordingArgs e, Cancellation
9293

9394
foreach (var (apiSpec, requests) in requestsByApiSpec)
9495
{
95-
var minimalPermissions = apiSpec.CheckMinimalPermissions(requests, Logger);
96+
var minimalPermissions = apiSpec.CheckMinimalPermissions(requests, Logger, Configuration.SchemeName);
9697

9798
var result = new MinimalPermissionsPluginReportApiResult
9899
{
@@ -102,7 +103,8 @@ public override async Task AfterRecordingStopAsync(RecordingArgs e, Cancellation
102103
.Select(o => $"{o.Method} {o.OriginalUrl}")
103104
.Distinct()],
104105
TokenPermissions = [.. minimalPermissions.TokenPermissions.Distinct()],
105-
MinimalPermissions = minimalPermissions.MinimalScopes
106+
MinimalPermissions = minimalPermissions.MinimalScopes,
107+
SchemeName = Configuration.SchemeName
106108
};
107109
results.Add(result);
108110

@@ -132,7 +134,15 @@ public override async Task AfterRecordingStopAsync(RecordingArgs e, Cancellation
132134
);
133135
}
134136

135-
Logger.LogInformation("Minimal permissions: {MinimalScopes}", string.Join(", ", result.MinimalPermissions));
137+
if (string.IsNullOrWhiteSpace(Configuration.SchemeName))
138+
{
139+
Logger.LogInformation("Minimal permissions: {MinimalScopes}", string.Join(", ", result.MinimalPermissions));
140+
}
141+
else
142+
{
143+
Logger.LogInformation("Minimal permissions of '{SchemeName}' scheme: {MinimalScopes}",
144+
Configuration.SchemeName, string.Join(", ", result.MinimalPermissions));
145+
}
136146
}
137147

138148
var report = new MinimalPermissionsPluginReport()

DevProxy.Plugins/Reporting/MinimalPermissionsPluginReport.cs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public sealed class MinimalPermissionsPluginReportApiResult
1515
public required IEnumerable<string> MinimalPermissions { get; init; }
1616
public required IEnumerable<string> Requests { get; init; }
1717
public required IEnumerable<string> TokenPermissions { get; init; }
18+
public string? SchemeName { get; init; }
1819
}
1920

2021
public sealed class MinimalPermissionsPluginReport : IMarkdownReport, IPlainTextReport
@@ -26,7 +27,7 @@ public sealed class MinimalPermissionsPluginReport : IMarkdownReport, IPlainText
2627
public string? ToMarkdown()
2728
{
2829
var sb = new StringBuilder();
29-
_ = sb.AppendLine($"# Minimal permissions report");
30+
_ = sb.AppendLine("# Minimal permissions report");
3031

3132
foreach (var apiResult in Results)
3233
{
@@ -36,10 +37,12 @@ public sealed class MinimalPermissionsPluginReport : IMarkdownReport, IPlainText
3637
.AppendLine("### Requests")
3738
.AppendLine()
3839
.AppendJoin(Environment.NewLine, apiResult.Requests.Select(r => $"- {r}"))
39-
.AppendLine()
40+
.AppendLine();
4041

41-
.AppendLine()
42-
.AppendLine("### Minimal permissions")
42+
var permissionsHeader = "### Minimal permissions" + (string.IsNullOrWhiteSpace(apiResult.SchemeName)
43+
? "" : $" for {apiResult.SchemeName} scheme");
44+
_ = sb.AppendLine()
45+
.AppendLine(permissionsHeader)
4346
.AppendLine()
4447
.AppendJoin(Environment.NewLine, apiResult.MinimalPermissions.Select(p => $"- {p}"))
4548
.AppendLine();
@@ -65,7 +68,7 @@ public sealed class MinimalPermissionsPluginReport : IMarkdownReport, IPlainText
6568
{
6669
var sb = new StringBuilder();
6770

68-
_ = sb.AppendLine($"Minimal permissions report");
71+
_ = sb.AppendLine("Minimal permissions report");
6972

7073
foreach (var apiResult in Results)
7174
{
@@ -75,9 +78,12 @@ public sealed class MinimalPermissionsPluginReport : IMarkdownReport, IPlainText
7578
.AppendLine("Requests:")
7679
.AppendLine()
7780
.AppendJoin(Environment.NewLine, apiResult.Requests.Select(r => $"- {r}"))
78-
.AppendLine()
79-
.AppendLine()
80-
.AppendLine("Minimal permissions:")
81+
.AppendLine();
82+
83+
var permissionsHeader = "Minimal permissions" + (string.IsNullOrWhiteSpace(apiResult.SchemeName)
84+
? "" : $" for {apiResult.SchemeName} scheme") + ":";
85+
_ = sb.AppendLine()
86+
.AppendLine(permissionsHeader)
8187
.AppendLine()
8288
.AppendJoin(Environment.NewLine, apiResult.MinimalPermissions.Select(p => $"- {p}"));
8389
}

schemas/v1.3.0/minimalpermissionsplugin.schema.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
"apiSpecsFolderPath": {
1111
"type": "string",
1212
"description": "Relative or absolute path to the folder with API specs. Used to determine minimal permissions required for API calls."
13+
},
14+
"schemeName": {
15+
"type": "string",
16+
"description": "The name of the security scheme definition. Used to determine minimal permissions required for API calls."
1317
}
1418
},
1519
"required": [

0 commit comments

Comments
 (0)