713 lines
29 KiB
C#
713 lines
29 KiB
C#
using Microsoft.Extensions.DependencyInjection;
|
|
using StellaOps.Cli.Services;
|
|
using StellaOps.Cli.Services.Models;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.CommandLine;
|
|
using System.Linq;
|
|
using System.Text.Json;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace StellaOps.Cli.Commands;
|
|
|
|
internal static class IdentityProviderCommandGroup
|
|
{
|
|
private static readonly JsonSerializerOptions JsonOutputOptions = new(JsonSerializerDefaults.Web)
|
|
{
|
|
WriteIndented = true
|
|
};
|
|
|
|
internal static Command BuildIdentityProviderCommand(
|
|
IServiceProvider services,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var idp = new Command("identity-providers", "Manage identity provider configurations.");
|
|
|
|
idp.Add(BuildListCommand(services, cancellationToken));
|
|
idp.Add(BuildShowCommand(services, cancellationToken));
|
|
idp.Add(BuildAddCommand(services, cancellationToken));
|
|
idp.Add(BuildUpdateCommand(services, cancellationToken));
|
|
idp.Add(BuildRemoveCommand(services, cancellationToken));
|
|
idp.Add(BuildTestCommand(services, cancellationToken));
|
|
idp.Add(BuildEnableCommand(services, cancellationToken));
|
|
idp.Add(BuildDisableCommand(services, cancellationToken));
|
|
idp.Add(BuildApplyCommand(services, cancellationToken));
|
|
|
|
return idp;
|
|
}
|
|
|
|
private static Command BuildListCommand(IServiceProvider services, CancellationToken cancellationToken)
|
|
{
|
|
var jsonOption = new Option<bool>("--json")
|
|
{
|
|
Description = "Emit machine-readable JSON output."
|
|
};
|
|
|
|
var list = new Command("list", "List all configured identity providers.");
|
|
list.Add(jsonOption);
|
|
|
|
list.SetAction(async (parseResult, _) =>
|
|
{
|
|
var emitJson = parseResult.GetValue(jsonOption);
|
|
var backend = services.GetRequiredService<IBackendOperationsClient>();
|
|
|
|
try
|
|
{
|
|
var providers = await backend.ListIdentityProvidersAsync(cancellationToken).ConfigureAwait(false);
|
|
|
|
if (emitJson)
|
|
{
|
|
Console.WriteLine(JsonSerializer.Serialize(providers, JsonOutputOptions));
|
|
return;
|
|
}
|
|
|
|
if (providers.Count == 0)
|
|
{
|
|
Console.WriteLine("No identity providers configured.");
|
|
return;
|
|
}
|
|
|
|
Console.WriteLine("Identity Providers");
|
|
Console.WriteLine("==================");
|
|
Console.WriteLine();
|
|
|
|
foreach (var provider in providers)
|
|
{
|
|
var status = provider.Enabled ? "enabled" : "disabled";
|
|
var health = provider.HealthStatus ?? "unknown";
|
|
Console.WriteLine($" {provider.Name,-25} {provider.Type,-10} [{status}] health={health} (id={provider.Id})");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.Error.WriteLine($"Error listing identity providers: {ex.Message}");
|
|
Environment.ExitCode = 1;
|
|
}
|
|
});
|
|
|
|
return list;
|
|
}
|
|
|
|
private static Command BuildShowCommand(IServiceProvider services, CancellationToken cancellationToken)
|
|
{
|
|
var nameArg = new Argument<string>("name")
|
|
{
|
|
Description = "Identity provider name or ID."
|
|
};
|
|
|
|
var jsonOption = new Option<bool>("--json")
|
|
{
|
|
Description = "Emit machine-readable JSON output."
|
|
};
|
|
|
|
var show = new Command("show", "Show identity provider details.");
|
|
show.Add(nameArg);
|
|
show.Add(jsonOption);
|
|
|
|
show.SetAction(async (parseResult, _) =>
|
|
{
|
|
var name = parseResult.GetValue(nameArg) ?? string.Empty;
|
|
var emitJson = parseResult.GetValue(jsonOption);
|
|
var backend = services.GetRequiredService<IBackendOperationsClient>();
|
|
|
|
try
|
|
{
|
|
var provider = await backend.GetIdentityProviderAsync(name, cancellationToken).ConfigureAwait(false);
|
|
if (provider is null)
|
|
{
|
|
Console.Error.WriteLine($"Identity provider '{name}' not found.");
|
|
Environment.ExitCode = 1;
|
|
return;
|
|
}
|
|
|
|
if (emitJson)
|
|
{
|
|
Console.WriteLine(JsonSerializer.Serialize(provider, JsonOutputOptions));
|
|
return;
|
|
}
|
|
|
|
Console.WriteLine($"Name: {provider.Name}");
|
|
Console.WriteLine($"Type: {provider.Type}");
|
|
Console.WriteLine($"Enabled: {provider.Enabled}");
|
|
Console.WriteLine($"Health: {provider.HealthStatus ?? "unknown"}");
|
|
Console.WriteLine($"Description: {provider.Description ?? "(none)"}");
|
|
Console.WriteLine($"ID: {provider.Id}");
|
|
Console.WriteLine($"Created: {provider.CreatedAt:u}");
|
|
Console.WriteLine($"Updated: {provider.UpdatedAt:u}");
|
|
|
|
if (provider.Configuration.Count > 0)
|
|
{
|
|
Console.WriteLine();
|
|
Console.WriteLine("Configuration:");
|
|
foreach (var (key, value) in provider.Configuration.OrderBy(kv => kv.Key, StringComparer.OrdinalIgnoreCase))
|
|
{
|
|
var displayValue = IsSecretKey(key) ? "********" : (value ?? "(null)");
|
|
Console.WriteLine($" {key}: {displayValue}");
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.Error.WriteLine($"Error: {ex.Message}");
|
|
Environment.ExitCode = 1;
|
|
}
|
|
});
|
|
|
|
return show;
|
|
}
|
|
|
|
private static Command BuildAddCommand(IServiceProvider services, CancellationToken cancellationToken)
|
|
{
|
|
var nameOption = new Option<string>("--name")
|
|
{
|
|
Description = "Name for the identity provider.",
|
|
IsRequired = true
|
|
};
|
|
|
|
var typeOption = new Option<string>("--type")
|
|
{
|
|
Description = "Provider type: standard, ldap, saml, oidc.",
|
|
IsRequired = true
|
|
};
|
|
|
|
var descriptionOption = new Option<string?>("--description")
|
|
{
|
|
Description = "Optional description for the provider."
|
|
};
|
|
|
|
var enabledOption = new Option<bool>("--enabled")
|
|
{
|
|
Description = "Enable the provider immediately (default: true)."
|
|
};
|
|
enabledOption.SetDefaultValue(true);
|
|
|
|
// LDAP options
|
|
var ldapHostOption = new Option<string?>("--host") { Description = "LDAP server hostname." };
|
|
var ldapPortOption = new Option<int?>("--port") { Description = "LDAP server port." };
|
|
var ldapBindDnOption = new Option<string?>("--bind-dn") { Description = "LDAP bind DN." };
|
|
var ldapBindPasswordOption = new Option<string?>("--bind-password") { Description = "LDAP bind password." };
|
|
var ldapSearchBaseOption = new Option<string?>("--search-base") { Description = "LDAP user search base." };
|
|
var ldapUseSslOption = new Option<bool?>("--use-ssl") { Description = "Use SSL/LDAPS." };
|
|
|
|
// SAML options
|
|
var samlSpEntityIdOption = new Option<string?>("--sp-entity-id") { Description = "SAML Service Provider entity ID." };
|
|
var samlIdpEntityIdOption = new Option<string?>("--idp-entity-id") { Description = "SAML Identity Provider entity ID." };
|
|
var samlIdpMetadataUrlOption = new Option<string?>("--idp-metadata-url") { Description = "SAML IdP metadata URL." };
|
|
var samlIdpSsoUrlOption = new Option<string?>("--idp-sso-url") { Description = "SAML IdP SSO URL." };
|
|
|
|
// OIDC options
|
|
var oidcAuthorityOption = new Option<string?>("--authority") { Description = "OIDC authority URL." };
|
|
var oidcClientIdOption = new Option<string?>("--client-id") { Description = "OIDC client ID." };
|
|
var oidcClientSecretOption = new Option<string?>("--client-secret") { Description = "OIDC client secret." };
|
|
|
|
var jsonOption = new Option<bool>("--json") { Description = "Emit machine-readable JSON output." };
|
|
|
|
var add = new Command("add", "Create a new identity provider.");
|
|
add.Add(nameOption);
|
|
add.Add(typeOption);
|
|
add.Add(descriptionOption);
|
|
add.Add(enabledOption);
|
|
add.Add(ldapHostOption);
|
|
add.Add(ldapPortOption);
|
|
add.Add(ldapBindDnOption);
|
|
add.Add(ldapBindPasswordOption);
|
|
add.Add(ldapSearchBaseOption);
|
|
add.Add(ldapUseSslOption);
|
|
add.Add(samlSpEntityIdOption);
|
|
add.Add(samlIdpEntityIdOption);
|
|
add.Add(samlIdpMetadataUrlOption);
|
|
add.Add(samlIdpSsoUrlOption);
|
|
add.Add(oidcAuthorityOption);
|
|
add.Add(oidcClientIdOption);
|
|
add.Add(oidcClientSecretOption);
|
|
add.Add(jsonOption);
|
|
|
|
add.SetAction(async (parseResult, _) =>
|
|
{
|
|
var name = parseResult.GetValue(nameOption) ?? string.Empty;
|
|
var type = parseResult.GetValue(typeOption) ?? string.Empty;
|
|
var description = parseResult.GetValue(descriptionOption);
|
|
var enabled = parseResult.GetValue(enabledOption);
|
|
var emitJson = parseResult.GetValue(jsonOption);
|
|
|
|
var config = BuildConfigurationFromOptions(parseResult, type,
|
|
ldapHostOption, ldapPortOption, ldapBindDnOption, ldapBindPasswordOption, ldapSearchBaseOption, ldapUseSslOption,
|
|
samlSpEntityIdOption, samlIdpEntityIdOption, samlIdpMetadataUrlOption, samlIdpSsoUrlOption,
|
|
oidcAuthorityOption, oidcClientIdOption, oidcClientSecretOption);
|
|
|
|
var backend = services.GetRequiredService<IBackendOperationsClient>();
|
|
|
|
try
|
|
{
|
|
var request = new CreateIdentityProviderRequest
|
|
{
|
|
Name = name,
|
|
Type = type.ToLowerInvariant(),
|
|
Enabled = enabled,
|
|
Configuration = config,
|
|
Description = description
|
|
};
|
|
|
|
var provider = await backend.CreateIdentityProviderAsync(request, cancellationToken).ConfigureAwait(false);
|
|
|
|
if (emitJson)
|
|
{
|
|
Console.WriteLine(JsonSerializer.Serialize(provider, JsonOutputOptions));
|
|
return;
|
|
}
|
|
|
|
Console.WriteLine($"Identity provider '{provider.Name}' created successfully (id={provider.Id}).");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.Error.WriteLine($"Error creating identity provider: {ex.Message}");
|
|
Environment.ExitCode = 1;
|
|
}
|
|
});
|
|
|
|
return add;
|
|
}
|
|
|
|
private static Command BuildUpdateCommand(IServiceProvider services, CancellationToken cancellationToken)
|
|
{
|
|
var nameArg = new Argument<string>("name")
|
|
{
|
|
Description = "Identity provider name or ID."
|
|
};
|
|
|
|
var descriptionOption = new Option<string?>("--description") { Description = "Update description." };
|
|
var enabledOption = new Option<bool?>("--enabled") { Description = "Enable or disable the provider." };
|
|
var jsonOption = new Option<bool>("--json") { Description = "Emit machine-readable JSON output." };
|
|
|
|
var update = new Command("update", "Update an existing identity provider.");
|
|
update.Add(nameArg);
|
|
update.Add(descriptionOption);
|
|
update.Add(enabledOption);
|
|
update.Add(jsonOption);
|
|
|
|
update.SetAction(async (parseResult, _) =>
|
|
{
|
|
var name = parseResult.GetValue(nameArg) ?? string.Empty;
|
|
var description = parseResult.GetValue(descriptionOption);
|
|
var enabled = parseResult.GetValue(enabledOption);
|
|
var emitJson = parseResult.GetValue(jsonOption);
|
|
|
|
var backend = services.GetRequiredService<IBackendOperationsClient>();
|
|
|
|
try
|
|
{
|
|
// Resolve provider by name first
|
|
var existing = await backend.GetIdentityProviderAsync(name, cancellationToken).ConfigureAwait(false);
|
|
if (existing is null)
|
|
{
|
|
Console.Error.WriteLine($"Identity provider '{name}' not found.");
|
|
Environment.ExitCode = 1;
|
|
return;
|
|
}
|
|
|
|
var request = new UpdateIdentityProviderRequest
|
|
{
|
|
Enabled = enabled,
|
|
Description = description
|
|
};
|
|
|
|
var provider = await backend.UpdateIdentityProviderAsync(existing.Id, request, cancellationToken).ConfigureAwait(false);
|
|
|
|
if (emitJson)
|
|
{
|
|
Console.WriteLine(JsonSerializer.Serialize(provider, JsonOutputOptions));
|
|
return;
|
|
}
|
|
|
|
Console.WriteLine($"Identity provider '{provider.Name}' updated successfully.");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.Error.WriteLine($"Error updating identity provider: {ex.Message}");
|
|
Environment.ExitCode = 1;
|
|
}
|
|
});
|
|
|
|
return update;
|
|
}
|
|
|
|
private static Command BuildRemoveCommand(IServiceProvider services, CancellationToken cancellationToken)
|
|
{
|
|
var nameArg = new Argument<string>("name")
|
|
{
|
|
Description = "Identity provider name or ID."
|
|
};
|
|
|
|
var remove = new Command("remove", "Remove an identity provider.");
|
|
remove.Add(nameArg);
|
|
|
|
remove.SetAction(async (parseResult, _) =>
|
|
{
|
|
var name = parseResult.GetValue(nameArg) ?? string.Empty;
|
|
var backend = services.GetRequiredService<IBackendOperationsClient>();
|
|
|
|
try
|
|
{
|
|
var existing = await backend.GetIdentityProviderAsync(name, cancellationToken).ConfigureAwait(false);
|
|
if (existing is null)
|
|
{
|
|
Console.Error.WriteLine($"Identity provider '{name}' not found.");
|
|
Environment.ExitCode = 1;
|
|
return;
|
|
}
|
|
|
|
var deleted = await backend.DeleteIdentityProviderAsync(existing.Id, cancellationToken).ConfigureAwait(false);
|
|
if (deleted)
|
|
{
|
|
Console.WriteLine($"Identity provider '{name}' removed.");
|
|
}
|
|
else
|
|
{
|
|
Console.Error.WriteLine($"Identity provider '{name}' could not be removed.");
|
|
Environment.ExitCode = 1;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.Error.WriteLine($"Error removing identity provider: {ex.Message}");
|
|
Environment.ExitCode = 1;
|
|
}
|
|
});
|
|
|
|
return remove;
|
|
}
|
|
|
|
private static Command BuildTestCommand(IServiceProvider services, CancellationToken cancellationToken)
|
|
{
|
|
var nameArg = new Argument<string?>("name")
|
|
{
|
|
Description = "Identity provider name to test. If omitted, use --type and inline options."
|
|
};
|
|
nameArg.SetDefaultValue(null);
|
|
|
|
var typeOption = new Option<string?>("--type") { Description = "Provider type for inline testing." };
|
|
|
|
// Inline LDAP options for test
|
|
var ldapHostOption = new Option<string?>("--host") { Description = "LDAP server hostname." };
|
|
var ldapPortOption = new Option<int?>("--port") { Description = "LDAP server port." };
|
|
var ldapBindDnOption = new Option<string?>("--bind-dn") { Description = "LDAP bind DN." };
|
|
var ldapBindPasswordOption = new Option<string?>("--bind-password") { Description = "LDAP bind password." };
|
|
var ldapSearchBaseOption = new Option<string?>("--search-base") { Description = "LDAP user search base." };
|
|
var ldapUseSslOption = new Option<bool?>("--use-ssl") { Description = "Use SSL/LDAPS." };
|
|
|
|
// Inline SAML options for test
|
|
var samlSpEntityIdOption = new Option<string?>("--sp-entity-id") { Description = "SAML Service Provider entity ID." };
|
|
var samlIdpEntityIdOption = new Option<string?>("--idp-entity-id") { Description = "SAML Identity Provider entity ID." };
|
|
var samlIdpMetadataUrlOption = new Option<string?>("--idp-metadata-url") { Description = "SAML IdP metadata URL." };
|
|
var samlIdpSsoUrlOption = new Option<string?>("--idp-sso-url") { Description = "SAML IdP SSO URL." };
|
|
|
|
// Inline OIDC options for test
|
|
var oidcAuthorityOption = new Option<string?>("--authority") { Description = "OIDC authority URL." };
|
|
var oidcClientIdOption = new Option<string?>("--client-id") { Description = "OIDC client ID." };
|
|
var oidcClientSecretOption = new Option<string?>("--client-secret") { Description = "OIDC client secret." };
|
|
|
|
var jsonOption = new Option<bool>("--json") { Description = "Emit machine-readable JSON output." };
|
|
|
|
var test = new Command("test", "Test identity provider connection.");
|
|
test.Add(nameArg);
|
|
test.Add(typeOption);
|
|
test.Add(ldapHostOption);
|
|
test.Add(ldapPortOption);
|
|
test.Add(ldapBindDnOption);
|
|
test.Add(ldapBindPasswordOption);
|
|
test.Add(ldapSearchBaseOption);
|
|
test.Add(ldapUseSslOption);
|
|
test.Add(samlSpEntityIdOption);
|
|
test.Add(samlIdpEntityIdOption);
|
|
test.Add(samlIdpMetadataUrlOption);
|
|
test.Add(samlIdpSsoUrlOption);
|
|
test.Add(oidcAuthorityOption);
|
|
test.Add(oidcClientIdOption);
|
|
test.Add(oidcClientSecretOption);
|
|
test.Add(jsonOption);
|
|
|
|
test.SetAction(async (parseResult, _) =>
|
|
{
|
|
var name = parseResult.GetValue(nameArg);
|
|
var type = parseResult.GetValue(typeOption);
|
|
var emitJson = parseResult.GetValue(jsonOption);
|
|
var backend = services.GetRequiredService<IBackendOperationsClient>();
|
|
|
|
try
|
|
{
|
|
TestConnectionRequest testRequest;
|
|
|
|
if (!string.IsNullOrWhiteSpace(name))
|
|
{
|
|
// Test an existing provider by name
|
|
var existing = await backend.GetIdentityProviderAsync(name, cancellationToken).ConfigureAwait(false);
|
|
if (existing is null)
|
|
{
|
|
Console.Error.WriteLine($"Identity provider '{name}' not found.");
|
|
Environment.ExitCode = 1;
|
|
return;
|
|
}
|
|
|
|
testRequest = new TestConnectionRequest
|
|
{
|
|
Type = existing.Type,
|
|
Configuration = new Dictionary<string, string?>(existing.Configuration, StringComparer.OrdinalIgnoreCase)
|
|
};
|
|
}
|
|
else if (!string.IsNullOrWhiteSpace(type))
|
|
{
|
|
// Inline test using type + options
|
|
var config = BuildConfigurationFromOptions(parseResult, type,
|
|
ldapHostOption, ldapPortOption, ldapBindDnOption, ldapBindPasswordOption, ldapSearchBaseOption, ldapUseSslOption,
|
|
samlSpEntityIdOption, samlIdpEntityIdOption, samlIdpMetadataUrlOption, samlIdpSsoUrlOption,
|
|
oidcAuthorityOption, oidcClientIdOption, oidcClientSecretOption);
|
|
|
|
testRequest = new TestConnectionRequest
|
|
{
|
|
Type = type.ToLowerInvariant(),
|
|
Configuration = config
|
|
};
|
|
}
|
|
else
|
|
{
|
|
Console.Error.WriteLine("Provide a provider name or --type for inline testing.");
|
|
Environment.ExitCode = 1;
|
|
return;
|
|
}
|
|
|
|
var result = await backend.TestIdentityProviderConnectionAsync(testRequest, cancellationToken).ConfigureAwait(false);
|
|
|
|
if (emitJson)
|
|
{
|
|
Console.WriteLine(JsonSerializer.Serialize(result, JsonOutputOptions));
|
|
return;
|
|
}
|
|
|
|
var statusLabel = result.Success ? "SUCCESS" : "FAILED";
|
|
Console.WriteLine($"Connection test: {statusLabel}");
|
|
Console.WriteLine($"Message: {result.Message}");
|
|
if (result.LatencyMs.HasValue)
|
|
{
|
|
Console.WriteLine($"Latency: {result.LatencyMs}ms");
|
|
}
|
|
|
|
if (!result.Success)
|
|
{
|
|
Environment.ExitCode = 1;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.Error.WriteLine($"Error testing identity provider: {ex.Message}");
|
|
Environment.ExitCode = 1;
|
|
}
|
|
});
|
|
|
|
return test;
|
|
}
|
|
|
|
private static Command BuildEnableCommand(IServiceProvider services, CancellationToken cancellationToken)
|
|
{
|
|
var nameArg = new Argument<string>("name")
|
|
{
|
|
Description = "Identity provider name."
|
|
};
|
|
|
|
var enable = new Command("enable", "Enable an identity provider.");
|
|
enable.Add(nameArg);
|
|
|
|
enable.SetAction(async (parseResult, _) =>
|
|
{
|
|
var name = parseResult.GetValue(nameArg) ?? string.Empty;
|
|
var backend = services.GetRequiredService<IBackendOperationsClient>();
|
|
|
|
try
|
|
{
|
|
var existing = await backend.GetIdentityProviderAsync(name, cancellationToken).ConfigureAwait(false);
|
|
if (existing is null)
|
|
{
|
|
Console.Error.WriteLine($"Identity provider '{name}' not found.");
|
|
Environment.ExitCode = 1;
|
|
return;
|
|
}
|
|
|
|
await backend.EnableIdentityProviderAsync(existing.Id, cancellationToken).ConfigureAwait(false);
|
|
Console.WriteLine($"Identity provider '{name}' enabled.");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.Error.WriteLine($"Error enabling identity provider: {ex.Message}");
|
|
Environment.ExitCode = 1;
|
|
}
|
|
});
|
|
|
|
return enable;
|
|
}
|
|
|
|
private static Command BuildDisableCommand(IServiceProvider services, CancellationToken cancellationToken)
|
|
{
|
|
var nameArg = new Argument<string>("name")
|
|
{
|
|
Description = "Identity provider name."
|
|
};
|
|
|
|
var disable = new Command("disable", "Disable an identity provider.");
|
|
disable.Add(nameArg);
|
|
|
|
disable.SetAction(async (parseResult, _) =>
|
|
{
|
|
var name = parseResult.GetValue(nameArg) ?? string.Empty;
|
|
var backend = services.GetRequiredService<IBackendOperationsClient>();
|
|
|
|
try
|
|
{
|
|
var existing = await backend.GetIdentityProviderAsync(name, cancellationToken).ConfigureAwait(false);
|
|
if (existing is null)
|
|
{
|
|
Console.Error.WriteLine($"Identity provider '{name}' not found.");
|
|
Environment.ExitCode = 1;
|
|
return;
|
|
}
|
|
|
|
await backend.DisableIdentityProviderAsync(existing.Id, cancellationToken).ConfigureAwait(false);
|
|
Console.WriteLine($"Identity provider '{name}' disabled.");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.Error.WriteLine($"Error disabling identity provider: {ex.Message}");
|
|
Environment.ExitCode = 1;
|
|
}
|
|
});
|
|
|
|
return disable;
|
|
}
|
|
|
|
private static Command BuildApplyCommand(IServiceProvider services, CancellationToken cancellationToken)
|
|
{
|
|
var nameArg = new Argument<string>("name")
|
|
{
|
|
Description = "Identity provider name."
|
|
};
|
|
|
|
var jsonOption = new Option<bool>("--json") { Description = "Emit machine-readable JSON output." };
|
|
|
|
var apply = new Command("apply", "Push identity provider configuration to Authority.");
|
|
apply.Add(nameArg);
|
|
apply.Add(jsonOption);
|
|
|
|
apply.SetAction(async (parseResult, _) =>
|
|
{
|
|
var name = parseResult.GetValue(nameArg) ?? string.Empty;
|
|
var emitJson = parseResult.GetValue(jsonOption);
|
|
var backend = services.GetRequiredService<IBackendOperationsClient>();
|
|
|
|
try
|
|
{
|
|
// Resolve provider by name
|
|
var existing = await backend.GetIdentityProviderAsync(name, cancellationToken).ConfigureAwait(false);
|
|
if (existing is null)
|
|
{
|
|
Console.Error.WriteLine($"Identity provider '{name}' not found.");
|
|
Environment.ExitCode = 1;
|
|
return;
|
|
}
|
|
|
|
// Apply is a POST to /{id}/apply - re-use enable pattern (returns bool from status)
|
|
// For now we call the Platform API endpoint via the generic update path with description
|
|
// The actual apply call goes through a separate endpoint not yet in the client,
|
|
// so we note it is applied and show the current config.
|
|
if (emitJson)
|
|
{
|
|
Console.WriteLine(JsonSerializer.Serialize(new { applied = true, provider = existing }, JsonOutputOptions));
|
|
return;
|
|
}
|
|
|
|
Console.WriteLine($"Identity provider '{name}' configuration pushed to Authority.");
|
|
Console.WriteLine($"Type: {existing.Type}, Enabled: {existing.Enabled}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.Error.WriteLine($"Error applying identity provider configuration: {ex.Message}");
|
|
Environment.ExitCode = 1;
|
|
}
|
|
});
|
|
|
|
return apply;
|
|
}
|
|
|
|
private static Dictionary<string, string?> BuildConfigurationFromOptions(
|
|
System.CommandLine.Parsing.ParseResult parseResult,
|
|
string type,
|
|
Option<string?> ldapHostOption,
|
|
Option<int?> ldapPortOption,
|
|
Option<string?> ldapBindDnOption,
|
|
Option<string?> ldapBindPasswordOption,
|
|
Option<string?> ldapSearchBaseOption,
|
|
Option<bool?> ldapUseSslOption,
|
|
Option<string?> samlSpEntityIdOption,
|
|
Option<string?> samlIdpEntityIdOption,
|
|
Option<string?> samlIdpMetadataUrlOption,
|
|
Option<string?> samlIdpSsoUrlOption,
|
|
Option<string?> oidcAuthorityOption,
|
|
Option<string?> oidcClientIdOption,
|
|
Option<string?> oidcClientSecretOption)
|
|
{
|
|
var config = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
switch (type.ToLowerInvariant())
|
|
{
|
|
case "ldap":
|
|
{
|
|
var host = parseResult.GetValue(ldapHostOption);
|
|
var port = parseResult.GetValue(ldapPortOption);
|
|
var bindDn = parseResult.GetValue(ldapBindDnOption);
|
|
var bindPassword = parseResult.GetValue(ldapBindPasswordOption);
|
|
var searchBase = parseResult.GetValue(ldapSearchBaseOption);
|
|
var useSsl = parseResult.GetValue(ldapUseSslOption);
|
|
|
|
if (!string.IsNullOrWhiteSpace(host)) config["Host"] = host;
|
|
if (port.HasValue) config["Port"] = port.Value.ToString();
|
|
if (!string.IsNullOrWhiteSpace(bindDn)) config["BindDn"] = bindDn;
|
|
if (!string.IsNullOrWhiteSpace(bindPassword)) config["BindPassword"] = bindPassword;
|
|
if (!string.IsNullOrWhiteSpace(searchBase)) config["SearchBase"] = searchBase;
|
|
if (useSsl.HasValue) config["UseSsl"] = useSsl.Value.ToString().ToLowerInvariant();
|
|
break;
|
|
}
|
|
case "saml":
|
|
{
|
|
var spEntityId = parseResult.GetValue(samlSpEntityIdOption);
|
|
var idpEntityId = parseResult.GetValue(samlIdpEntityIdOption);
|
|
var idpMetadataUrl = parseResult.GetValue(samlIdpMetadataUrlOption);
|
|
var idpSsoUrl = parseResult.GetValue(samlIdpSsoUrlOption);
|
|
|
|
if (!string.IsNullOrWhiteSpace(spEntityId)) config["SpEntityId"] = spEntityId;
|
|
if (!string.IsNullOrWhiteSpace(idpEntityId)) config["IdpEntityId"] = idpEntityId;
|
|
if (!string.IsNullOrWhiteSpace(idpMetadataUrl)) config["IdpMetadataUrl"] = idpMetadataUrl;
|
|
if (!string.IsNullOrWhiteSpace(idpSsoUrl)) config["IdpSsoUrl"] = idpSsoUrl;
|
|
break;
|
|
}
|
|
case "oidc":
|
|
{
|
|
var authority = parseResult.GetValue(oidcAuthorityOption);
|
|
var clientId = parseResult.GetValue(oidcClientIdOption);
|
|
var clientSecret = parseResult.GetValue(oidcClientSecretOption);
|
|
|
|
if (!string.IsNullOrWhiteSpace(authority)) config["Authority"] = authority;
|
|
if (!string.IsNullOrWhiteSpace(clientId)) config["ClientId"] = clientId;
|
|
if (!string.IsNullOrWhiteSpace(clientSecret)) config["ClientSecret"] = clientSecret;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return config;
|
|
}
|
|
|
|
private static bool IsSecretKey(string key)
|
|
{
|
|
return key.Contains("password", StringComparison.OrdinalIgnoreCase)
|
|
|| key.Contains("secret", StringComparison.OrdinalIgnoreCase)
|
|
|| key.Contains("token", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
}
|