< Summary

Information
Class: Elsa.ExternalAuthentication.Endpoints.Previews.InitiatePreview
Assembly: Elsa.ExternalAuthentication
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.ExternalAuthentication/Endpoints/Previews/PreviewSignInEndpoints.cs
Line coverage
39%
Covered lines: 9
Uncovered lines: 14
Coverable lines: 23
Total lines: 122
Line coverage: 39.1%
Branch coverage
25%
Covered branches: 4
Total branches: 16
Branch coverage: 25%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
Configure()100%11100%
HandleAsync()0%7280%
BuildAuthorizePath(...)50%88100%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.ExternalAuthentication/Endpoints/Previews/PreviewSignInEndpoints.cs

#LineLine coverage
 1using Elsa.Abstractions;
 2using Elsa.Common.Multitenancy;
 3using Elsa.ExternalAuthentication.Constants;
 4using Elsa.ExternalAuthentication.Models;
 5using Elsa.ExternalAuthentication.Permissions;
 6using Elsa.ExternalAuthentication.Services;
 7using Microsoft.AspNetCore.Builder;
 8using Microsoft.AspNetCore.Http;
 9using Microsoft.AspNetCore.RateLimiting;
 10
 11namespace Elsa.ExternalAuthentication.Endpoints.Previews;
 12
 213internal sealed class InitiatePreview(PreviewSignInService previews, ITenantAccessor tenantAccessor) : ElsaEndpointWitho
 14{
 15    public override void Configure()
 16    {
 217        Post("/external-authentication/connections/{connectionId}/preview");
 218        ConfigurePermissions(ExternalAuthenticationPermissions.ConnectionsPreview);
 219    }
 20
 21    public override async Task HandleAsync(CancellationToken cancellationToken)
 22    {
 023        if (!Endpoints.Connections.ConnectionEndpointSupport.TryGetExpectedRevision(HttpContext, out var revision))
 24        {
 025            HttpContext.Response.StatusCode = StatusCodes.Status428PreconditionRequired;
 026            return;
 27        }
 028        var result = await previews.InitiateAsync(Route<string>("connectionId")!, revision, tenantAccessor.TenantId, Use
 29        switch (result)
 30        {
 31            case PreviewInitiationResult.Started(var handle, var expiresAt):
 032                await HttpContext.Response.WriteAsJsonAsync(new PreviewInitiationResponse(BuildAuthorizePath(HttpContext
 033                return;
 34            case PreviewInitiationResult.PreconditionFailed(var currentRevision):
 035                HttpContext.Response.StatusCode = StatusCodes.Status412PreconditionFailed;
 036                await HttpContext.Response.WriteAsJsonAsync(new { error = "precondition_failed", currentRevision }, canc
 037                return;
 38            case PreviewInitiationResult.NotFound:
 039                HttpContext.Response.StatusCode = StatusCodes.Status404NotFound;
 040                return;
 41            default:
 042                HttpContext.Response.StatusCode = StatusCodes.Status403Forbidden;
 043                return;
 44        }
 045    }
 46
 47    internal static string BuildAuthorizePath(PathString pathBase, PathString requestPath, string handle)
 48    {
 49        const string routeMarker = "/external-authentication/connections/";
 350        var requestPathValue = requestPath.Value ?? string.Empty;
 351        var markerIndex = requestPathValue.LastIndexOf(routeMarker, StringComparison.OrdinalIgnoreCase);
 352        var routePrefix = markerIndex >= 0 ? requestPathValue[..markerIndex].TrimEnd('/') : string.Empty;
 353        var applicationPrefix = pathBase.Value?.TrimEnd('/') ?? string.Empty;
 354        return $"{applicationPrefix}{routePrefix}/external-authentication/previews/{Uri.EscapeDataString(handle)}/author
 55    }
 56}
 57
 58internal sealed class AuthorizePreview(PreviewSignInService previews) : ElsaEndpointWithoutRequest
 59{
 60    public override void Configure()
 61    {
 62        Get("/external-authentication/previews/{previewHandle}/authorize");
 63        AllowAnonymous();
 64        Options(x => x.RequireRateLimiting(ExternalAuthenticationRateLimitPolicyNames.ExternalInitiation));
 65    }
 66
 67    public override async Task HandleAsync(CancellationToken cancellationToken)
 68    {
 69        var result = await previews.AuthorizeAsync(Route<string>("previewHandle")!, cancellationToken);
 70        if (result is PreviewAuthorizeResult.Redirect(var navigationUri))
 71        {
 72            await Send.RedirectAsync(navigationUri.ToString(), allowRemoteRedirects: true);
 73            return;
 74        }
 75        await Send.StatusCodeAsync(StatusCodes.Status410Gone, cancellationToken);
 76    }
 77}
 78
 79internal sealed class CompletePreview(PreviewSignInService previews) : ElsaEndpointWithoutRequest
 80{
 81    public override void Configure()
 82    {
 83        Get("/external-authentication/previews/callback/{connectionId}");
 84        AllowAnonymous();
 85        Options(x => x.RequireRateLimiting(ExternalAuthenticationRateLimitPolicyNames.ProviderCallback));
 86    }
 87
 88    public override async Task HandleAsync(CancellationToken cancellationToken)
 89    {
 90        var parameters = HttpContext.Request.Query.ToDictionary(x => x.Key, x => (IReadOnlyCollection<string>)x.Value.Wh
 91        var result = await previews.CompleteAsync(Route<string>("connectionId")!, HttpContext.Request.Query["state"].ToS
 92        HttpContext.Response.StatusCode = result is PreviewCallbackResult.Completed ? StatusCodes.Status200OK : StatusCo
 93        await HttpContext.Response.WriteAsJsonAsync(new { completed = result is PreviewCallbackResult.Completed }, cance
 94    }
 95}
 96
 97internal sealed class GetPreviewResult(PreviewSignInService previews, ITenantAccessor tenantAccessor) : ElsaEndpointWith
 98{
 99    public override void Configure()
 100    {
 101        Get("/external-authentication/previews/{previewHandle}");
 102        ConfigurePermissions(ExternalAuthenticationPermissions.ConnectionsPreview);
 103    }
 104
 105    public override async Task HandleAsync(CancellationToken cancellationToken)
 106    {
 107        var result = await previews.TakeResultAsync(Route<string>("previewHandle")!, tenantAccessor.TenantId, User, canc
 108        if (result is TakeResult<PreviewResult>.Taken { Value: var preview })
 109        {
 110            await HttpContext.Response.WriteAsJsonAsync(PreviewResultDocument.From(preview), cancellationToken);
 111            return;
 112        }
 113        var statusCode = result is TakeResult<PreviewResult>.Expired or TakeResult<PreviewResult>.AlreadyConsumed ? Stat
 114        await Send.StatusCodeAsync(statusCode, cancellationToken);
 115    }
 116}
 117
 118internal sealed record PreviewInitiationResponse(string NavigationUrl, DateTimeOffset ExpiresAt);
 119internal sealed record PreviewResultDocument(string ConnectionId, string MaterialRevision, string Issuer, string MaskedS
 120{
 121    public static PreviewResultDocument From(PreviewResult value) => new(value.ConnectionId, value.MaterialRevision, val
 122}