// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Threading; using System.Threading.Tasks; namespace EpicGames.OIDC; #pragma warning disable CA1721 // Property names should not match get methods /// /// A fake implementation of OidcTokenManager for testing /// Assumes just a single provider. /// public class FakeOidcTokenManager : IOidcTokenManager { private const string AccessTokenPrefix = "fakeAccessToken"; /// /// Refresh token in use /// public string? RefreshToken { get; set; } /// /// Latest access token /// public string? AccessToken { get; set; } /// /// Time to live for newly minted access token /// public TimeSpan AccessTokenTtl { get; set; } = TimeSpan.FromMinutes(15); /// /// Expiry time for access token /// public DateTimeOffset AccessTokenExpiry { get; set; } = DateTimeOffset.UnixEpoch; private int _refreshCounter = 1; private int _accessCounter = 1; private readonly Func _utcNow; /// /// Constructor /// public FakeOidcTokenManager(Func utcNow) { _utcNow = utcNow; } /// public Task LoginAsync(string providerIdentifier, CancellationToken cancellationToken = default) { RefreshToken = "fakeRefreshToken-" + _refreshCounter++; RefreshAccessToken(); return Task.FromResult(GetOidcTokenInfo()); } /// public Task GetAccessToken(string providerIdentifier, CancellationToken cancellationToken = default) { throw new NotImplementedException("Method not in use"); } /// public Task TryGetAccessToken(string providerIdentifier, CancellationToken cancellationToken = default) { if (String.IsNullOrEmpty(RefreshToken)) { throw new NotLoggedInException(); } OidcTokenInfo tokenInfo = GetOidcTokenInfo(); // Ensure token is valid at least for another two minutes if (tokenInfo.IsValid(_utcNow().AddMinutes(-2))) { return Task.FromResult(tokenInfo); } // Access token not valid, simulate a refresh with provider RefreshAccessToken(); // Assume the updated access token is valid return Task.FromResult(GetOidcTokenInfo()); } /// public OidcStatus GetStatusForProvider(string providerIdentifier) { return OidcTokenClient.GetStatus(RefreshToken, AccessToken, AccessTokenExpiry); } /// /// Validate a fake access token minted by this class /// /// Token to check /// If token is malformed or expired public void ValidateAccessToken(string? token) { if (String.IsNullOrEmpty(token)) { throw new Exception($"Empty or null token: {token}"); } string[] parts = token.Split('-'); string prefix = parts[0]; int id = Convert.ToInt32(parts[1]); long expireUnixTime = Convert.ToInt64(parts[2]); DateTimeOffset expireTime = DateTimeOffset.FromUnixTimeMilliseconds(expireUnixTime); DateTimeOffset utcNow = _utcNow(); if (prefix != AccessTokenPrefix) { throw new Exception($"Bad prefix for token: {token}"); } if (utcNow > expireTime) { Console.WriteLine($"_clock.UtcNow: {utcNow}"); Console.WriteLine($" expireTime: {expireTime}"); int totalSeconds = (int)(_utcNow() - expireTime).TotalSeconds; throw new Exception($"Access token expired ({totalSeconds} seconds ago). Token: {token}"); } } /// /// Perform fake refresh of the access token /// private void RefreshAccessToken() { AccessTokenExpiry = _utcNow() + AccessTokenTtl; AccessToken = $"{AccessTokenPrefix}-{_accessCounter++}-{AccessTokenExpiry.ToUnixTimeMilliseconds()}-{AccessTokenExpiry.ToString()}"; } private OidcTokenInfo GetOidcTokenInfo() { return new OidcTokenInfo { RefreshToken = RefreshToken, AccessToken = AccessToken, TokenExpiry = AccessTokenExpiry }; } }