< Summary

Information
Class: Elsa.Identity.HostedServices.IdentityBootstrapDiagnostic
Assembly: Elsa.Identity
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Identity/HostedServices/IdentityBootstrapDiagnostic.cs
Line coverage
100%
Covered lines: 26
Uncovered lines: 0
Coverable lines: 26
Total lines: 69
Line coverage: 100%
Branch coverage
100%
Covered branches: 6
Total branches: 6
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%
StartAsync()100%66100%
StopAsync(...)100%11100%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Identity/HostedServices/IdentityBootstrapDiagnostic.cs

#LineLine coverage
 1using Elsa.Identity.Contracts;
 2using Elsa.Identity.Options;
 3using JetBrains.Annotations;
 4using Microsoft.Extensions.DependencyInjection;
 5using Microsoft.Extensions.Hosting;
 6using Microsoft.Extensions.Logging;
 7using Microsoft.Extensions.Options;
 8
 9namespace Elsa.Identity.HostedServices;
 10
 11/// <summary>
 12/// Reports, at startup, an instance nobody can sign in to: an empty user store with no bootstrap configured.
 13/// </summary>
 14/// <remarks>
 15/// Bootstrapping the first identity is a chicken-and-egg problem — every management endpoint requires a
 16/// permission, which requires a role, which requires a user. Elsa answers it declaratively: configure
 17/// <see cref="DefaultAdminUserOptions"/> to seed an admin, or <see cref="AdminApiKeyOptions"/> to accept an
 18/// out-of-band key. Both work in a deployed environment, and both attach an identity to whatever the caller
 19/// then does.
 20///
 21/// This replaces the localhost permission grant that used to ride on the SecurityRoot policy. That grant
 22/// trusted network position, which is exactly the signal that stops meaning anything behind a reverse proxy,
 23/// inside a container, or across a port-forward — and it granted unauthenticated access, so the bootstrap
 24/// action had no identity to audit. What it did offer was a hint that something needed configuring; without
 25/// it, an unconfigured instance would answer every request with 403 and no explanation. This says so instead.
 26/// </remarks>
 27[UsedImplicitly]
 828public class IdentityBootstrapDiagnostic(
 829    IServiceScopeFactory scopeFactory,
 830    IOptions<DefaultAdminUserOptions> adminUserOptions,
 831    IOptions<AdminApiKeyOptions> adminApiKeyOptions,
 832    ILogger<IdentityBootstrapDiagnostic> logger) : IHostedService
 33{
 34    /// <inheritdoc />
 35    public async Task StartAsync(CancellationToken cancellationToken)
 36    {
 837        var admin = adminUserOptions.Value;
 838        var adminUserConfigured = !string.IsNullOrWhiteSpace(admin.AdminUserName) && !string.IsNullOrWhiteSpace(admin.Ad
 839        var apiKeyConfigured = !string.IsNullOrWhiteSpace(adminApiKeyOptions.Value.ApiKey);
 40
 841        if (adminUserConfigured || apiKeyConfigured)
 542            return;
 43
 44        try
 45        {
 346            using var scope = scopeFactory.CreateScope();
 347            var userStore = scope.ServiceProvider.GetRequiredService<IUserStore>();
 48
 349            if ((await userStore.FindManyAsync(new(), cancellationToken)).Any())
 150                return;
 51
 152            logger.LogError(
 153                "No users exist and no identity bootstrap is configured, so nothing can sign in and every " +
 154                "management endpoint will answer 403. Configure one of: (1) a seeded administrator via " +
 155                "UseDefaultAdmin(...) or the DefaultAdminUser configuration section, which creates the admin " +
 156                "role and user at startup and is idempotent; or (2) an admin API key via UseAdminApiKey(...) " +
 157                "or the AdminApiKey setting. Both work in a deployed environment.");
 158        }
 159        catch (Exception e)
 60        {
 61            // A store that cannot be read yet is not this check's problem to report; it will surface on its
 62            // own. Never let a diagnostic take the host down.
 163            logger.LogDebug(e, "Could not determine whether the user store is empty; skipping the bootstrap check.");
 164        }
 865    }
 66
 67    /// <inheritdoc />
 668    public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
 69}