* Refactor OidcTokenProvider to remove dependency on IdentityModel and improve token handling * Improve OidcTokenProvider error handling and expiry setting The constructor `OidcTokenProvider` now always sets the `_expiry` field by calling `GetExpiryFromToken()`, regardless of whether `_idToken` is null or empty, removing the previous check for a non-empty `_idToken`. The `GetExpiryFromToken` method has been updated to handle invalid JWT token formats more gracefully. Instead of throwing an `ArgumentException` when the token format is invalid or when the 'exp' claim is missing, the method now returns a default value. The logic for parsing the JWT token and extracting the 'exp' claim has been wrapped in a try-catch block. If any exception occurs during this process, it is caught, and the method returns a default value instead of throwing an exception. * Refactor parts initialization inside try block Moved the initialization of the `parts` variable, which splits the `_idToken` string, inside the `try` block. Removed the previous check for exactly three elements in the `parts` array and the default return value if the check failed. * Add tests. --------- Co-authored-by: Boshi Lian <farmer1992@gmail.com>
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
<PackageReference Include="System.Reactive" />
|
||||
<PackageReference Include="Nito.AsyncEx" />
|
||||
<PackageReference Include="Portable.BouncyCastle" />
|
||||
<PackageReference Include="Wiremock.Net" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
using FluentAssertions;
|
||||
using k8s.Authentication;
|
||||
using k8s.Exceptions;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using WireMock.Server;
|
||||
using WireMock.RequestBuilders;
|
||||
using WireMock.ResponseBuilders;
|
||||
using Xunit;
|
||||
|
||||
namespace k8s.Tests
|
||||
@@ -53,5 +58,87 @@ namespace k8s.Tests
|
||||
Assert.StartsWith("Unable to refresh OIDC token.", e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TestOidcAuthWithWireMock()
|
||||
{
|
||||
// Arrange
|
||||
var server = WireMockServer.Start();
|
||||
var idpIssuerUrl = server.Url + "/token";
|
||||
var clientId = "CLIENT_ID";
|
||||
var clientSecret = "CLIENT_SECRET";
|
||||
var expiredIdToken = "eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjB9.f37LFpIw_XIS5TZt3wdtEjjyCNshYy03lOWpyDViRM0";
|
||||
var refreshToken = "REFRESH_TOKEN";
|
||||
var newIdToken = "NEW_ID_TOKEN";
|
||||
var expiresIn = 3600;
|
||||
|
||||
// Simulate a successful token refresh response
|
||||
server
|
||||
.Given(Request.Create().WithPath("/token").UsingPost())
|
||||
.RespondWith(Response.Create()
|
||||
.WithStatusCode(HttpStatusCode.OK)
|
||||
.WithBody($@"{{
|
||||
""id_token"": ""{newIdToken}"",
|
||||
""refresh_token"": ""{refreshToken}"",
|
||||
""expires_in"": {expiresIn}
|
||||
}}"));
|
||||
|
||||
var auth = new OidcTokenProvider(clientId, clientSecret, idpIssuerUrl, expiredIdToken, refreshToken);
|
||||
|
||||
// Act
|
||||
var result = await auth.GetAuthenticationHeaderAsync(CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Scheme.Should().Be("Bearer");
|
||||
result.Parameter.Should().Be(newIdToken);
|
||||
|
||||
// Verify that the expiry is set correctly
|
||||
var expectedExpiry = DateTimeOffset.UtcNow.AddSeconds(expiresIn);
|
||||
var actualExpiry = typeof(OidcTokenProvider)
|
||||
.GetField("_expiry", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
|
||||
?.GetValue(auth) as DateTimeOffset?;
|
||||
actualExpiry.Should().NotBeNull();
|
||||
actualExpiry.Value.Should().BeCloseTo(expectedExpiry, precision: TimeSpan.FromSeconds(5));
|
||||
|
||||
// Verify that the refresh token is set correctly
|
||||
var actualRefreshToken = typeof(OidcTokenProvider)
|
||||
.GetField("_refreshToken", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
|
||||
?.GetValue(auth) as string;
|
||||
actualRefreshToken.Should().NotBeNull();
|
||||
actualRefreshToken.Should().Be(refreshToken);
|
||||
|
||||
// Stop the server
|
||||
server.Stop();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TestOidcAuthWithServerError()
|
||||
{
|
||||
// Arrange
|
||||
var server = WireMockServer.Start();
|
||||
var idpIssuerUrl = server.Url + "/token";
|
||||
var clientId = "CLIENT_ID";
|
||||
var clientSecret = "CLIENT_SECRET";
|
||||
var expiredIdToken = "eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjB9.f37LFpIw_XIS5TZt3wdtEjjyCNshYy03lOWpyDViRM0";
|
||||
var refreshToken = "REFRESH_TOKEN";
|
||||
|
||||
// Simulate a server error response
|
||||
server
|
||||
.Given(Request.Create().WithPath("/token").UsingPost())
|
||||
.RespondWith(Response.Create()
|
||||
.WithStatusCode(HttpStatusCode.InternalServerError)
|
||||
.WithBody(@"{ ""error"": ""server_error"" }"));
|
||||
|
||||
var auth = new OidcTokenProvider(clientId, clientSecret, idpIssuerUrl, expiredIdToken, refreshToken);
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<KubernetesClientException>(
|
||||
() => auth.GetAuthenticationHeaderAsync(CancellationToken.None));
|
||||
exception.Message.Should().StartWith("Unable to refresh OIDC token.");
|
||||
exception.InnerException.Message.Should().Contain("500");
|
||||
|
||||
// Stop the server
|
||||
server.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user