Skip to content

Commit 4d802c1

Browse files
Fixes determining minimal Microsoft Graph permissions (#805)
1 parent 4de8d71 commit 4d802c1

File tree

3 files changed

+75
-22
lines changed

3 files changed

+75
-22
lines changed

dev-proxy-plugins/GraphUtils.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
using System.Net.Http.Json;
5+
using Microsoft.DevProxy.Plugins.RequestLogs.MinimalPermissions;
6+
using Microsoft.Extensions.Logging;
47
using Titanium.Web.Proxy.Http;
58

69
namespace Microsoft.DevProxy.Plugins;
@@ -27,4 +30,63 @@ public static string BuildThrottleKey(Uri uri)
2730

2831
return workload;
2932
}
33+
34+
internal static string GetScopeTypeString(PermissionsType type)
35+
{
36+
return type switch
37+
{
38+
PermissionsType.Application => "Application",
39+
PermissionsType.Delegated => "DelegatedWork",
40+
_ => throw new InvalidOperationException($"Unknown scope type: {type}")
41+
};
42+
}
43+
44+
internal static async Task<string[]> UpdateUserScopes(string[] minimalScopes, IEnumerable<(string method, string url)> endpoints, PermissionsType permissionsType, ILogger logger)
45+
{
46+
var userEndpoints = endpoints.Where(e => e.url.Contains("/users/{", StringComparison.OrdinalIgnoreCase));
47+
if (!userEndpoints.Any())
48+
{
49+
return minimalScopes;
50+
}
51+
52+
var newMinimalScopes = new HashSet<string>(minimalScopes);
53+
54+
var url = $"https://graphexplorerapi.azurewebsites.net/permissions?scopeType={GetScopeTypeString(permissionsType)}";
55+
using var httpClient = new HttpClient();
56+
var urls = userEndpoints.Select(e => {
57+
logger.LogDebug("Getting permissions for {method} {url}", e.method, e.url);
58+
return $"{url}&requesturl={e.url}&method={e.method}";
59+
});
60+
var tasks = urls.Select(u => {
61+
logger.LogTrace("Calling {url}...", u);
62+
return httpClient.GetFromJsonAsync<PermissionInfo[]>(u);
63+
});
64+
await Task.WhenAll(tasks);
65+
66+
foreach (var task in tasks)
67+
{
68+
var response = await task;
69+
if (response is null)
70+
{
71+
continue;
72+
}
73+
74+
// there's only one scope so it must be minimal already
75+
if (response.Length < 2)
76+
{
77+
continue;
78+
}
79+
80+
if (newMinimalScopes.Contains(response[0].Value))
81+
{
82+
logger.LogDebug("Replacing scope {old} with {new}", response[0].Value, response[1].Value);
83+
newMinimalScopes.Remove(response[0].Value);
84+
newMinimalScopes.Add(response[1].Value);
85+
}
86+
}
87+
88+
logger.LogDebug("Updated minimal scopes. Original: {original}, New: {new}", string.Join(", ", minimalScopes), string.Join(", ", newMinimalScopes));
89+
90+
return newMinimalScopes.ToArray();
91+
}
3092
}

dev-proxy-plugins/RequestLogs/MinimalPermissionsGuidancePlugin.cs

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -252,16 +252,6 @@ private async Task AfterRecordingStop(object? sender, RecordingArgs e)
252252
}
253253
}
254254

255-
private string GetScopeTypeString(PermissionsType scopeType)
256-
{
257-
return scopeType switch
258-
{
259-
PermissionsType.Application => "Application",
260-
PermissionsType.Delegated => "DelegatedWork",
261-
_ => throw new InvalidOperationException($"Unknown scope type: {scopeType}")
262-
};
263-
}
264-
265255
private async Task EvaluateMinimalScopes(IEnumerable<(string method, string url)> endpoints, string[] permissionsFromAccessToken, PermissionsType scopeType, MinimalPermissionsInfo permissionsInfo)
266256
{
267257
var payload = endpoints.Select(e => new RequestInfo { Method = e.method, Url = e.url });
@@ -275,7 +265,7 @@ private async Task EvaluateMinimalScopes(IEnumerable<(string method, string url)
275265

276266
try
277267
{
278-
var url = $"https://graphexplorerapi.azurewebsites.net/permissions?scopeType={GetScopeTypeString(scopeType)}";
268+
var url = $"https://graphexplorerapi.azurewebsites.net/permissions?scopeType={GraphUtils.GetScopeTypeString(scopeType)}";
279269
using var client = new HttpClient();
280270
var stringPayload = JsonSerializer.Serialize(payload, ProxyUtils.JsonSerializerOptions);
281271
Logger.LogDebug(string.Format("Calling {0} with payload{1}{2}", url, Environment.NewLine, stringPayload));
@@ -288,6 +278,12 @@ private async Task EvaluateMinimalScopes(IEnumerable<(string method, string url)
288278
var resultsAndErrors = JsonSerializer.Deserialize<ResultsAndErrors>(content, ProxyUtils.JsonSerializerOptions);
289279
var minimalPermissions = resultsAndErrors?.Results?.Select(p => p.Value).ToArray() ?? Array.Empty<string>();
290280
var errors = resultsAndErrors?.Errors?.Select(e => $"- {e.Url} ({e.Message})") ?? Array.Empty<string>();
281+
282+
if (scopeType == PermissionsType.Delegated)
283+
{
284+
minimalPermissions = await GraphUtils.UpdateUserScopes(minimalPermissions, endpoints, scopeType, Logger);
285+
}
286+
291287
if (minimalPermissions.Any())
292288
{
293289
var excessPermissions = permissionsFromAccessToken

dev-proxy-plugins/RequestLogs/MinimalPermissionsPlugin.cs

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -136,23 +136,13 @@ private async Task AfterRecordingStop(object? sender, RecordingArgs e)
136136
return requests.ToArray();
137137
}
138138

139-
private string GetScopeTypeString()
140-
{
141-
return _configuration.Type switch
142-
{
143-
PermissionsType.Application => "Application",
144-
PermissionsType.Delegated => "DelegatedWork",
145-
_ => throw new InvalidOperationException($"Unknown scope type: {_configuration.Type}")
146-
};
147-
}
148-
149139
private async Task<MinimalPermissionsPluginReport?> DetermineMinimalScopes(IEnumerable<(string method, string url)> endpoints)
150140
{
151141
var payload = endpoints.Select(e => new RequestInfo { Method = e.method, Url = e.url });
152142

153143
try
154144
{
155-
var url = $"https://graphexplorerapi.azurewebsites.net/permissions?scopeType={GetScopeTypeString()}";
145+
var url = $"https://graphexplorerapi.azurewebsites.net/permissions?scopeType={GraphUtils.GetScopeTypeString(_configuration.Type)}";
156146
using var client = new HttpClient();
157147
var stringPayload = JsonSerializer.Serialize(payload, ProxyUtils.JsonSerializerOptions);
158148
Logger.LogDebug("Calling {url} with payload\r\n{stringPayload}", url, stringPayload);
@@ -166,6 +156,11 @@ private string GetScopeTypeString()
166156
var minimalScopes = resultsAndErrors?.Results?.Select(p => p.Value).ToArray() ?? Array.Empty<string>();
167157
var errors = resultsAndErrors?.Errors?.Select(e => $"- {e.Url} ({e.Message})") ?? Array.Empty<string>();
168158

159+
if (_configuration.Type == PermissionsType.Delegated)
160+
{
161+
minimalScopes = await GraphUtils.UpdateUserScopes(minimalScopes, endpoints, _configuration.Type, Logger);
162+
}
163+
169164
if (minimalScopes.Any())
170165
{
171166
Logger.LogInformation("Minimal permissions:\r\n{permissions}", string.Join(", ", minimalScopes));

0 commit comments

Comments
 (0)