File: Configuration\PostConfigureDashboardOptions.cs
Web Access
Project: src\src\Aspire.Dashboard\Aspire.Dashboard.csproj (Aspire.Dashboard)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Aspire.Hosting;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
 
namespace Aspire.Dashboard.Configuration;
 
public sealed class PostConfigureDashboardOptions : IPostConfigureOptions<DashboardOptions>
{
    private readonly IConfiguration _configuration;
    private readonly ILogger _logger;
 
    public PostConfigureDashboardOptions(IConfiguration configuration) : this(configuration, NullLogger<PostConfigureDashboardOptions>.Instance)
    {
    }
 
    public PostConfigureDashboardOptions(IConfiguration configuration, ILogger<PostConfigureDashboardOptions> logger)
    {
        _configuration = configuration;
        _logger = logger;
    }
 
    public void PostConfigure(string? name, DashboardOptions options)
    {
        _logger.LogDebug($"PostConfigure {nameof(DashboardOptions)} with name '{name}'.");
 
        if (_configuration[DashboardConfigNames.DashboardApplicationName.EnvVarName] is { Length: > 0 } applicationName)
        {
            options.ApplicationName = applicationName;
        }
 
        if (_configuration[DashboardConfigNames.DashboardDataDirectoryName.EnvVarName] is { Length: > 0 } dataDirectory)
        {
            options.Data.Directory = dataDirectory;
        }
 
        if (_configuration[DashboardConfigNames.DashboardPersistenceModeName.EnvVarName] is { Length: > 0 } persistenceMode)
        {
            if (Enum.TryParse<DashboardPersistenceMode>(persistenceMode, ignoreCase: true, out var parsedPersistenceMode) &&
                Enum.IsDefined(parsedPersistenceMode))
            {
                options.Data.PersistenceMode = parsedPersistenceMode;
                options.Data.PersistenceModeParseError = null;
            }
            else
            {
                options.Data.PersistenceModeParseError = $"Failed to parse dashboard persistence mode '{persistenceMode}'. Possible values: {string.Join(", ", typeof(DashboardPersistenceMode).GetEnumNames())}.";
            }
        }
 
        // Copy aliased config values to the strongly typed options.
        if (_configuration.GetString(DashboardConfigNames.DashboardOtlpGrpcUrlName.ConfigKey,
                                     DashboardConfigNames.Legacy.DashboardOtlpGrpcUrlName.ConfigKey, fallbackOnEmpty: true) is { } otlpGrpcUrl)
        {
            options.Otlp.GrpcEndpointUrl = otlpGrpcUrl;
        }
 
        // Copy aliased config values to the strongly typed options.
        if (_configuration.GetString(DashboardConfigNames.DashboardOtlpHttpUrlName.ConfigKey,
                                     DashboardConfigNames.Legacy.DashboardOtlpHttpUrlName.ConfigKey, fallbackOnEmpty: true) is { } otlpHttpUrl)
        {
            options.Otlp.HttpEndpointUrl = otlpHttpUrl;
        }
 
        if (_configuration[DashboardConfigNames.DashboardFrontendUrlName.ConfigKey] is { Length: > 0 } frontendUrls)
        {
            options.Frontend.EndpointUrls = frontendUrls;
        }
 
        if (_configuration.GetString(DashboardConfigNames.ResourceServiceUrlName.ConfigKey,
                                     DashboardConfigNames.Legacy.ResourceServiceUrlName.ConfigKey, fallbackOnEmpty: true) is { } resourceServiceUrl)
        {
            options.ResourceServiceClient.Url = resourceServiceUrl;
        }
 
        if (_configuration.GetBool(DashboardConfigNames.DashboardUnsecuredAllowAnonymousName.ConfigKey,
                                   DashboardConfigNames.Legacy.DashboardUnsecuredAllowAnonymousName.ConfigKey) ?? false)
        {
            // This setting explicitly opts into unsecured endpoints. See the security considerations at
            // https://aspire.dev/dashboard/security-considerations/ before enabling it outside local development.
            // When the corresponding endpoints are enabled, the dashboard logs warnings and displays a warning
            // in the UI to inform users about the risks of anonymous access.
            options.Frontend.AuthMode = FrontendAuthMode.Unsecured;
            options.Otlp.AuthMode = OtlpAuthMode.Unsecured;
            options.Api.AuthMode = ApiAuthMode.Unsecured;
        }
        else
        {
            options.Frontend.AuthMode ??= FrontendAuthMode.BrowserToken;
            // OTLP is unsecured by default for local development. See the security considerations at
            // https://aspire.dev/dashboard/security-considerations/ when exposing the endpoint outside a trusted environment.
            options.Otlp.AuthMode ??= OtlpAuthMode.Unsecured;
            options.Api.AuthMode ??= ApiAuthMode.ApiKey;
        }
 
        if (options.Frontend.AuthMode == FrontendAuthMode.BrowserToken && string.IsNullOrEmpty(options.Frontend.BrowserToken))
        {
            var token = TokenGenerator.GenerateToken();
 
            // Set the generated token in configuration. This is required because options could be created multiple times
            // (at startup, after CI is created, after options change). Setting the token in configuration makes it consistent.
            _configuration[DashboardConfigNames.DashboardFrontendBrowserTokenName.ConfigKey] = token;
            options.Frontend.BrowserToken = token;
        }
 
        if (options.Api.AuthMode == ApiAuthMode.ApiKey && string.IsNullOrEmpty(options.Api.PrimaryApiKey))
        {
            var apiKey = TokenGenerator.GenerateToken();
 
            // Set the generated API key in configuration. This is required because options could be created multiple times
            // (at startup, after CI is created, after options change). Setting the key in configuration makes it consistent.
            _configuration[DashboardConfigNames.DashboardApiPrimaryApiKeyName.ConfigKey] = apiKey;
            options.Api.PrimaryApiKey = apiKey;
        }
 
        // DashboardAspireApiDisabledName takes precedence over DashboardAspireApiEnabledName.
        if (_configuration.GetBool(DashboardConfigNames.DashboardAspireApiDisabledName.ConfigKey) is { } apiDisabled)
        {
            options.Api.Disabled ??= apiDisabled;
        }
        else if (_configuration.GetBool(DashboardConfigNames.DashboardAspireApiEnabledName.ConfigKey) is { } apiEnabled)
        {
#pragma warning disable CS0618 // Type or member is obsolete
            options.Api.Enabled ??= apiEnabled;
#pragma warning restore CS0618
        }
 
        options.Api.Disabled ??= false;
 
        if (_configuration.GetBool(DashboardConfigNames.Legacy.DashboardOtlpSuppressUnsecuredTelemetryMessageName.ConfigKey) is { } suppressUnsecuredTelemetryMessage)
        {
            options.Otlp.SuppressUnsecuredMessage = suppressUnsecuredTelemetryMessage;
        }
    }
}