elsa-core/test/unit/Elsa.SasTokens.UnitTests/Contracts/DataProtectorTokenServiceTests.cs
Sipke Schoorstra b4947fbf00
[codex] Harden initial security remediation slice (#7495)
* Harden initial security remediation slice

* Address security review feedback

* Handle null resume request bodies

* Address security review follow-ups

* Address PR review security comments

* Address review comments for zip cache and resume parsing

Agent-Logs-Url: https://github.com/elsa-workflows/elsa-core/sessions/23734885-ebba-4a37-b888-1ded4cc7cff5

Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com>

* Tighten zip cache path validation

Agent-Logs-Url: https://github.com/elsa-workflows/elsa-core/sessions/23734885-ebba-4a37-b888-1ded4cc7cff5

Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com>

* Address ZipManager code quality review

* Fix ZipManager test options reference

* Harden cached zip path validation

* Address PR review hardening feedback

* Allow dotted zip download correlation ids

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com>
2026-05-22 00:45:47 +02:00

112 lines
3.9 KiB
C#

using System.Text.Json;
using Elsa.SasTokens.Contracts;
using Microsoft.AspNetCore.DataProtection;
namespace Elsa.SasTokens.UnitTests.Contracts;
public class DataProtectorTokenServiceTests : IDisposable
{
private readonly string _keyDirectory = Path.Join(Path.GetTempPath(), Path.GetRandomFileName());
private readonly IDataProtectionProvider _dataProtectionProvider;
private readonly DataProtectorTokenService _service;
public DataProtectorTokenServiceTests()
{
Directory.CreateDirectory(_keyDirectory);
_dataProtectionProvider = DataProtectionProvider.Create(new DirectoryInfo(_keyDirectory));
_service = new DataProtectorTokenService(_dataProtectionProvider);
}
[Fact]
public void DecryptToken_CreatedWithLifetimeBeforeExpiration_ReturnsPayload()
{
var payload = new TokenPayload("workflow-instance-1", "bookmark-1");
var token = _service.CreateToken(payload, TimeSpan.FromMinutes(5));
var result = _service.DecryptToken<TokenPayload>(token);
Assert.StartsWith("v1.tl.", token);
AssertPayload(payload, result);
}
[Fact]
public void TryDecryptToken_CreatedWithPastExpiration_ReturnsFalse()
{
var payload = new TokenPayload("workflow-instance-1", "bookmark-1");
var token = _service.CreateToken(payload, DateTimeOffset.UtcNow.AddMinutes(-1));
var result = _service.TryDecryptToken<TokenPayload>(token, out _);
Assert.False(result);
}
[Fact]
public void TryDecryptToken_CreatedWithoutExpiration_ReturnsPayload()
{
var payload = new TokenPayload("workflow-instance-1", "bookmark-1");
var token = _service.CreateToken(payload);
var result = _service.TryDecryptToken<TokenPayload>(token, out var decryptedPayload);
Assert.StartsWith("v1.ne.", token);
Assert.True(result);
AssertPayload(payload, decryptedPayload);
}
[Fact]
public void TryDecryptToken_LegacyTimeLimitedTokenBeforeExpiration_ReturnsPayload()
{
var payload = new TokenPayload("workflow-instance-1", "bookmark-1");
var json = JsonSerializer.Serialize(payload);
var token = _dataProtectionProvider.CreateProtector("Elsa Tokens").ToTimeLimitedDataProtector().Protect(json, TimeSpan.FromMinutes(5));
var result = _service.TryDecryptToken<TokenPayload>(token, out var decryptedPayload);
Assert.True(result);
AssertPayload(payload, decryptedPayload);
}
[Fact]
public void TryDecryptToken_LegacyNonExpiringToken_ReturnsPayload()
{
var payload = new TokenPayload("workflow-instance-1", "bookmark-1");
var json = JsonSerializer.Serialize(payload);
var token = _dataProtectionProvider.CreateProtector("Elsa Tokens").Protect(json);
var result = _service.TryDecryptToken<TokenPayload>(token, out var decryptedPayload);
Assert.True(result);
AssertPayload(payload, decryptedPayload);
}
[Fact]
public void TryDecryptToken_StringPayloadCreatedWithPastExpiration_ReturnsFalse()
{
var token = _service.CreateToken("payload", DateTimeOffset.UtcNow.AddMinutes(-1));
var result = _service.TryDecryptToken<string>(token, out _);
Assert.False(result);
}
public void Dispose()
{
try
{
if (Directory.Exists(_keyDirectory))
Directory.Delete(_keyDirectory, true);
}
catch (Exception e) when (e is IOException or UnauthorizedAccessException)
{
// Cleanup failures should not fail token behavior tests.
}
}
private static void AssertPayload(TokenPayload expected, TokenPayload actual)
{
Assert.Equal(expected.WorkflowInstanceId, actual.WorkflowInstanceId);
Assert.Equal(expected.BookmarkId, actual.BookmarkId);
}
private record TokenPayload(string WorkflowInstanceId, string BookmarkId);
}