Fix external authentication connection contracts
This commit is contained in:
parent
75a3216ae8
commit
eb07aa2cde
|
|
@ -6,6 +6,7 @@ using Elsa.Dashboard.Api.ShellFeatures;
|
|||
using Elsa.Diagnostics.ConsoleLogs.Dashboard.ShellFeatures;
|
||||
using Elsa.Diagnostics.StructuredLogs.Dashboard.ShellFeatures;
|
||||
using Elsa.ExternalAuthentication.OpenIdConnect.ShellFeatures;
|
||||
using Elsa.ExternalAuthentication.Secrets.ShellFeatures;
|
||||
using Elsa.ModularServer.Web;
|
||||
using Elsa.ModularServer.Web.Catalog;
|
||||
using Elsa.Platform.Integration.ShellFeatures;
|
||||
|
|
@ -82,6 +83,7 @@ builder.AddShells(shells => shells
|
|||
typeof(DistributedRuntimeFeature),
|
||||
typeof(ElsaPlatformIntegrationFeature),
|
||||
typeof(OpenIdConnectExternalAuthenticationFeature),
|
||||
typeof(ElsaSecretsExternalAuthenticationFeature),
|
||||
typeof(DashboardApiFeature),
|
||||
typeof(WorkflowRuntimeDashboardFeature),
|
||||
typeof(ConsoleLogsDashboardFeature),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
using CShells.Features;
|
||||
using Elsa.ExternalAuthentication.ShellFeatures;
|
||||
using Elsa.Platform.PackageManifest.Generator.Hints;
|
||||
using Elsa.Secrets.ShellFeatures;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.ExternalAuthentication.Secrets.ShellFeatures;
|
||||
|
||||
[ManifestFeatureCategory("Identity")]
|
||||
[ManifestFeatureCategory("Security")]
|
||||
[ShellFeature(
|
||||
DisplayName = "Elsa Secrets External Authentication",
|
||||
Description = "Provides managed External Authentication secret bindings backed by Elsa Secrets.",
|
||||
DependsOn = [typeof(ExternalAuthenticationShellFeature), typeof(SecretsFeature)])]
|
||||
[UsedImplicitly]
|
||||
public sealed class ElsaSecretsExternalAuthenticationFeature : IShellFeature
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddElsaSecretsExternalAuthentication();
|
||||
}
|
||||
}
|
||||
|
|
@ -116,7 +116,7 @@ internal sealed class ConnectionResponse
|
|||
public PolicySelection? UnlinkedPolicy { get; init; }
|
||||
public IReadOnlyCollection<GrantSourceSelection> PermissionGrantSources { get; init; } = [];
|
||||
public ClaimProjection ClaimProjection { get; init; } = ClaimProjection.Empty;
|
||||
public UpstreamLogoutMode UpstreamLogoutMode { get; init; }
|
||||
public string UpstreamLogoutMode { get; init; } = null!;
|
||||
public long Revision { get; init; }
|
||||
public string MaterialRevision { get; init; } = null!;
|
||||
public ConnectionObservationResponse? LatestObservation { get; init; }
|
||||
|
|
@ -165,7 +165,7 @@ internal sealed class ConnectionResponse
|
|||
UnlinkedPolicy = effective.Connection.UnlinkedPolicy,
|
||||
PermissionGrantSources = effective.Connection.PermissionGrantSources.ToArray(),
|
||||
ClaimProjection = effective.Connection.ClaimProjection,
|
||||
UpstreamLogoutMode = effective.Connection.UpstreamLogoutMode,
|
||||
UpstreamLogoutMode = FormatUpstreamLogoutMode(effective.Connection.UpstreamLogoutMode),
|
||||
Revision = effective.Connection.Revision,
|
||||
MaterialRevision = effective.Connection.MaterialRevision,
|
||||
LatestObservation = observation is null
|
||||
|
|
@ -179,6 +179,14 @@ internal sealed class ConnectionResponse
|
|||
observation.Summary)
|
||||
};
|
||||
}
|
||||
|
||||
private static string FormatUpstreamLogoutMode(UpstreamLogoutMode mode) => mode switch
|
||||
{
|
||||
Elsa.ExternalAuthentication.Models.UpstreamLogoutMode.Disabled => "disabled",
|
||||
Elsa.ExternalAuthentication.Models.UpstreamLogoutMode.UserChoice => "user-choice",
|
||||
Elsa.ExternalAuthentication.Models.UpstreamLogoutMode.Always => "always",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, "The upstream logout mode is not supported.")
|
||||
};
|
||||
}
|
||||
|
||||
internal sealed record ConnectionObservationResponse(string Status, DateTimeOffset ObservedAt, string TestedMaterialRevision, bool IsStale, string Category, string Summary);
|
||||
|
|
|
|||
|
|
@ -162,6 +162,19 @@ public class ConnectionManagementTests : IAsyncLifetime
|
|||
Assert.False(restoredConnection.EnabledIntent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConnectionResponseEmitsCanonicalUpstreamLogoutModeString()
|
||||
{
|
||||
var response = await _client!.PostAsJsonAsync(
|
||||
"/external-authentication/connections",
|
||||
CreateRequest("user-choice-logout", upstreamLogoutMode: "user-choice"));
|
||||
var body = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
|
||||
|
||||
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
|
||||
Assert.Equal(JsonValueKind.String, body.RootElement.GetProperty("upstreamLogoutMode").ValueKind);
|
||||
Assert.Equal("user-choice", body.RootElement.GetProperty("upstreamLogoutMode").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConfigurationConnectionIsReadOnlyAndBlocksSameScopeKeyCreation()
|
||||
{
|
||||
|
|
@ -523,7 +536,7 @@ public class ConnectionManagementTests : IAsyncLifetime
|
|||
Assert.Equal(new[] { "workflow-user" }, _roleAuthorizationService.LastRequestedRoleIds);
|
||||
}
|
||||
|
||||
private static object CreateRequest(string key, object? scope = null, string displayName = "Contoso", object? settings = null, bool confirmUnsafeSettings = false, object? unlinkedPolicy = null) => new
|
||||
private static object CreateRequest(string key, object? scope = null, string displayName = "Contoso", object? settings = null, bool confirmUnsafeSettings = false, object? unlinkedPolicy = null, string upstreamLogoutMode = "disabled") => new
|
||||
{
|
||||
key,
|
||||
scope = scope ?? new { kind = "host" },
|
||||
|
|
@ -533,7 +546,7 @@ public class ConnectionManagementTests : IAsyncLifetime
|
|||
displayName,
|
||||
order = 10,
|
||||
claimProjection = new { allowedClaimTypes = Array.Empty<string>(), redactedClaimTypes = Array.Empty<string>(), maximumClaimCount = 0, maximumValueLength = 0, maximumTotalBytes = 0 },
|
||||
upstreamLogoutMode = "disabled",
|
||||
upstreamLogoutMode,
|
||||
confirmUnsafeSettings,
|
||||
unlinkedPolicy
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
using Elsa.ExternalAuthentication.Contracts;
|
||||
using Elsa.ExternalAuthentication.Secrets.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ElsaSecretsExternalAuthenticationShellFeature = Elsa.ExternalAuthentication.Secrets.ShellFeatures.ElsaSecretsExternalAuthenticationFeature;
|
||||
|
||||
namespace Elsa.ExternalAuthentication.UnitTests.Features;
|
||||
|
||||
public class ElsaSecretsExternalAuthenticationFeatureTests
|
||||
{
|
||||
[Fact]
|
||||
public void ShellFeatureRegistersTheManagedSecretBridge()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
var feature = new ElsaSecretsExternalAuthenticationShellFeature();
|
||||
|
||||
feature.ConfigureServices(services);
|
||||
|
||||
Assert.Contains(services, descriptor =>
|
||||
descriptor.ServiceType == typeof(ISecretBindingResolver) &&
|
||||
descriptor.ImplementationType == typeof(ElsaSecretBindingResolver));
|
||||
Assert.Contains(services, descriptor =>
|
||||
descriptor.ServiceType == typeof(IManagedSecretBindingWriter) &&
|
||||
descriptor.ImplementationType == typeof(ElsaSecretBindingResolver));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue