Skip to content

Commit

Permalink
Fixes determining minimal Microsoft Graph permissions (#805)
Browse files Browse the repository at this point in the history
  • Loading branch information
waldekmastykarz authored Jun 21, 2024
1 parent 4de8d71 commit 4d802c1
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 22 deletions.
62 changes: 62 additions & 0 deletions dev-proxy-plugins/GraphUtils.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Net.Http.Json;
using Microsoft.DevProxy.Plugins.RequestLogs.MinimalPermissions;
using Microsoft.Extensions.Logging;
using Titanium.Web.Proxy.Http;

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

return workload;
}

internal static string GetScopeTypeString(PermissionsType type)
{
return type switch
{
PermissionsType.Application => "Application",
PermissionsType.Delegated => "DelegatedWork",
_ => throw new InvalidOperationException($"Unknown scope type: {type}")
};
}

internal static async Task<string[]> UpdateUserScopes(string[] minimalScopes, IEnumerable<(string method, string url)> endpoints, PermissionsType permissionsType, ILogger logger)
{
var userEndpoints = endpoints.Where(e => e.url.Contains("/users/{", StringComparison.OrdinalIgnoreCase));
if (!userEndpoints.Any())
{
return minimalScopes;
}

var newMinimalScopes = new HashSet<string>(minimalScopes);

var url = $"https://graphexplorerapi.azurewebsites.net/permissions?scopeType={GetScopeTypeString(permissionsType)}";
using var httpClient = new HttpClient();
var urls = userEndpoints.Select(e => {
logger.LogDebug("Getting permissions for {method} {url}", e.method, e.url);
return $"{url}&requesturl={e.url}&method={e.method}";
});
var tasks = urls.Select(u => {
logger.LogTrace("Calling {url}...", u);
return httpClient.GetFromJsonAsync<PermissionInfo[]>(u);
});
await Task.WhenAll(tasks);

foreach (var task in tasks)
{
var response = await task;
if (response is null)
{
continue;
}

// there's only one scope so it must be minimal already
if (response.Length < 2)
{
continue;
}

if (newMinimalScopes.Contains(response[0].Value))
{
logger.LogDebug("Replacing scope {old} with {new}", response[0].Value, response[1].Value);
newMinimalScopes.Remove(response[0].Value);
newMinimalScopes.Add(response[1].Value);
}
}

logger.LogDebug("Updated minimal scopes. Original: {original}, New: {new}", string.Join(", ", minimalScopes), string.Join(", ", newMinimalScopes));

return newMinimalScopes.ToArray();
}
}
18 changes: 7 additions & 11 deletions dev-proxy-plugins/RequestLogs/MinimalPermissionsGuidancePlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -252,16 +252,6 @@ private async Task AfterRecordingStop(object? sender, RecordingArgs e)
}
}

private string GetScopeTypeString(PermissionsType scopeType)
{
return scopeType switch
{
PermissionsType.Application => "Application",
PermissionsType.Delegated => "DelegatedWork",
_ => throw new InvalidOperationException($"Unknown scope type: {scopeType}")
};
}

private async Task EvaluateMinimalScopes(IEnumerable<(string method, string url)> endpoints, string[] permissionsFromAccessToken, PermissionsType scopeType, MinimalPermissionsInfo permissionsInfo)
{
var payload = endpoints.Select(e => new RequestInfo { Method = e.method, Url = e.url });
Expand All @@ -275,7 +265,7 @@ private async Task EvaluateMinimalScopes(IEnumerable<(string method, string url)

try
{
var url = $"https://graphexplorerapi.azurewebsites.net/permissions?scopeType={GetScopeTypeString(scopeType)}";
var url = $"https://graphexplorerapi.azurewebsites.net/permissions?scopeType={GraphUtils.GetScopeTypeString(scopeType)}";
using var client = new HttpClient();
var stringPayload = JsonSerializer.Serialize(payload, ProxyUtils.JsonSerializerOptions);
Logger.LogDebug(string.Format("Calling {0} with payload{1}{2}", url, Environment.NewLine, stringPayload));
Expand All @@ -288,6 +278,12 @@ private async Task EvaluateMinimalScopes(IEnumerable<(string method, string url)
var resultsAndErrors = JsonSerializer.Deserialize<ResultsAndErrors>(content, ProxyUtils.JsonSerializerOptions);
var minimalPermissions = resultsAndErrors?.Results?.Select(p => p.Value).ToArray() ?? Array.Empty<string>();
var errors = resultsAndErrors?.Errors?.Select(e => $"- {e.Url} ({e.Message})") ?? Array.Empty<string>();

if (scopeType == PermissionsType.Delegated)
{
minimalPermissions = await GraphUtils.UpdateUserScopes(minimalPermissions, endpoints, scopeType, Logger);
}

if (minimalPermissions.Any())
{
var excessPermissions = permissionsFromAccessToken
Expand Down
17 changes: 6 additions & 11 deletions dev-proxy-plugins/RequestLogs/MinimalPermissionsPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,23 +136,13 @@ private async Task AfterRecordingStop(object? sender, RecordingArgs e)
return requests.ToArray();
}

private string GetScopeTypeString()
{
return _configuration.Type switch
{
PermissionsType.Application => "Application",
PermissionsType.Delegated => "DelegatedWork",
_ => throw new InvalidOperationException($"Unknown scope type: {_configuration.Type}")
};
}

private async Task<MinimalPermissionsPluginReport?> DetermineMinimalScopes(IEnumerable<(string method, string url)> endpoints)
{
var payload = endpoints.Select(e => new RequestInfo { Method = e.method, Url = e.url });

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

if (_configuration.Type == PermissionsType.Delegated)
{
minimalScopes = await GraphUtils.UpdateUserScopes(minimalScopes, endpoints, _configuration.Type, Logger);
}

if (minimalScopes.Any())
{
Logger.LogInformation("Minimal permissions:\r\n{permissions}", string.Join(", ", minimalScopes));
Expand Down

0 comments on commit 4d802c1

Please sign in to comment.