TenantAtlas/apps/platform/app/Services/TenantConfiguration/ExchangePowerShellInvocationReadinessEvaluator.php
ahmido a6f4529941 feat: add Exchange credential permission evidence readiness (#500)
Validation: sail artisan test --filter=Spec433|Spec432|Spec431 --compact; pint --dirty --test; git diff --check
Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #500
2026-07-07 23:55:32 +00:00

85 lines
3.3 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\TenantConfiguration;
use App\Models\ManagedEnvironment;
use App\Models\ProviderConnection;
use App\Support\Providers\ProviderReasonCodes;
final class ExchangePowerShellInvocationReadinessEvaluator
{
public function __construct(
private readonly ExchangePowerShellCredentialReferenceResolver $credentials,
private readonly ExchangePowerShellPermissionEvidenceEvaluator $permissions,
) {}
public function evaluate(ManagedEnvironment $environment, ProviderConnection $connection): ExchangePowerShellGateResult
{
if (! $this->scopeMatches($environment, $connection)) {
return ExchangePowerShellGateResult::blocked(
reasonCode: ProviderReasonCodes::TenantTargetMismatch,
failureCode: 'readiness_blocked_scope_mismatch',
message: 'Exchange PowerShell invocation readiness requires a matching workspace, environment, and provider connection.',
context: [
'readiness_state' => 'blocked',
'readiness_blocker' => 'scope',
'scope_state' => 'mismatch',
],
);
}
$credentialGate = $this->credentials->resolve($connection);
if (! $credentialGate->allowed) {
return $this->blockedFromGate($credentialGate, 'credential');
}
$permissionGate = $this->permissions->evaluate($environment, $connection);
if (! $permissionGate->allowed) {
return $this->blockedFromGate($permissionGate, 'permission');
}
return ExchangePowerShellGateResult::allowed([
'readiness_state' => 'ready_for_live_invocation',
'credential_state' => $credentialGate->context['credential_state'] ?? 'ready',
'permission_evidence_state' => $permissionGate->context['permission_evidence_state'] ?? 'verified',
'permission_currentness_days' => $permissionGate->context['permission_currentness_days'] ?? null,
]);
}
private function scopeMatches(ManagedEnvironment $environment, ProviderConnection $connection): bool
{
return (int) $connection->workspace_id === (int) $environment->workspace_id
&& (int) $connection->managed_environment_id === (int) $environment->getKey();
}
private function blockedFromGate(ExchangePowerShellGateResult $gate, string $blocker): ExchangePowerShellGateResult
{
return ExchangePowerShellGateResult::blocked(
reasonCode: $gate->reasonCode ?? ProviderReasonCodes::ProviderBindingUnsupported,
failureCode: $gate->failureCode ?? 'readiness_blocked',
message: $gate->message ?? 'Exchange PowerShell invocation readiness was blocked.',
context: [
'readiness_state' => 'blocked',
'readiness_blocker' => $blocker,
...$this->safeContext($gate->context),
],
);
}
/**
* @param array<string, mixed> $context
* @return array<string, mixed>
*/
private function safeContext(array $context): array
{
return array_filter(
$context,
static fn (mixed $value): bool => is_scalar($value) || $value === null,
);
}
}