where('provider_connection_id', (int) $connection->getKey()) ->first(); if (! $credential instanceof ProviderCredential) { return $this->blocked( reasonCode: ProviderReasonCodes::ProviderCredentialMissing, failureCode: 'credential_blocked_missing_reference', message: 'Exchange PowerShell requires a provider credential reference.', context: ['credential_state' => 'missing'], ); } $kind = $this->credentialKind($credential); $baseContext = [ 'credential_state' => 'blocked', 'credential_kind' => $this->safeCredentialKindLabel($kind), 'credential_reference_id' => (int) $credential->getKey(), 'credential_material_persisted' => false, ]; if ($kind === ProviderCredentialKind::ClientSecret->value) { return $this->blocked( reasonCode: ProviderReasonCodes::ProviderCredentialInvalid, failureCode: 'credential_blocked_secret_based_app', message: 'Secret-based app credentials are not supported for Exchange PowerShell production invocation.', context: $this->credentialContext($baseContext, 'secret_based_app_unsupported'), ); } if ($kind === ProviderCredentialKind::Certificate->value) { if (! $credential->last_rotated_at instanceof Carbon || ! $credential->expires_at instanceof Carbon) { return $this->blocked( reasonCode: ProviderReasonCodes::ProviderCredentialMissing, failureCode: 'credential_blocked_certificate_missing', message: 'Certificate credential metadata is incomplete.', context: $this->credentialContext($baseContext, 'certificate_missing'), ); } if ($credential->expires_at instanceof Carbon && $credential->expires_at->isPast()) { return $this->blocked( reasonCode: ProviderReasonCodes::ProviderCredentialInvalid, failureCode: 'credential_blocked_certificate_expired', message: 'Certificate credential reference is expired.', context: $this->credentialContext($baseContext, 'certificate_expired'), ); } if ($this->credentialKindSupported($kind)) { return ExchangePowerShellGateResult::allowed($this->credentialContext($baseContext, 'certificate_supported')); } return $this->blocked( reasonCode: ProviderReasonCodes::ProviderCredentialInvalid, failureCode: 'credential_blocked_certificate_unsupported', message: 'Certificate credentials are not enabled for Exchange PowerShell production invocation.', context: $this->credentialContext($baseContext, 'certificate_unsupported'), ); } if ($kind === ProviderCredentialKind::Federated->value) { if (! $credential->last_rotated_at instanceof Carbon) { return $this->blocked( reasonCode: ProviderReasonCodes::ProviderCredentialMissing, failureCode: 'credential_blocked_federated_missing', message: 'Federated credential metadata is incomplete.', context: $this->credentialContext($baseContext, 'federated_missing'), ); } if ($this->credentialKindSupported($kind)) { return ExchangePowerShellGateResult::allowed($this->credentialContext($baseContext, 'federated_supported')); } return $this->blocked( reasonCode: ProviderReasonCodes::ProviderCredentialInvalid, failureCode: 'credential_blocked_federated_unsupported', message: 'Federated credentials are not enabled for Exchange PowerShell production invocation.', context: $this->credentialContext($baseContext, 'federated_unsupported'), ); } if ($kind === 'managed_identity') { if ($this->credentialKindSupported($kind)) { return ExchangePowerShellGateResult::allowed($this->credentialContext($baseContext, 'managed_identity_supported')); } return $this->blocked( reasonCode: ProviderReasonCodes::ProviderCredentialInvalid, failureCode: 'credential_blocked_managed_identity_unsupported', message: 'Managed identity credentials are not enabled for Exchange PowerShell production invocation.', context: $this->credentialContext($baseContext, 'managed_identity_unsupported'), ); } return $this->blocked( reasonCode: ProviderReasonCodes::ProviderCredentialInvalid, failureCode: 'credential_blocked_unknown_kind', message: 'Provider credential kind is not supported for Exchange PowerShell production invocation.', context: $this->credentialContext($baseContext, 'unknown_kind'), ); } private function credentialKind(ProviderCredential $credential): string { $kind = $credential->getRawOriginal('credential_kind'); if (! is_string($kind) || trim($kind) === '') { $kind = $credential->getRawOriginal('type'); } return is_string($kind) ? strtolower(trim($kind)) : ''; } private function credentialKindSupported(string $kind): bool { $supportedKinds = config('tenantpilot.exchange_powershell.credentials.supported_reference_kinds', []); if (is_string($supportedKinds)) { $supportedKinds = explode(',', $supportedKinds); } if (! is_array($supportedKinds)) { return false; } $supportedKinds = array_map( static fn (mixed $supportedKind): string => strtolower(trim((string) $supportedKind)), $supportedKinds, ); return in_array($kind, $supportedKinds, true); } private function safeCredentialKindLabel(string $kind): string { return match ($kind) { ProviderCredentialKind::ClientSecret->value => 'secret_based_app', ProviderCredentialKind::Certificate->value => 'certificate', ProviderCredentialKind::Federated->value => 'federated', 'managed_identity' => 'managed_identity', default => 'unknown', }; } /** * @param array $baseContext * @return array */ private function credentialContext(array $baseContext, string $state): array { return array_replace($baseContext, ['credential_state' => $state]); } /** * @param array $context */ private function blocked(string $reasonCode, string $failureCode, string $message, array $context): ExchangePowerShellGateResult { return ExchangePowerShellGateResult::blocked( reasonCode: $reasonCode, failureCode: $failureCode, message: $message, context: $context, ); } }