< Summary

Information
Class: Elsa.AI.Host.Endpoints.AI.AIHttpContextIdentity
Assembly: Elsa.AI.Host
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.AI.Host/Endpoints/AI/AIHttpContextIdentity.cs
Line coverage
88%
Covered lines: 24
Uncovered lines: 3
Coverable lines: 27
Total lines: 64
Line coverage: 88.8%
Branch coverage
86%
Covered branches: 40
Total branches: 46
Branch coverage: 86.9%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
GetActorId(...)92.85%1414100%
GetTenantId(...)100%1616100%
GetPermissions(...)100%44100%
GetAuthorizedAgent(...)100%66100%
HasRequiredPermissions(...)16.66%14640%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.AI.Host/Endpoints/AI/AIHttpContextIdentity.cs

#LineLine coverage
 1using System.Security.Claims;
 2using Elsa.AI.Host.Options;
 3using Elsa.Authorization;
 4using Elsa.Common.Multitenancy;
 5using Microsoft.AspNetCore.Http;
 6using Microsoft.Extensions.DependencyInjection;
 7
 8namespace Elsa.AI.Host.Endpoints.AI;
 9
 10internal static class AIHttpContextIdentity
 11{
 12    private const string TenantIdClaimType = "http://schemas.microsoft.com/identity/claims/tenantid";
 13
 14    public static string GetActorId(HttpContext? context) =>
 1115        context?.User.FindFirstValue(ClaimTypes.NameIdentifier) ??
 1116        context?.User.FindFirstValue("sub") ??
 1117        context?.User.Identity?.Name ??
 1118        "anonymous";
 19
 20    public static string? GetTenantId(HttpContext? context)
 21    {
 1122        var tenantAccessor = context?.RequestServices?.GetService<ITenantAccessor>();
 1123        if (tenantAccessor != null)
 124            return tenantAccessor.TenantId;
 25
 1026        return context?.User.FindFirstValue(TenantIdClaimType) ??
 1027               context?.User.FindFirstValue("tenant_id") ??
 1028               context?.User.FindFirstValue("tenantId");
 29    }
 30
 31    public static ICollection<string> GetPermissions(HttpContext? context) =>
 1132        context?.User
 1133            .FindAll(PermissionNames.ClaimType)
 134            .Select(x => x.Value)
 135            .Where(x => !string.IsNullOrWhiteSpace(x))
 1136            .Distinct(StringComparer.OrdinalIgnoreCase)
 1137            .ToList() ?? [];
 38
 39    public static string? GetAuthorizedAgent(string? requestedAgent, AIHostOptions options, ClaimsPrincipal? user)
 40    {
 1141        if (string.IsNullOrWhiteSpace(requestedAgent))
 842            return null;
 43
 644        var agent = options.Agents.FirstOrDefault(x => string.Equals(x.Name, requestedAgent, StringComparison.OrdinalIgn
 345        if (agent == null || !HasRequiredPermissions(agent.Permissions, user))
 146            return null;
 47
 248        return agent.Name;
 49    }
 50
 51    // Routed through the shared evaluator so a wildcard grant such as ai/*:execute reaches an agent's declared
 52    // permissions, and so the comparison is ordinal like every other site in the model. The previous
 53    // case-insensitive exact-set containment did neither: it admitted casing the rest of the model rejects,
 54    // while refusing the wildcards the rest of the model honours.
 55    private static bool HasRequiredPermissions(ICollection<string> requiredPermissions, ClaimsPrincipal? user)
 56    {
 257        if (requiredPermissions.Count == 0)
 258            return true;
 059        if (user is null)
 060            return false;
 61
 062        return requiredPermissions.All(x => Permission.TryParse(x, out var required) && PermissionEvaluator.Shared.HasPe
 63    }
 64}