forked from mirrors/jellyfin-plugin-sso
a167d00b73
ci/woodpecker/pr/ci Pipeline was successful
Keys each in-flight OpenID flow in the server's Valkey/Redis connection so a login can start on one replica and finish on another. Falls back to an in-memory store when no multiplexer is registered.
83 lines
3.0 KiB
C#
83 lines
3.0 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using Duende.IdentityModel.OidcClient;
|
|
using Jellyfin.Plugin.SSO_Auth.Api;
|
|
using Jellyfin.Plugin.SSO_Auth.State;
|
|
using Xunit;
|
|
|
|
namespace Jellyfin.Plugin.SSO_Auth.Tests;
|
|
|
|
public class SsoStateSerializerTests
|
|
{
|
|
[Fact]
|
|
public void RoundTrip_PreservesEveryFieldTheFlowDependsOn()
|
|
{
|
|
var original = new TimedAuthorizeState(
|
|
new AuthorizeState
|
|
{
|
|
State = "state-value",
|
|
StartUrl = "https://idp.example/authorize?code_challenge=abc",
|
|
RedirectUri = "https://jellyfin.example/sso/OID/redirect/authentik",
|
|
CodeVerifier = "code-verifier-secret"
|
|
},
|
|
new DateTime(2026, 9, 19, 10, 30, 0, DateTimeKind.Utc))
|
|
{
|
|
Valid = true,
|
|
Username = "alice",
|
|
Admin = true,
|
|
IsLinking = true,
|
|
Folders = new List<string> { "movies", "shows" },
|
|
EnableLiveTv = true,
|
|
EnableLiveTvManagement = true,
|
|
AvatarURL = "https://idp.example/avatar/alice.png"
|
|
};
|
|
|
|
var restored = SsoStateSerializer.Deserialize(SsoStateSerializer.Serialize(original));
|
|
|
|
Assert.NotNull(restored);
|
|
Assert.Equal("state-value", restored.State.State);
|
|
Assert.Equal("https://idp.example/authorize?code_challenge=abc", restored.State.StartUrl);
|
|
Assert.Equal("https://jellyfin.example/sso/OID/redirect/authentik", restored.State.RedirectUri);
|
|
|
|
// The code verifier is required to complete the token exchange on the pod that handles the callback.
|
|
Assert.Equal("code-verifier-secret", restored.State.CodeVerifier);
|
|
|
|
Assert.Equal(original.Created, restored.Created);
|
|
Assert.True(restored.Valid);
|
|
Assert.Equal("alice", restored.Username);
|
|
Assert.True(restored.Admin);
|
|
Assert.True(restored.IsLinking);
|
|
Assert.Equal(new[] { "movies", "shows" }, restored.Folders);
|
|
Assert.True(restored.EnableLiveTv);
|
|
Assert.True(restored.EnableLiveTvManagement);
|
|
Assert.Equal("https://idp.example/avatar/alice.png", restored.AvatarURL);
|
|
}
|
|
|
|
[Fact]
|
|
public void RoundTrip_PreservesNulls()
|
|
{
|
|
var original = new TimedAuthorizeState(
|
|
new AuthorizeState { State = "state-value", CodeVerifier = null, RedirectUri = null, StartUrl = null },
|
|
DateTime.UtcNow);
|
|
|
|
var restored = SsoStateSerializer.Deserialize(SsoStateSerializer.Serialize(original));
|
|
|
|
Assert.NotNull(restored);
|
|
Assert.Equal("state-value", restored.State.State);
|
|
Assert.Null(restored.State.CodeVerifier);
|
|
Assert.Null(restored.State.RedirectUri);
|
|
Assert.Null(restored.Username);
|
|
Assert.Null(restored.AvatarURL);
|
|
Assert.Null(restored.Folders);
|
|
Assert.False(restored.Valid);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(null)]
|
|
[InlineData("")]
|
|
public void Deserialize_WithoutPayload_ReturnsNull(string payload)
|
|
{
|
|
Assert.Null(SsoStateSerializer.Deserialize(payload));
|
|
}
|
|
}
|