< Summary

Information
Class: Elsa.ExternalAuthentication.Endpoints.Sessions.ExternalAuthenticationSessionListResponse
Assembly: Elsa.ExternalAuthentication
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.ExternalAuthentication/Endpoints/Sessions/ExternalAuthenticationSessionEndpoints.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 1
Coverable lines: 1
Total lines: 122
Line coverage: 0%
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
get_Items()100%210%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.ExternalAuthentication/Endpoints/Sessions/ExternalAuthenticationSessionEndpoints.cs

#LineLine coverage
 1using System.Security.Claims;
 2using Elsa.Authorization;
 3using System.Text;
 4using System.Text.Json;
 5using Elsa.Abstractions;
 6using Elsa.Common.Multitenancy;
 7using Elsa.ExternalAuthentication.Contracts;
 8using Elsa.ExternalAuthentication.Notifications;
 9using Elsa.ExternalAuthentication.Options;
 10using Elsa.ExternalAuthentication.Permissions;
 11using Elsa.ExternalAuthentication.Services;
 12using Microsoft.AspNetCore.Http;
 13using Microsoft.Extensions.Options;
 14
 15namespace Elsa.ExternalAuthentication.Endpoints.Sessions;
 16
 17internal sealed class ListExternalAuthenticationSessions(
 18    IExternalAuthenticationSessionStore sessions,
 19    ITenantAccessor tenantAccessor,
 20    IOptions<ExternalAuthenticationOptions> options) : ElsaEndpoint<ExternalAuthenticationSessionListRequest, ExternalAu
 21{
 22    public override void Configure()
 23    {
 24        Get("/external-authentication/sessions");
 25        RequirePermission(ExternalAuthenticationResourcePermissions.Sessions, CoreVerbs.View);
 26    }
 27
 28    public override async Task<ExternalAuthenticationSessionListResponse> ExecuteAsync(ExternalAuthenticationSessionList
 29    {
 30        if (!options.Value.Operations.EnableSessionAdministration)
 31        {
 32            HttpContext.Response.StatusCode = StatusCodes.Status404NotFound;
 33            return new([], null);
 34        }
 35        if (request.PageSize is < 1 or > 100 || !IsKnownStatus(request.Status) || !TryDecode(request.Cursor, out var cur
 36        {
 37            HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
 38            return new([], null);
 39        }
 40        var pageSize = request.PageSize ?? 100;
 41        var rows = (await sessions.FindAsync(new()
 42                { TenantId = tenantAccessor.TenantId, UserId = request.UserId, ConnectionKey = request.ConnectionKey, St
 43            .OrderBy(x => x.StartedAt).ThenBy(x => x.Id, StringComparer.Ordinal)
 44            .Where(x => cursor is null || Compare(new(x.StartedAt, x.Id), cursor) > 0)
 45            .Take(pageSize + 1).ToArray();
 46        var hasMore = rows.Length > pageSize;
 47        var page = hasMore ? rows[..^1] : rows;
 48        return new(page.Select(ExternalAuthenticationSessionDocument.From).ToArray(), hasMore ? Encode(new(page[^1].Star
 49    }
 50
 51    private static bool IsKnownStatus(string? status) => string.IsNullOrWhiteSpace(status) || status.Equals("active", St
 52    private static int Compare(SessionCursor left, SessionCursor right) => left.StartedAt != right.StartedAt ? left.Star
 53    internal static string Encode(SessionCursor cursor) => Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonSerializer.
 54    internal static bool TryDecode(string? value, out SessionCursor? cursor)
 55    {
 56        cursor = null;
 57        if (string.IsNullOrWhiteSpace(value)) return true;
 58        try
 59        {
 60            var padded = value.Replace('-', '+').Replace('_', '/');
 61            padded = padded.PadRight(padded.Length + (4 - padded.Length % 4) % 4, '=');
 62            cursor = JsonSerializer.Deserialize<SessionCursor>(Encoding.UTF8.GetString(Convert.FromBase64String(padded))
 63            return cursor is { Id.Length: > 0 and <= 256 };
 64        }
 65        catch (Exception exception) when (exception is FormatException or JsonException) { return false; }
 66    }
 67}
 68
 69internal sealed class RevokeExternalAuthenticationSession(
 70    IExternalAuthenticationSessionStore sessions,
 71    ITenantAccessor tenantAccessor,
 72    IOptions<ExternalAuthenticationOptions> options,
 73    ExternalAuthenticationSecurityNotifier notifier) : ElsaEndpoint<RevokeExternalAuthenticationSessionRequest>
 74{
 75    public override void Configure()
 76    {
 77        Delete("/external-authentication/sessions/{sessionId}");
 78        RequirePermission(ExternalAuthenticationResourcePermissions.Sessions, ExternalAuthenticationVerbs.Revoke);
 79    }
 80
 81    public override async Task HandleAsync(RevokeExternalAuthenticationSessionRequest request, CancellationToken cancell
 82    {
 83        if (!options.Value.Operations.EnableSessionAdministration)
 84        {
 85            HttpContext.Response.StatusCode = StatusCodes.Status404NotFound;
 86            return;
 87        }
 88        var session = await sessions.FindByIdAsync(Route<string>("sessionId")!, cancellationToken);
 89        if (session is null || !string.Equals(session.TenantId, tenantAccessor.TenantId, StringComparison.Ordinal))
 90        {
 91            HttpContext.Response.StatusCode = StatusCodes.Status404NotFound;
 92            return;
 93        }
 94        var revoked = await sessions.RevokeAsync(session.Id, string.IsNullOrWhiteSpace(request.Reason) ? "administrator_
 95        if (!revoked)
 96        {
 97            HttpContext.Response.StatusCode = StatusCodes.Status409Conflict;
 98            return;
 99        }
 100        await notifier.PublishAsync(new ExternalAuthenticationSessionRevoked(
 101            ExternalAuthenticationSecurityNotifier.Context(User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? User.Find
 102            session.Id,
 103            "administrator_revoked"), cancellationToken);
 104        HttpContext.Response.StatusCode = StatusCodes.Status204NoContent;
 105    }
 106}
 107
 108internal sealed class ExternalAuthenticationSessionListRequest
 109{
 110    public string? UserId { get; set; }
 111    public string? ConnectionKey { get; set; }
 112    public string? Status { get; set; }
 113    public string? Cursor { get; set; }
 114    public int? PageSize { get; set; }
 115}
 116internal sealed class RevokeExternalAuthenticationSessionRequest { public string? Reason { get; set; } }
 117internal sealed record SessionCursor(DateTimeOffset StartedAt, string Id);
 0118internal sealed record ExternalAuthenticationSessionListResponse(IReadOnlyCollection<ExternalAuthenticationSessionDocume
 119internal sealed record ExternalAuthenticationSessionDocument(string Id, string UserId, string TenantId, string Connectio
 120{
 121    public static ExternalAuthenticationSessionDocument From(Models.ExternalAuthenticationSession value) => new(value.Id
 122}

Methods/Properties

get_Items()