Files
UnrealEngine/Engine/Source/Programs/Shared/EpicGames.OIDC/ProviderConfigurationFactory.cs
2025-05-18 13:04:45 +08:00

137 lines
4.7 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net;
using System.Security.Cryptography;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
#pragma warning disable CS1591 // Missing XML documentation on public types
namespace EpicGames.OIDC
{
public static class ProviderConfigurationFactory
{
const string ConfigFileName = "oidc-configuration.json";
public static IReadOnlyList<string> ConfigPaths { get; } = new string[]
{
$"Programs/OidcToken/{ConfigFileName}",
$"Restricted/NoRedist/Programs/OidcToken/{ConfigFileName}",
$"Restricted/NotForLicensees/Programs/OidcToken/{ConfigFileName}"
};
public static IConfiguration ReadConfiguration(DirectoryInfo engineDir, DirectoryInfo? gameDir)
{
if (!engineDir.Exists)
{
throw new Exception($"Failed to locate engine dir at {engineDir}");
}
ConfigurationBuilder configBuilder = new ConfigurationBuilder();
configBuilder
.AddJsonFile($"{engineDir}/Programs/OidcToken/{ConfigFileName}", true, false)
.AddJsonFile($"{engineDir}/Restricted/NoRedist/Programs/OidcToken/{ConfigFileName}", true, false)
.AddJsonFile($"{engineDir}/Restricted/NotForLicensees/Programs/OidcToken/{ConfigFileName}", true, false);
if (gameDir?.Exists ?? false)
{
configBuilder.AddJsonFile($"{gameDir}/Programs/OidcToken/{ConfigFileName}", true, false)
.AddJsonFile($"{gameDir}/Restricted/NoRedist/Programs/OidcToken/{ConfigFileName}", true, false)
.AddJsonFile($"{gameDir}/Restricted/NotForLicensees/Programs/OidcToken/{ConfigFileName}", true, false);
}
IConfiguration config = configBuilder.Build();
return config.GetSection("OidcToken");
}
public static IConfiguration MergeConfiguration(IEnumerable<(DirectoryInfo, DirectoryInfo?)> configurationPaths)
{
ConfigurationBuilder builder = new ConfigurationBuilder();
foreach ((DirectoryInfo engineDir, DirectoryInfo? gameDir) in configurationPaths)
{
IConfiguration newConfiguration = ReadConfiguration(engineDir, gameDir);
builder.AddConfiguration(newConfiguration);
}
return builder.Build();
}
public static async Task<ClientAuthConfigurationV1?> ReadRemoteAuthConfigurationAsync(Uri remoteUrl, string encryptionKey)
{
using HttpClient httpClient = new HttpClient();
const int MaxAttempts = 3;
byte[]? b = null;
for (int i = 0; i < MaxAttempts; i++)
{
using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, new Uri(remoteUrl, "api/v1/auth/oidc-configuration"));
using HttpResponseMessage response = await httpClient.SendAsync(request);
if (response.StatusCode == HttpStatusCode.NotFound)
{
return null;
}
if (response.IsSuccessStatusCode)
{
b = await response.Content.ReadAsByteArrayAsync();
}
}
if (b == null)
{
throw new Exception($"Failed to read config after {MaxAttempts} attempts. Unable to read configuration");
}
using Aes aes = Aes.Create();
int ivLength = aes.IV.Length;
byte[] iv = new byte[ivLength];
Array.Copy(b, iv, ivLength);
byte[] key = Convert.FromHexString(encryptionKey);
MemoryStream ms = new MemoryStream(b, ivLength, b.Length - ivLength);
await using CryptoStream cryptoStream = new(ms, aes.CreateDecryptor(key, iv), CryptoStreamMode.Read);
using StreamReader decryptReader = new(cryptoStream);
return await JsonSerializer.DeserializeAsync<ClientAuthConfigurationV1>(cryptoStream, ProviderConfigurationStateContext.Default.ClientAuthConfigurationV1);
}
public const string DefaultEncryptionKey = "892a27ef5cbf4894af2e6bd53a54aa48";
public static IConfiguration BindOptions(ClientAuthConfigurationV1 remoteAuthConfig)
{
using MemoryStream ms = new MemoryStream();
{
using Utf8JsonWriter jsonWriter = new Utf8JsonWriter(ms);
JsonSerializer.Serialize(jsonWriter, remoteAuthConfig, ProviderConfigurationStateContext.Default.ClientAuthConfigurationV1);
ms.Position = 0;
}
IConfigurationRoot config = new ConfigurationBuilder().AddJsonStream(ms).Build();
return config;
}
}
public class ClientAuthConfigurationV1: OidcTokenOptions
{
/// <summary>
/// The provider in Providers to use by default
/// </summary>
public string? DefaultProvider { get; set; } = null;
/// <summary>
/// Can be set to "Anonymous" to indicate that auth is disabled. This is a horde specific convention.
/// </summary>
public string Method { get; set; } = "";
}
[JsonSerializable(typeof(ClientAuthConfigurationV1))]
internal partial class ProviderConfigurationStateContext : JsonSerializerContext
{
}
}