| File: Providers\MacPlatformSsoDetectionProvider.cs | Web Access |
| Project: Microsoft.DotNet.Cli.InternalMicrosoft.csproj (Microsoft.DotNet.Cli.InternalMicrosoft) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Text.Json; namespace Microsoft.DotNet.Cli.InternalMicrosoft; /// <summary> /// Reads Microsoft tenant and corporate identity evidence from macOS Platform SSO. /// The detector reuses this stateless provider for process-wide detection. /// </summary> internal sealed class MacPlatformSsoDetectionProvider : IInternalMicrosoftDetectionProvider { public string Name => "Mac Platform SSO"; public int Stage => 1; public bool IsSupported(InternalMicrosoftDetectionContext context) => context.IsMacOS; public async Task<InternalMicrosoftProbeResult> DetectAsync( InternalMicrosoftDetectionContext context, CancellationToken cancellationToken) { if (!context.CommandExists(context.MacPlatformSsoPath)) { return InternalMicrosoftProbeResult.NotDetected; } var processResult = await context.RunProcessProbeAsync( context.MacPlatformSsoPath, ["platform", "-s"], cancellationToken).ConfigureAwait(false); return processResult.Failure is not null ? InternalMicrosoftProbeResult.Failed(processResult.Failure) : Parse($"{processResult.StandardOutput}{Environment.NewLine}{processResult.StandardError}"); } internal static InternalMicrosoftProbeResult Parse(string output) { using var deviceConfiguration = TryParseSection(output, "Device Configuration"); using var loginConfiguration = TryParseSection(output, "Login Configuration"); using var userConfiguration = TryParseSection(output, "User Configuration"); if (deviceConfiguration is null || loginConfiguration is null || userConfiguration is null) { return Failure( InternalMicrosoftProbeFailureCode.JsonParse, InternalMicrosoftProbeFailureStage.PlatformSso); } if (!TryGetBoolean(deviceConfiguration.RootElement, "registrationCompleted", out var registrationCompleted)) { return Failure( InternalMicrosoftProbeFailureCode.JsonShape, InternalMicrosoftProbeFailureStage.PlatformSsoRegistration); } if (!registrationCompleted) { return Failure( InternalMicrosoftProbeFailureCode.RegistrationIncomplete, InternalMicrosoftProbeFailureStage.PlatformSsoRegistration); } if (!TryGetAbsoluteUri(loginConfiguration.RootElement, "issuer", out var issuer) || !IsMicrosoftTenantEndpoint(issuer, "/v2.0")) { return Failure( InternalMicrosoftProbeFailureCode.TenantMismatch, InternalMicrosoftProbeFailureStage.PlatformSsoIssuer); } if (!TryGetAbsoluteUri(loginConfiguration.RootElement, "keyEndpointURL", out var keyEndpoint) || !IsMicrosoftTenantEndpoint(keyEndpoint, "/getkeydata")) { return Failure( InternalMicrosoftProbeFailureCode.TenantMismatch, InternalMicrosoftProbeFailureStage.PlatformSsoKeyEndpoint); } if (!TryGetAbsoluteUri(loginConfiguration.RootElement, "tokenEndpointURL", out var tokenEndpoint) || !IsMicrosoftTenantEndpoint(tokenEndpoint, "/oauth2/v2.0/token")) { return Failure( InternalMicrosoftProbeFailureCode.TenantMismatch, InternalMicrosoftProbeFailureStage.PlatformSsoTokenEndpoint); } if (!userConfiguration.RootElement.TryGetProperty("kerberosStatus", out var kerberosStatuses) || kerberosStatuses.ValueKind != JsonValueKind.Array || kerberosStatuses.GetArrayLength() == 0) { return Failure( InternalMicrosoftProbeFailureCode.RegistrationIncomplete, InternalMicrosoftProbeFailureStage.PlatformSsoIdentity); } foreach (var kerberosStatus in kerberosStatuses.EnumerateArray()) { if (kerberosStatus.ValueKind != JsonValueKind.Object || !InternalMicrosoftDetectionUtilities.TryGetString(kerberosStatus, "upn", out var upn) || !InternalMicrosoftDetectionUtilities.TryGetString(kerberosStatus, "realm", out var realm) || !InternalMicrosoftDetectionUtilities.TryGetCorporateDomain(realm, out var realmDomain)) { continue; } var separator = upn.IndexOf('@'); if (separator > 0 && InternalMicrosoftDetectionUtilities.TryGetCorporateDomain( upn[(separator + 1)..], out var upnDomain) && string.Equals(upnDomain, realmDomain, StringComparison.OrdinalIgnoreCase) && InternalMicrosoftDetectionUtilities.NormalizeAlias(upn[..separator]) is { } alias) { return new InternalMicrosoftProbeResult(true, alias, realmDomain); } } return Failure( InternalMicrosoftProbeFailureCode.IdentityMismatch, InternalMicrosoftProbeFailureStage.PlatformSsoIdentity); } private static JsonDocument? TryParseSection(string output, string sectionName) { var header = sectionName + ":"; var headerIndex = output.IndexOf(header, StringComparison.Ordinal); if (headerIndex < 0) { return null; } var objectStart = output.IndexOf('{', headerIndex + header.Length); if (objectStart < 0) { return null; } var depth = 0; var inString = false; var escaped = false; for (var index = objectStart; index < output.Length; index++) { var character = output[index]; if (inString) { if (escaped) { escaped = false; } else if (character == '\\') { escaped = true; } else if (character == '"') { inString = false; } continue; } if (character == '"') { inString = true; } else if (character == '{') { depth++; } else if (character == '}' && --depth == 0) { try { return JsonDocument.Parse(output[objectStart..(index + 1)]); } catch (JsonException) { return null; } } } return null; } private static bool TryGetBoolean(JsonElement root, string propertyName, out bool value) { value = false; if (!root.TryGetProperty(propertyName, out var property) || property.ValueKind is not (JsonValueKind.True or JsonValueKind.False)) { return false; } value = property.GetBoolean(); return true; } private static bool TryGetAbsoluteUri(JsonElement root, string propertyName, out Uri uri) { if (InternalMicrosoftDetectionUtilities.TryGetString(root, propertyName, out var value) && Uri.TryCreate(value, UriKind.Absolute, out var parsedUri)) { uri = parsedUri; return true; } uri = null!; return false; } private static bool IsMicrosoftTenantEndpoint(Uri uri, string expectedSuffix) => uri.Scheme == Uri.UriSchemeHttps && string.Equals( uri.Host, InternalMicrosoftDetectionUtilities.MicrosoftLoginHost, StringComparison.OrdinalIgnoreCase) && uri.AbsolutePath.Equals( $"/{InternalMicrosoftDetectionUtilities.MicrosoftTenantId}{expectedSuffix}", StringComparison.OrdinalIgnoreCase); private static InternalMicrosoftProbeResult Failure(string code, string stage) => InternalMicrosoftProbeResult.Failed(new(code, stage)); }