< Summary

Information
Class: Elsa.Identity.Services.PermissionStampCalculator
Assembly: Elsa.Identity
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Identity/Services/PermissionStampCalculator.cs
Line coverage
10%
Covered lines: 1
Uncovered lines: 9
Coverable lines: 10
Total lines: 49
Line coverage: 10%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
ComputeAsync()100%210%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Identity/Services/PermissionStampCalculator.cs

#LineLine coverage
 1using System.Security.Cryptography;
 2using System.Text;
 3using Elsa.Extensions;
 4using Elsa.Identity.Contracts;
 5using Elsa.Identity.Entities;
 6using JetBrains.Annotations;
 7
 8namespace Elsa.Identity.Services;
 9
 10/// <summary>Computes a stamp that changes whenever a user's effective grants change.</summary>
 11public interface IPermissionStampCalculator
 12{
 13    /// <summary>The current stamp for <paramref name="user"/>.</summary>
 14    ValueTask<string> ComputeAsync(User user, CancellationToken cancellationToken = default);
 15}
 16
 17/// <inheritdoc />
 18/// <remarks>
 19/// The stamp is <em>derived</em> from the user's roles and those roles' permissions rather than stored as
 20/// a counter on the user. That is deliberate: a stored counter would change the Identity schema and
 21/// require migrations across all five EF providers, which would make revocation tightening depend on the
 22/// tenancy milestone. A derived stamp needs no schema, and every node computes the same value from the
 23/// same store without any cross-node invalidation -- which matters, because Elsa has none.
 24///
 25/// It changes when a role is added to or removed from the user, and when any held role's permissions
 26/// change. It does not change when an unrelated role changes, so it is no broader than it needs to be.
 27/// </remarks>
 28[UsedImplicitly]
 529public class PermissionStampCalculator(IRoleProvider roleProvider) : IPermissionStampCalculator
 30{
 31    /// <summary>The claim carrying the stamp issued with a token.</summary>
 32    public const string ClaimType = "elsa:permission_stamp";
 33
 34    /// <inheritdoc />
 35    public async ValueTask<string> ComputeAsync(User user, CancellationToken cancellationToken = default)
 36    {
 037        var roles = (await roleProvider.FindByIdsAsync(user.Roles, cancellationToken)).ToList();
 38
 039        var material = string.Join(
 040            "\n",
 041            roles
 042                .OrderBy(x => x.Id, StringComparer.Ordinal)
 043                .Select(role => $"{role.Id}={string.Join(",", role.Permissions.OrderBy(x => x, StringComparer.Ordinal))}
 44
 045        var hash = SHA256.HashData(Encoding.UTF8.GetBytes(material));
 46
 047        return Convert.ToHexString(hash)[..16];
 048    }
 49}