using System; using System.Collections.Generic; using System.IO; using System.Text.Json; namespace StellaOps.Scanner.Surface.Secrets.Providers; internal sealed class FileSurfaceSecretProvider : ISurfaceSecretProvider { private readonly string _root; public FileSurfaceSecretProvider(string root) { if (string.IsNullOrWhiteSpace(root)) { throw new ArgumentException("File secret provider root cannot be null or whitespace.", nameof(root)); } _root = root; } public SurfaceSecretHandle Get(SurfaceSecretRequest request) { if (request is null) { throw new ArgumentNullException(nameof(request)); } var path = ResolvePath(request); if (!File.Exists(path)) { throw new SurfaceSecretNotFoundException(request); } var json = File.ReadAllText(path); var descriptor = JsonSerializer.Deserialize(json); if (descriptor is null) { throw new SurfaceSecretNotFoundException(request); } if (string.IsNullOrWhiteSpace(descriptor.Payload)) { return SurfaceSecretHandle.Empty; } var bytes = Convert.FromBase64String(descriptor.Payload); return SurfaceSecretHandle.FromBytes(bytes, descriptor.Metadata); } public async ValueTask GetAsync( SurfaceSecretRequest request, CancellationToken cancellationToken = default) { if (request is null) { throw new ArgumentNullException(nameof(request)); } var path = ResolvePath(request); if (!File.Exists(path)) { throw new SurfaceSecretNotFoundException(request); } await using var stream = File.OpenRead(path); var descriptor = await JsonSerializer.DeserializeAsync(stream, cancellationToken: cancellationToken).ConfigureAwait(false); if (descriptor is null) { throw new SurfaceSecretNotFoundException(request); } if (string.IsNullOrWhiteSpace(descriptor.Payload)) { return SurfaceSecretHandle.Empty; } var bytes = Convert.FromBase64String(descriptor.Payload); return SurfaceSecretHandle.FromBytes(bytes, descriptor.Metadata); } private string ResolvePath(SurfaceSecretRequest request) { var name = request.Name ?? "default"; return Path.Combine(_root, request.Tenant, request.Component, request.SecretType, name + ".json"); } private sealed class FileSecretDescriptor { public string? Payload { get; init; } public Dictionary? Metadata { get; init; } } }