< Summary

Information
Class: Elsa.Identity.Services.PermissionStampValidator
Assembly: Elsa.Identity
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Identity/Services/PermissionStampValidator.cs
Line coverage
96%
Covered lines: 24
Uncovered lines: 1
Coverable lines: 25
Total lines: 58
Line coverage: 96%
Branch coverage
71%
Covered branches: 10
Total branches: 14
Branch coverage: 71.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
IsCurrentAsync()75%121293.33%
<IsCurrentAsync()50%22100%

File(s)

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

#LineLine coverage
 1using System.Security.Claims;
 2using Elsa.Common.Multitenancy;
 3using Elsa.Identity.Contracts;
 4using Elsa.Identity.Models;
 5using Elsa.Identity.Options;
 6using JetBrains.Annotations;
 7using Microsoft.Extensions.Caching.Memory;
 8using Microsoft.Extensions.Options;
 9
 10namespace Elsa.Identity.Services;
 11
 12/// <summary>Rejects a token whose permission stamp no longer matches the user's current grants.</summary>
 13[UsedImplicitly]
 614public class PermissionStampValidator(
 615    IUserProvider userProvider,
 616    IPermissionStampCalculator calculator,
 617    IMemoryCache cache,
 618    ITenantAccessor tenantAccessor,
 619    IOptions<PermissionStampOptions> options)
 20{
 21    /// <summary>
 22    /// Whether <paramref name="principal"/> still carries a current stamp. Returns <c>true</c> when the
 23    /// stamp is disabled, and when a token predates the feature being turned on -- an absent stamp is not
 24    /// treated as a mismatch, so enabling it does not sign everyone out.
 25    /// </summary>
 26    public async ValueTask<bool> IsCurrentAsync(ClaimsPrincipal principal, CancellationToken cancellationToken = default
 27    {
 628        if (!options.Value.IsEnabled)
 129            return true;
 30
 531        var presented = principal.FindFirst(PermissionStampCalculator.ClaimType)?.Value;
 32
 533        if (string.IsNullOrWhiteSpace(presented))
 134            return true;
 35
 436        var userName = principal.Identity?.Name;
 37
 438        if (string.IsNullOrWhiteSpace(userName))
 039            return true;
 40
 41        // The cache key and the lookup are both tenant-scoped. User names are unique per tenant, not
 42        // globally, so keying on the name alone lets one tenant's cached stamp satisfy a revoked token
 43        // belonging to a same-named user in another tenant.
 444        var tenantId = tenantAccessor.TenantId;
 45
 446        var current = await cache.GetOrCreateAsync($"elsa:permission-stamp:{tenantId}:{userName}", async entry =>
 447        {
 448            entry.AbsoluteExpirationRelativeToNow = options.Value.CacheLifetime;
 449
 450            var user = await userProvider.FindAsync(new UserFilter { Name = userName, TenantId = tenantId }, cancellatio
 451
 452            return user is null ? null : await calculator.ComputeAsync(user, cancellationToken);
 853        });
 54
 55        // A user that cannot be resolved is not evidence of a stale token; leave that to authentication.
 456        return current is null || string.Equals(current, presented, StringComparison.Ordinal);
 657    }
 58}