getApiTokenMaxTtlDays(); $value = $this->settingsMetadataGateway->getInt(SettingKeys::API_TOKEN_DEFAULT_TTL_DAYS_KEY); if ($value !== null && $value >= self::API_TOKEN_TTL_DAYS_MIN && $value <= self::API_TOKEN_TTL_DAYS_HARD_MAX) { return min($value, $maxDays); } return min(self::API_TOKEN_DEFAULT_TTL_DAYS_FALLBACK, $maxDays); } public function setApiTokenDefaultTtlDays(?int $days, ?string $description = null): bool { $value = $days ?? self::API_TOKEN_DEFAULT_TTL_DAYS_FALLBACK; if ($value < self::API_TOKEN_TTL_DAYS_MIN || $value > self::API_TOKEN_TTL_DAYS_HARD_MAX) { return false; } if ($value > $this->getApiTokenMaxTtlDays()) { return false; } $desc = $description ?? 'setting.api_token_default_ttl_days'; return $this->settingsMetadataGateway->set(SettingKeys::API_TOKEN_DEFAULT_TTL_DAYS_KEY, (string) $value, $desc); } public function getApiTokenMaxTtlDays(): int { $value = $this->settingsMetadataGateway->getInt(SettingKeys::API_TOKEN_MAX_TTL_DAYS_KEY); if ($value !== null && $value >= self::API_TOKEN_TTL_DAYS_MIN && $value <= self::API_TOKEN_TTL_DAYS_HARD_MAX) { return $value; } return self::API_TOKEN_MAX_TTL_DAYS_FALLBACK; } public function setApiTokenMaxTtlDays(?int $days, ?string $description = null): bool { $value = $days ?? self::API_TOKEN_MAX_TTL_DAYS_FALLBACK; if ($value < self::API_TOKEN_TTL_DAYS_MIN || $value > self::API_TOKEN_TTL_DAYS_HARD_MAX) { return false; } $currentDefault = $this->settingsMetadataGateway->getInt(SettingKeys::API_TOKEN_DEFAULT_TTL_DAYS_KEY); if ($currentDefault !== null && $currentDefault > $value) { $defaultUpdated = $this->settingsMetadataGateway->set( SettingKeys::API_TOKEN_DEFAULT_TTL_DAYS_KEY, (string) $value, 'setting.api_token_default_ttl_days' ); if (!$defaultUpdated) { return false; } } $desc = $description ?? 'setting.api_token_max_ttl_days'; return $this->settingsMetadataGateway->set(SettingKeys::API_TOKEN_MAX_TTL_DAYS_KEY, (string) $value, $desc); } public function getApiCorsAllowedOrigins(): array { $stored = $this->settingsMetadataGateway->getValue(SettingKeys::API_CORS_ALLOWED_ORIGINS_KEY); if ($stored === null || trim($stored) === '') { return []; } return $this->parseCorsOrigins($stored, false) ?? []; } public function getApiCorsAllowedOriginsText(): string { $origins = $this->getApiCorsAllowedOrigins(); return implode("\n", $origins); } public function setApiCorsAllowedOrigins(?string $rawOrigins, ?string $description = null): bool { $value = (string) ($rawOrigins ?? ''); if (trim($value) === '') { return $this->settingsMetadataGateway->set( SettingKeys::API_CORS_ALLOWED_ORIGINS_KEY, null, $description ?? 'setting.api_cors_allowed_origins' ); } $origins = $this->parseCorsOrigins($value, true); if ($origins === null) { return false; } return $this->settingsMetadataGateway->set( SettingKeys::API_CORS_ALLOWED_ORIGINS_KEY, implode("\n", $origins), $description ?? 'setting.api_cors_allowed_origins' ); } private function parseCorsOrigins(string $rawOrigins, bool $strict): ?array { $parts = preg_split('/[\r\n,]+/', $rawOrigins) ?: []; $normalizedOrigins = []; foreach ($parts as $part) { $origin = $this->normalizeCorsOrigin($part); if ($origin === null) { if ($strict && trim((string) $part) !== '') { return null; } continue; } $normalizedOrigins[$origin] = true; } return array_keys($normalizedOrigins); } private function normalizeCorsOrigin(string $origin): ?string { $origin = trim($origin); if ($origin === '') { return null; } $origin = rtrim($origin, '/'); if ($origin === '') { return null; } $parsed = parse_url($origin); if (!is_array($parsed)) { return null; } $scheme = strtolower((string) ($parsed['scheme'] ?? '')); $host = strtolower((string) ($parsed['host'] ?? '')); $port = isset($parsed['port']) ? (int) $parsed['port'] : null; if (!in_array($scheme, ['http', 'https'], true) || $host === '') { return null; } if ( isset($parsed['path']) || isset($parsed['query']) || isset($parsed['fragment']) || isset($parsed['user']) || isset($parsed['pass']) ) { return null; } $defaultPort = ($scheme === 'https') ? 443 : 80; $portSuffix = ($port !== null && $port !== $defaultPort) ? ':' . $port : ''; return $scheme . '://' . $host . $portSuffix; } }