123 lines
4.3 KiB
C#
123 lines
4.3 KiB
C#
using System.Text;
|
|
using System.Text.Json;
|
|
using BuildingBlock.Identity.Contracts.Conventions;
|
|
using BuildingBlock.Identity.Contracts.Requests;
|
|
using Thalos.Service.Application.Oidc;
|
|
using Thalos.Service.Application.Secrets;
|
|
|
|
namespace Thalos.Service.Application.UnitTests;
|
|
|
|
public class GoogleIdentityProviderTokenExchangeServiceTests
|
|
{
|
|
[Fact]
|
|
public async Task ExchangeAsync_WhenTokenClaimsMatch_ReturnsAuthenticatedSubject()
|
|
{
|
|
var provider = new FakeSecretMaterialProvider(
|
|
new Dictionary<string, string>(StringComparer.Ordinal)
|
|
{
|
|
["Oidc:Google:ClientId"] = "google-client-1",
|
|
["Oidc:Google:Issuer"] = "https://accounts.google.com"
|
|
});
|
|
var service = new GoogleIdentityProviderTokenExchangeService(provider);
|
|
var token = BuildUnsignedJwt(new Dictionary<string, object?>
|
|
{
|
|
["sub"] = "google-sub-1",
|
|
["aud"] = "google-client-1",
|
|
["iss"] = "https://accounts.google.com"
|
|
});
|
|
|
|
var response = await service.ExchangeAsync(new ExchangeIdentityProviderTokenRequest(
|
|
"tenant-1",
|
|
IdentityAuthProvider.Google,
|
|
token,
|
|
"corr-1"));
|
|
|
|
Assert.True(response.IsAuthenticated);
|
|
Assert.Equal("google-sub-1", response.SubjectId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExchangeAsync_WhenAudienceMismatches_ReturnsUnauthenticated()
|
|
{
|
|
var provider = new FakeSecretMaterialProvider(
|
|
new Dictionary<string, string>(StringComparer.Ordinal)
|
|
{
|
|
["Oidc:Google:ClientId"] = "google-client-1"
|
|
});
|
|
var service = new GoogleIdentityProviderTokenExchangeService(provider);
|
|
var token = BuildUnsignedJwt(new Dictionary<string, object?>
|
|
{
|
|
["sub"] = "google-sub-2",
|
|
["aud"] = "google-client-2",
|
|
["iss"] = "https://accounts.google.com"
|
|
});
|
|
|
|
var response = await service.ExchangeAsync(new ExchangeIdentityProviderTokenRequest(
|
|
"tenant-2",
|
|
IdentityAuthProvider.Google,
|
|
token,
|
|
"corr-2"));
|
|
|
|
Assert.False(response.IsAuthenticated);
|
|
Assert.Equal(string.Empty, response.SubjectId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExchangeAsync_WhenGoogleClientIdSecretMissing_ReturnsUnauthenticated()
|
|
{
|
|
var provider = new FakeSecretMaterialProvider(new Dictionary<string, string>(StringComparer.Ordinal));
|
|
var service = new GoogleIdentityProviderTokenExchangeService(provider);
|
|
var token = BuildUnsignedJwt(new Dictionary<string, object?>
|
|
{
|
|
["sub"] = "google-sub-3",
|
|
["aud"] = "google-client-1",
|
|
["iss"] = "https://accounts.google.com"
|
|
});
|
|
|
|
var response = await service.ExchangeAsync(new ExchangeIdentityProviderTokenRequest(
|
|
"tenant-3",
|
|
IdentityAuthProvider.Google,
|
|
token,
|
|
"corr-3"));
|
|
|
|
Assert.False(response.IsAuthenticated);
|
|
Assert.Equal(string.Empty, response.SubjectId);
|
|
}
|
|
|
|
private static string BuildUnsignedJwt(Dictionary<string, object?> payload)
|
|
{
|
|
var header = Base64UrlEncode("""{"alg":"none","typ":"JWT"}""");
|
|
var payloadJson = JsonSerializer.Serialize(payload);
|
|
var payloadEncoded = Base64UrlEncode(payloadJson);
|
|
return $"{header}.{payloadEncoded}.";
|
|
}
|
|
|
|
private static string Base64UrlEncode(string value)
|
|
{
|
|
var bytes = Encoding.UTF8.GetBytes(value);
|
|
var encoded = Convert.ToBase64String(bytes);
|
|
return encoded.TrimEnd('=').Replace('+', '-').Replace('/', '_');
|
|
}
|
|
|
|
private sealed class FakeSecretMaterialProvider(
|
|
IReadOnlyDictionary<string, string> secrets) : IIdentitySecretMaterialProvider
|
|
{
|
|
public bool TryGetSecret(string secretKey, out string secretValue)
|
|
{
|
|
if (secrets.TryGetValue(secretKey, out var value) && !string.IsNullOrWhiteSpace(value))
|
|
{
|
|
secretValue = value;
|
|
return true;
|
|
}
|
|
|
|
secretValue = string.Empty;
|
|
return false;
|
|
}
|
|
|
|
public string GetSecret(string secretKey)
|
|
{
|
|
return TryGetSecret(secretKey, out var secretValue) ? secretValue : string.Empty;
|
|
}
|
|
}
|
|
}
|