| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135 |
- <?php
- declare(strict_types=1);
- namespace App\Application\Admin;
- use Psr\Http\Message\ResponseInterface;
- use Psr\Http\Message\ServerRequestInterface;
- /**
- * `GET /api/v1/admin/config` — effective config the api is using,
- * with secrets masked.
- *
- * Masking rules per SPEC §M12.5:
- * - `***` for: INTERNAL_JOB_TOKEN, MAXMIND_LICENSE_KEY,
- * IPINFO_TOKEN, DB_MYSQL_PASSWORD, APP_SECRET
- * - first 8 + `...` for: UI_SERVICE_TOKEN
- * - plain values for everything else
- *
- * Returns config grouped by section so the UI can render it without
- * inventing categorisation. RBAC: Admin only — viewers and operators
- * see no_access.
- */
- final class ConfigController
- {
- use AdminControllerSupport;
- /**
- * @param array<string, mixed> $settings Effective settings array (the same array that built the container).
- */
- public function __construct(private readonly array $settings)
- {
- }
- public function show(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
- {
- return self::json($response, 200, [
- 'sections' => $this->sections(),
- ]);
- }
- /**
- * @return array<string, array<string, mixed>>
- */
- private function sections(): array
- {
- $db = $this->settings['db'] ?? [];
- $geoip = $this->settings['geoip'] ?? [];
- return [
- 'app' => [
- 'APP_ENV' => $this->settings['app_env'] ?? null,
- 'LOG_LEVEL' => $this->levelName(),
- 'APP_SECRET' => self::mask((string) ($this->settings['app_secret'] ?? '')),
- 'UI_ORIGIN' => $this->settings['ui_origin'] ?? null,
- ],
- 'database' => [
- 'DB_DRIVER' => $db['driver'] ?? null,
- 'DB_SQLITE_PATH' => $db['sqlite_path'] ?? null,
- 'DB_MYSQL_HOST' => $db['mysql_host'] ?? null,
- 'DB_MYSQL_PORT' => $db['mysql_port'] ?? null,
- 'DB_MYSQL_DATABASE' => $db['mysql_database'] ?? null,
- 'DB_MYSQL_USERNAME' => $db['mysql_username'] ?? null,
- 'DB_MYSQL_PASSWORD' => self::mask((string) ($db['mysql_password'] ?? '')),
- ],
- 'auth' => [
- 'UI_SERVICE_TOKEN' => self::previewToken((string) ($this->settings['ui_service_token'] ?? '')),
- 'INTERNAL_JOB_TOKEN' => self::mask((string) ($this->settings['internal_job_token'] ?? '')),
- 'OIDC_DEFAULT_ROLE' => $this->oidcDefaultRoleName(),
- ],
- 'reputation' => [
- 'SCORE_REPORT_HARD_CUTOFF_DAYS' => $this->settings['score_hard_cutoff_days'] ?? null,
- 'SCORE_RECOMPUTE_INTERVAL_SECONDS' => $this->settings['score_recompute_interval_seconds'] ?? null,
- 'API_RATE_LIMIT_PER_SECOND' => $this->settings['rate_limit_per_second'] ?? null,
- 'CIDR_EVALUATOR_TTL_SECONDS' => $this->settings['cidr_evaluator_ttl_seconds'] ?? null,
- 'BLOCKLIST_CACHE_TTL_SECONDS' => $this->settings['blocklist_cache_ttl_seconds'] ?? null,
- ],
- 'jobs' => [
- 'JOB_RECOMPUTE_MAX_RUNTIME_SECONDS' => $this->settings['job_recompute_max_runtime_seconds'] ?? null,
- 'JOB_RECOMPUTE_MAX_ROWS_PER_TICK' => $this->settings['job_recompute_max_rows_per_tick'] ?? null,
- 'JOB_AUDIT_RETENTION_DAYS' => $this->settings['job_audit_retention_days'] ?? null,
- 'JOB_GEOIP_REFRESH_INTERVAL_DAYS' => $geoip['refresh_interval_days'] ?? null,
- ],
- 'geoip' => [
- 'GEOIP_ENABLED' => $geoip['enabled'] ?? null,
- 'GEOIP_PROVIDER' => $geoip['provider'] ?? null,
- 'GEOIP_COUNTRY_DB' => $geoip['country_db'] ?? null,
- 'GEOIP_ASN_DB' => $geoip['asn_db'] ?? null,
- 'MAXMIND_LICENSE_KEY' => self::mask((string) ($geoip['maxmind_license_key'] ?? '')),
- 'IPINFO_TOKEN' => self::mask((string) ($geoip['ipinfo_token'] ?? '')),
- ],
- ];
- }
- private static function mask(string $value): string
- {
- return $value === '' ? '' : '***';
- }
- /**
- * Token preview: empty stays empty so misconfiguration is visible;
- * present values show first 8 + ellipsis.
- */
- private static function previewToken(string $value): string
- {
- if ($value === '') {
- return '';
- }
- return substr($value, 0, 8) . '...';
- }
- private function levelName(): ?string
- {
- $level = $this->settings['log_level'] ?? null;
- if ($level instanceof \Monolog\Level) {
- return $level->getName();
- }
- return is_string($level) ? $level : null;
- }
- private function oidcDefaultRoleName(): ?string
- {
- $role = $this->settings['oidc_default_role'] ?? null;
- if ($role === null) {
- return 'none';
- }
- if ($role instanceof \App\Domain\Auth\Role) {
- return $role->value;
- }
- return is_string($role) ? $role : null;
- }
- }
|