< Summary

Information
Class: Elsa.ExternalAuthentication.Endpoints.Previews.AuthorizePreview
Assembly: Elsa.ExternalAuthentication
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.ExternalAuthentication/Endpoints/Previews/PreviewSignInEndpoints.cs
Line coverage
100%
Covered lines: 11
Uncovered lines: 0
Coverable lines: 11
Total lines: 122
Line coverage: 100%
Branch coverage
100%
Covered branches: 2
Total branches: 2
Branch coverage: 100%
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()100%22100%

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
 13internal sealed class InitiatePreview(PreviewSignInService previews, ITenantAccessor tenantAccessor) : ElsaEndpointWitho
 14{
 15    public override void Configure()
 16    {
 17        Post("/external-authentication/connections/{connectionId}/preview");
 18        ConfigurePermissions(ExternalAuthenticationPermissions.ConnectionsPreview);
 19    }
 20
 21    public override async Task HandleAsync(CancellationToken cancellationToken)
 22    {
 23        if (!Endpoints.Connections.ConnectionEndpointSupport.TryGetExpectedRevision(HttpContext, out var revision))
 24        {
 25            HttpContext.Response.StatusCode = StatusCodes.Status428PreconditionRequired;
 26            return;
 27        }
 28        var result = await previews.InitiateAsync(Route<string>("connectionId")!, revision, tenantAccessor.TenantId, Use
 29        switch (result)
 30        {
 31            case PreviewInitiationResult.Started(var handle, var expiresAt):
 32                await HttpContext.Response.WriteAsJsonAsync(new PreviewInitiationResponse(BuildAuthorizePath(HttpContext
 33                return;
 34            case PreviewInitiationResult.PreconditionFailed(var currentRevision):
 35                HttpContext.Response.StatusCode = StatusCodes.Status412PreconditionFailed;
 36                await HttpContext.Response.WriteAsJsonAsync(new { error = "precondition_failed", currentRevision }, canc
 37                return;
 38            case PreviewInitiationResult.NotFound:
 39                HttpContext.Response.StatusCode = StatusCodes.Status404NotFound;
 40                return;
 41            default:
 42                HttpContext.Response.StatusCode = StatusCodes.Status403Forbidden;
 43                return;
 44        }
 45    }
 46
 47    internal static string BuildAuthorizePath(PathString pathBase, PathString requestPath, string handle)
 48    {
 49        const string routeMarker = "/external-authentication/connections/";
 50        var requestPathValue = requestPath.Value ?? string.Empty;
 51        var markerIndex = requestPathValue.LastIndexOf(routeMarker, StringComparison.OrdinalIgnoreCase);
 52        var routePrefix = markerIndex >= 0 ? requestPathValue[..markerIndex].TrimEnd('/') : string.Empty;
 53        var applicationPrefix = pathBase.Value?.TrimEnd('/') ?? string.Empty;
 54        return $"{applicationPrefix}{routePrefix}/external-authentication/previews/{Uri.EscapeDataString(handle)}/author
 55    }
 56}
 57
 458internal sealed class AuthorizePreview(PreviewSignInService previews) : ElsaEndpointWithoutRequest
 59{
 60    public override void Configure()
 61    {
 262        Get("/external-authentication/previews/{previewHandle}/authorize");
 263        AllowAnonymous();
 464        Options(x => x.RequireRateLimiting(ExternalAuthenticationRateLimitPolicyNames.ExternalInitiation));
 265    }
 66
 67    public override async Task HandleAsync(CancellationToken cancellationToken)
 68    {
 269        var result = await previews.AuthorizeAsync(Route<string>("previewHandle")!, cancellationToken);
 270        if (result is PreviewAuthorizeResult.Redirect(var navigationUri))
 71        {
 172            await Send.RedirectAsync(navigationUri.ToString(), allowRemoteRedirects: true);
 173            return;
 74        }
 175        await Send.StatusCodeAsync(StatusCodes.Status410Gone, cancellationToken);
 276    }
 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}