stabilizaiton work - projects rework for maintenanceability and ui livening
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
|
||||
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using StellaOps.Authority.Plugins.Abstractions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace StellaOps.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Analyses Authority plugin configurations for common security issues.
|
||||
/// </summary>
|
||||
public static class AuthorityPluginConfigurationAnalyzer
|
||||
{
|
||||
private const int BaselineMinimumLength = 12;
|
||||
private const bool BaselineRequireUppercase = true;
|
||||
private const bool BaselineRequireLowercase = true;
|
||||
private const bool BaselineRequireDigit = true;
|
||||
private const bool BaselineRequireSymbol = true;
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates plugin contexts and returns diagnostics describing potential misconfigurations.
|
||||
/// </summary>
|
||||
/// <param name="contexts">Plugin contexts produced by <see cref="AuthorityPluginConfigurationLoader"/>.</param>
|
||||
/// <returns>Diagnostics describing any detected issues.</returns>
|
||||
public static IReadOnlyList<AuthorityConfigurationDiagnostic> Analyze(IEnumerable<AuthorityPluginContext> contexts)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(contexts);
|
||||
|
||||
var diagnostics = new List<AuthorityConfigurationDiagnostic>();
|
||||
|
||||
foreach (var context in contexts)
|
||||
{
|
||||
if (context is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.Equals(context.Manifest.AssemblyName, "StellaOps.Authority.Plugin.Standard", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
AnalyzeStandardPlugin(context, diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
return diagnostics;
|
||||
}
|
||||
|
||||
private static void AnalyzeStandardPlugin(AuthorityPluginContext context, ICollection<AuthorityConfigurationDiagnostic> diagnostics)
|
||||
{
|
||||
var section = context.Configuration.GetSection("passwordPolicy");
|
||||
if (!section.Exists())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int minLength = section.GetValue("minimumLength", BaselineMinimumLength);
|
||||
bool requireUppercase = section.GetValue("requireUppercase", BaselineRequireUppercase);
|
||||
bool requireLowercase = section.GetValue("requireLowercase", BaselineRequireLowercase);
|
||||
bool requireDigit = section.GetValue("requireDigit", BaselineRequireDigit);
|
||||
bool requireSymbol = section.GetValue("requireSymbol", BaselineRequireSymbol);
|
||||
|
||||
var deviations = new List<string>();
|
||||
|
||||
if (minLength < BaselineMinimumLength)
|
||||
{
|
||||
deviations.Add($"minimum length {minLength.ToString(CultureInfo.InvariantCulture)} < {BaselineMinimumLength}");
|
||||
}
|
||||
|
||||
if (!requireUppercase && BaselineRequireUppercase)
|
||||
{
|
||||
deviations.Add("uppercase requirement disabled");
|
||||
}
|
||||
|
||||
if (!requireLowercase && BaselineRequireLowercase)
|
||||
{
|
||||
deviations.Add("lowercase requirement disabled");
|
||||
}
|
||||
|
||||
if (!requireDigit && BaselineRequireDigit)
|
||||
{
|
||||
deviations.Add("digit requirement disabled");
|
||||
}
|
||||
|
||||
if (!requireSymbol && BaselineRequireSymbol)
|
||||
{
|
||||
deviations.Add("symbol requirement disabled");
|
||||
}
|
||||
|
||||
if (deviations.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var message = $"Password policy for plugin '{context.Manifest.Name}' weakens host defaults: {string.Join(", ", deviations)}.";
|
||||
diagnostics.Add(new AuthorityConfigurationDiagnostic(context.Manifest.Name, AuthorityConfigurationDiagnosticSeverity.Warning, message));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace StellaOps.Configuration;
|
||||
|
||||
public static partial class AuthorityPluginConfigurationLoader
|
||||
{
|
||||
private static string ResolveConfigurationDirectory(string configurationDirectory, string basePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(configurationDirectory))
|
||||
{
|
||||
return Path.GetFullPath(basePath);
|
||||
}
|
||||
|
||||
var directory = configurationDirectory;
|
||||
if (!Path.IsPathRooted(directory))
|
||||
{
|
||||
directory = Path.Combine(basePath, directory);
|
||||
}
|
||||
|
||||
return Path.GetFullPath(directory);
|
||||
}
|
||||
|
||||
private static string ResolveConfigPath(string configurationDirectory, string? configFile)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(configFile))
|
||||
{
|
||||
throw new InvalidOperationException("Authority plugin descriptor must specify a configFile.");
|
||||
}
|
||||
|
||||
if (Path.IsPathRooted(configFile))
|
||||
{
|
||||
return Path.GetFullPath(configFile);
|
||||
}
|
||||
|
||||
return Path.GetFullPath(Path.Combine(configurationDirectory, configFile));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
|
||||
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using NetEscapades.Configuration.Yaml;
|
||||
using StellaOps.Authority.Plugins.Abstractions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace StellaOps.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Utility helpers for loading Authority plugin configuration manifests.
|
||||
/// </summary>
|
||||
public static partial class AuthorityPluginConfigurationLoader
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads plugin configuration files based on the supplied Authority options.
|
||||
/// </summary>
|
||||
/// <param name="options">Authority configuration containing plugin descriptors.</param>
|
||||
/// <param name="basePath">Application base path used to resolve relative directories.</param>
|
||||
/// <param name="configureBuilder">Optional hook to customise per-plugin configuration builder.</param>
|
||||
public static IReadOnlyList<AuthorityPluginContext> Load(
|
||||
StellaOpsAuthorityOptions options,
|
||||
string basePath,
|
||||
Action<IConfigurationBuilder>? configureBuilder = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
ArgumentNullException.ThrowIfNull(basePath);
|
||||
|
||||
var descriptorPairs = options.Plugins.Descriptors
|
||||
.OrderBy(static pair => pair.Key, StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
|
||||
if (descriptorPairs.Length == 0)
|
||||
{
|
||||
return Array.Empty<AuthorityPluginContext>();
|
||||
}
|
||||
|
||||
var configurationDirectory = ResolveConfigurationDirectory(options.Plugins.ConfigurationDirectory, basePath);
|
||||
var contexts = new List<AuthorityPluginContext>(descriptorPairs.Length);
|
||||
|
||||
foreach (var (name, descriptor) in descriptorPairs)
|
||||
{
|
||||
var configPath = ResolveConfigPath(configurationDirectory, descriptor.ConfigFile);
|
||||
var optional = !descriptor.Enabled;
|
||||
|
||||
if (!optional && !File.Exists(configPath))
|
||||
{
|
||||
throw new FileNotFoundException($"Required Authority plugin configuration '{configPath}' was not found.", configPath);
|
||||
}
|
||||
|
||||
var builder = new ConfigurationBuilder();
|
||||
var builderBasePath = Path.GetDirectoryName(configPath);
|
||||
if (!string.IsNullOrEmpty(builderBasePath) && Directory.Exists(builderBasePath))
|
||||
{
|
||||
builder.SetBasePath(builderBasePath);
|
||||
}
|
||||
|
||||
configureBuilder?.Invoke(builder);
|
||||
builder.AddYamlFile(configPath, optional: optional, reloadOnChange: false);
|
||||
var configuration = builder.Build();
|
||||
|
||||
var manifest = AuthorityPluginManifestFactory.Create(descriptor, name, configPath);
|
||||
contexts.Add(new AuthorityPluginContext(manifest, configuration));
|
||||
}
|
||||
|
||||
return contexts;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using StellaOps.Authority.Plugins.Abstractions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace StellaOps.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Factory for creating <see cref="AuthorityPluginManifest"/> instances from descriptor options.
|
||||
/// </summary>
|
||||
public static class AuthorityPluginManifestFactory
|
||||
{
|
||||
private static readonly StringComparer _ordinalIgnoreCase = StringComparer.OrdinalIgnoreCase;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="AuthorityPluginManifest"/> from the specified descriptor.
|
||||
/// </summary>
|
||||
/// <param name="descriptor">Plugin descriptor options.</param>
|
||||
/// <param name="name">Logical plugin name.</param>
|
||||
/// <param name="configPath">Absolute path to the plugin configuration file.</param>
|
||||
public static AuthorityPluginManifest Create(AuthorityPluginDescriptorOptions descriptor, string name, string configPath)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(descriptor);
|
||||
|
||||
var capabilitiesSnapshot = descriptor.Capabilities.Count == 0
|
||||
? Array.Empty<string>()
|
||||
: descriptor.Capabilities.ToArray();
|
||||
|
||||
var metadataSnapshot = descriptor.Metadata.Count == 0
|
||||
? new Dictionary<string, string?>(_ordinalIgnoreCase)
|
||||
: new Dictionary<string, string?>(descriptor.Metadata, _ordinalIgnoreCase);
|
||||
|
||||
return new AuthorityPluginManifest(
|
||||
name,
|
||||
descriptor.Type ?? name,
|
||||
descriptor.Enabled,
|
||||
descriptor.AssemblyName,
|
||||
descriptor.AssemblyPath,
|
||||
capabilitiesSnapshot,
|
||||
metadataSnapshot,
|
||||
configPath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" />
|
||||
<PackageReference Include="NetEscapades.Configuration.Yaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StellaOps.Configuration\StellaOps.Configuration.csproj" />
|
||||
<ProjectReference Include="..\..\Authority\StellaOps.Authority\StellaOps.Authority.Plugins.Abstractions\StellaOps.Authority.Plugins.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user