116 lines
4.0 KiB
PHP
116 lines
4.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\TenantConfiguration;
|
|
|
|
final readonly class ExchangePowerShellRuntimePolicy
|
|
{
|
|
/**
|
|
* @param list<string> $allowedEnvironments
|
|
*/
|
|
public function __construct(
|
|
public bool $invocationEnabled,
|
|
public bool $productionRunnerEnabled,
|
|
public array $allowedEnvironments,
|
|
public string $currentEnvironment,
|
|
public string $powershellBinary,
|
|
public bool $moduleCheckEnabled,
|
|
public int $checkTimeoutSeconds,
|
|
public int $invocationTimeoutSeconds,
|
|
public int $stdoutMaxBytes,
|
|
public int $stderrMaxBytes,
|
|
public int $itemLimit,
|
|
public int $lockTtlSeconds,
|
|
public bool $tempFilesEnabled,
|
|
public ?string $tempDirectory,
|
|
) {}
|
|
|
|
public static function fromConfig(): self
|
|
{
|
|
$runtime = config('tenantpilot.exchange_powershell.runtime', []);
|
|
$runtime = is_array($runtime) ? $runtime : [];
|
|
|
|
return new self(
|
|
invocationEnabled: (bool) config(ExchangePowerShellInvocationGate::FEATURE_CONFIG_PATH, false),
|
|
productionRunnerEnabled: (bool) config(ExchangePowerShellInvocationGate::PRODUCTION_RUNNER_CONFIG_PATH, false),
|
|
allowedEnvironments: self::stringList($runtime['allowed_environments'] ?? ['local', 'testing']),
|
|
currentEnvironment: app()->environment(),
|
|
powershellBinary: self::nonEmptyString($runtime['powershell_binary'] ?? null, 'pwsh'),
|
|
moduleCheckEnabled: (bool) ($runtime['module_check_enabled'] ?? true),
|
|
checkTimeoutSeconds: max(0, (int) ($runtime['check_timeout_seconds'] ?? 5)),
|
|
invocationTimeoutSeconds: max(0, (int) ($runtime['invocation_timeout_seconds'] ?? 60)),
|
|
stdoutMaxBytes: max(0, (int) ($runtime['stdout_max_bytes'] ?? 524288)),
|
|
stderrMaxBytes: max(0, (int) ($runtime['stderr_max_bytes'] ?? 65536)),
|
|
itemLimit: max(0, (int) ($runtime['item_limit'] ?? 1000)),
|
|
lockTtlSeconds: max(0, (int) ($runtime['lock_ttl_seconds'] ?? 300)),
|
|
tempFilesEnabled: (bool) ($runtime['temp_files_enabled'] ?? false),
|
|
tempDirectory: self::nullableString($runtime['temp_directory'] ?? null),
|
|
);
|
|
}
|
|
|
|
public function environmentAllowed(): bool
|
|
{
|
|
return in_array($this->currentEnvironment, $this->allowedEnvironments, true);
|
|
}
|
|
|
|
public function timeoutPolicyValid(): bool
|
|
{
|
|
return $this->checkTimeoutSeconds > 0
|
|
&& $this->invocationTimeoutSeconds > 0
|
|
&& $this->stdoutMaxBytes > 0
|
|
&& $this->stderrMaxBytes > 0
|
|
&& $this->itemLimit > 0
|
|
&& $this->lockTtlSeconds > 0;
|
|
}
|
|
|
|
public function tempPolicySafe(): bool
|
|
{
|
|
if (! $this->tempFilesEnabled) {
|
|
return true;
|
|
}
|
|
|
|
if ($this->tempDirectory === null || $this->tempDirectory === '') {
|
|
return false;
|
|
}
|
|
|
|
$allowedPrefix = rtrim(storage_path('app/tmp'), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR;
|
|
$directory = rtrim($this->tempDirectory, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR;
|
|
|
|
return str_starts_with($directory, $allowedPrefix);
|
|
}
|
|
|
|
/**
|
|
* @return list<string>
|
|
*/
|
|
private static function stringList(mixed $values): array
|
|
{
|
|
if (is_string($values)) {
|
|
$values = explode(',', $values);
|
|
}
|
|
|
|
if (! is_array($values)) {
|
|
return [];
|
|
}
|
|
|
|
return array_values(array_filter(
|
|
array_map(static fn (mixed $value): string => trim((string) $value), $values),
|
|
static fn (string $value): bool => $value !== '',
|
|
));
|
|
}
|
|
|
|
private static function nonEmptyString(mixed $value, string $fallback): string
|
|
{
|
|
$value = is_string($value) ? trim($value) : '';
|
|
|
|
return $value !== '' ? $value : $fallback;
|
|
}
|
|
|
|
private static function nullableString(mixed $value): ?string
|
|
{
|
|
$value = is_string($value) ? trim($value) : '';
|
|
|
|
return $value !== '' ? $value : null;
|
|
}
|
|
}
|