80 lines
2.6 KiB
C#
80 lines
2.6 KiB
C#
using System.Security.Claims;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Microsoft.Extensions.Options;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Security.Jwt;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Security.Tests;
|
|
|
|
public sealed class JwtTokenServiceTests
|
|
{
|
|
private const string TestKey = "this-is-a-32-byte-test-signing-key!!";
|
|
|
|
private static JwtTokenService NewService(string key = TestKey, int expiryMinutes = 15) =>
|
|
new(Options.Create(new JwtOptions
|
|
{
|
|
SigningKey = key,
|
|
Issuer = "otopcua-test",
|
|
Audience = "otopcua-test",
|
|
ExpiryMinutes = expiryMinutes,
|
|
}), NullLogger<JwtTokenService>.Instance);
|
|
|
|
[Fact]
|
|
public void Short_signing_key_throws()
|
|
{
|
|
Should.Throw<InvalidOperationException>(() => NewService("too-short"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Issue_then_validate_roundtrips_claims()
|
|
{
|
|
var jwt = NewService();
|
|
var token = jwt.Issue("Joe User", "joe", new[] { "ReadOnly", "AlarmAck" });
|
|
|
|
jwt.TryValidate(token, out var principal).ShouldBeTrue();
|
|
principal.ShouldNotBeNull();
|
|
principal!.FindFirst(JwtTokenService.UsernameClaimType)!.Value.ShouldBe("joe");
|
|
principal.FindFirst(JwtTokenService.DisplayNameClaimType)!.Value.ShouldBe("Joe User");
|
|
principal.FindAll(JwtTokenService.RoleClaimType)
|
|
.Select(c => c.Value).ShouldBe(new[] { "ReadOnly", "AlarmAck" }, ignoreOrder: true);
|
|
}
|
|
|
|
[Fact]
|
|
public void Tampered_token_is_rejected()
|
|
{
|
|
var jwt = NewService();
|
|
var token = jwt.Issue("Joe", "joe", Array.Empty<string>());
|
|
|
|
// Corrupt the payload — flip a char in the middle segment.
|
|
var parts = token.Split('.');
|
|
parts[1] = parts[1][..^2] + "AA";
|
|
var tampered = string.Join('.', parts);
|
|
|
|
jwt.TryValidate(tampered, out var principal).ShouldBeFalse();
|
|
principal.ShouldBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void Expired_token_is_rejected()
|
|
{
|
|
// Issue with -1 min expiry so it's already past at validate time.
|
|
var jwt = NewService(expiryMinutes: -1);
|
|
var token = jwt.Issue("Joe", "joe", Array.Empty<string>());
|
|
|
|
jwt.TryValidate(token, out var principal).ShouldBeFalse();
|
|
principal.ShouldBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void Cross_key_token_is_rejected()
|
|
{
|
|
var issuer = NewService();
|
|
var token = issuer.Issue("Joe", "joe", Array.Empty<string>());
|
|
|
|
var different = NewService("a-different-32-byte-signing-key!!!");
|
|
different.TryValidate(token, out var principal).ShouldBeFalse();
|
|
principal.ShouldBeNull();
|
|
}
|
|
}
|