Adds durable, identity-neutral, workflow-bound human tasks, and reconciles the REST surface with the approved Studio contract. - Flat summary/detail DTOs, a global capability descriptor, and workflow context captured at activation. - Scope is part of the list authorization predicate; manager decisions require manage:user-tasks; a denied command answers 404 so it cannot prove a task exists. - Guest sessions are task-scoped, action-allowlisted, and revoked when the task closes. Invitations resolve by token hash through the repository, wait in a Data Protection encrypted outbox, and are rate limited per caller. - Masked form values are disclosed only through an audited reveal command. - Store-specific concurrency failures are translated into a single UserTaskRevisionConflictException, so a concurrent edit returns the documented revision-conflict result behind any provider instead of a 500. EF Core (SQLite, SQL Server, PostgreSQL, MySQL, Oracle) and VNext persistence, hosted due/reconciliation/delivery workers, docs, and 49 tests. Note: this branch also carries two commits inherited from its branch point that are not part of User Tasks and are squashed in here — the revert-version allocation change from #7917 (WorkflowDefinitionPublisher.RevertVersionAsync now allocates from the last version rather than the latest) and an NU1903 package pin. Merged deliberately rather than rebased out.
46 lines
2.1 KiB
C#
46 lines
2.1 KiB
C#
using System.Security.Claims;
|
|
using Elsa.UserTasks.Contracts;
|
|
using Elsa.UserTasks.Models;
|
|
using Elsa.UserTasks.Options;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
namespace Elsa.UserTasks.Services;
|
|
|
|
public sealed class DefaultClaimsIdentityResolver(IOptions<UserTasksOptions> options) : IUserTaskIdentityResolver
|
|
{
|
|
private readonly UserTasksOptions _options = options.Value;
|
|
|
|
public ValueTask<UserTaskActor?> ResolveAsync(ClaimsPrincipal principal, CancellationToken cancellationToken = default)
|
|
{
|
|
var subjectId = principal.FindFirstValue(_options.SubjectClaimType)
|
|
?? principal.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
|
|
if (string.IsNullOrWhiteSpace(subjectId))
|
|
return ValueTask.FromResult<UserTaskActor?>(null);
|
|
|
|
var tenantId = principal.FindFirstValue(_options.TenantClaimType) ?? _options.DefaultTenantId;
|
|
var provider = principal.FindFirstValue(_options.ProviderClaimType) ?? _options.DefaultProvider;
|
|
var subject = new ParticipantReference(tenantId, provider, UserTaskParticipantType.User, subjectId,
|
|
principal.FindFirstValue(_options.DisplayNameClaimType) ?? principal.Identity?.Name);
|
|
var groups = _options.GroupClaimTypes
|
|
.SelectMany(type => principal.FindAll(type))
|
|
.Select(x => x.Value)
|
|
.Where(value => !string.IsNullOrWhiteSpace(value))
|
|
.Distinct(StringComparer.Ordinal)
|
|
.Select(id => new ParticipantReference(tenantId, provider, UserTaskParticipantType.Group, id))
|
|
.ToArray();
|
|
var permissions = _options.PermissionClaimTypes
|
|
.SelectMany(type => principal.FindAll(type))
|
|
.Select(x => x.Value)
|
|
.Where(value => !string.IsNullOrWhiteSpace(value))
|
|
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
|
|
|
return ValueTask.FromResult<UserTaskActor?>(new UserTaskActor(subject, groups, subject.DisplayName)
|
|
{
|
|
IsManager = principal.IsInRole("user-tasks:manager")
|
|
|| permissions.Contains("manage:user-tasks"),
|
|
Permissions = permissions
|
|
});
|
|
}
|
|
}
|