TenantAtlas/app/Services/Providers/ProviderConnectionStateProjector.php
ahmido bab01f07a9 feat: standardize platform provider identity (#166)
## Summary
- standardize Microsoft provider connections around explicit platform vs dedicated identity modes
- centralize admin-consent URL and runtime identity resolution so platform flows no longer fall back to tenant-local credentials
- add migration classification, richer consent and verification state handling, dedicated override management, and focused regression coverage

## Validation
- focused repo test coverage was added across provider identity, onboarding, audit, policy, guard, and migration flows
- latest explicit passing run in the workspace: `vendor/bin/sail artisan test --compact tests/Feature/AdminConsentCallbackTest.php tests/Feature/Audit/ProviderConnectionConsentAuditTest.php`

## Notes
- branch includes the full Spec 137 artifact set under `specs/137-platform-provider-identity/`
- target base branch: `dev`

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #166
2026-03-13 16:29:08 +00:00

244 lines
9.1 KiB
PHP

<?php
namespace App\Services\Providers;
use App\Models\ProviderConnection;
use App\Services\Providers\Contracts\HealthResult;
use App\Support\Providers\ProviderConnectionType;
use App\Support\Providers\ProviderConsentStatus;
use App\Support\Providers\ProviderReasonCodes;
use App\Support\Providers\ProviderVerificationStatus;
final class ProviderConnectionStateProjector
{
/**
* @return array{status: string, health_status: string}
*/
public function projectForConnection(ProviderConnection $connection): array
{
return $this->project(
connectionType: $connection->connection_type,
consentStatus: $connection->consent_status,
verificationStatus: $connection->verification_status,
currentStatus: is_string($connection->status) ? $connection->status : null,
);
}
/**
* @return array{status: string, health_status: string}
*/
public function project(
ProviderConnectionType|string|null $connectionType,
ProviderConsentStatus|string|null $consentStatus,
ProviderVerificationStatus|string|null $verificationStatus,
?string $currentStatus = null,
): array {
$resolvedConnectionType = $this->normalizeConnectionType($connectionType) ?? ProviderConnectionType::Platform;
$resolvedConsentStatus = $this->normalizeConsentStatus($consentStatus) ?? ProviderConsentStatus::Unknown;
$resolvedVerificationStatus = $this->normalizeVerificationStatus($verificationStatus) ?? ProviderVerificationStatus::Unknown;
$status = $currentStatus === 'disabled'
? 'disabled'
: $this->projectStatus($resolvedConnectionType, $resolvedConsentStatus, $resolvedVerificationStatus);
return [
'status' => $status,
'health_status' => $this->projectHealthStatus($resolvedVerificationStatus),
];
}
/**
* @return array{
* consent_status: ProviderConsentStatus,
* verification_status: ProviderVerificationStatus,
* status: string,
* health_status: string,
* last_error_reason_code: ?string,
* last_error_message: ?string,
* consent_error_code: ?string,
* consent_error_message: ?string,
* consent_revoked_detected: bool
* }
*/
public function projectVerificationOutcome(ProviderConnection $connection, HealthResult $result): array
{
$currentConsentStatus = $this->normalizeConsentStatus($connection->consent_status)
?? (((string) $connection->status === 'needs_consent') ? ProviderConsentStatus::Required : ProviderConsentStatus::Unknown);
$effectiveReasonCode = $result->healthy
? null
: $this->effectiveReasonCodeForVerification($currentConsentStatus, $result->reasonCode);
$consentStatus = $this->consentStatusAfterVerification($currentConsentStatus, $effectiveReasonCode, $result->healthy);
$verificationStatus = $this->verificationStatusAfterVerification($effectiveReasonCode, $result->healthy, $result->healthStatus);
$projected = $this->project(
connectionType: $connection->connection_type,
consentStatus: $consentStatus,
verificationStatus: $verificationStatus,
currentStatus: is_string($connection->status) ? $connection->status : null,
);
$consentErrorCode = in_array($consentStatus, [
ProviderConsentStatus::Required,
ProviderConsentStatus::Failed,
ProviderConsentStatus::Revoked,
], true) ? $effectiveReasonCode : null;
return [
'consent_status' => $consentStatus,
'verification_status' => $verificationStatus,
'status' => $projected['status'],
'health_status' => $projected['health_status'],
'last_error_reason_code' => $effectiveReasonCode,
'last_error_message' => $result->healthy ? null : $result->message,
'consent_error_code' => $consentErrorCode,
'consent_error_message' => $consentErrorCode === null || $result->healthy ? null : $result->message,
'consent_revoked_detected' => $currentConsentStatus === ProviderConsentStatus::Granted
&& $effectiveReasonCode === ProviderReasonCodes::ProviderConsentRevoked,
];
}
private function normalizeConnectionType(ProviderConnectionType|string|null $connectionType): ?ProviderConnectionType
{
if ($connectionType instanceof ProviderConnectionType) {
return $connectionType;
}
if (! is_string($connectionType)) {
return null;
}
return ProviderConnectionType::tryFrom(trim($connectionType));
}
private function normalizeConsentStatus(ProviderConsentStatus|string|null $consentStatus): ?ProviderConsentStatus
{
if ($consentStatus instanceof ProviderConsentStatus) {
return $consentStatus;
}
if (! is_string($consentStatus)) {
return null;
}
return ProviderConsentStatus::tryFrom(trim($consentStatus));
}
private function normalizeVerificationStatus(
ProviderVerificationStatus|string|null $verificationStatus,
): ?ProviderVerificationStatus {
if ($verificationStatus instanceof ProviderVerificationStatus) {
return $verificationStatus;
}
if (! is_string($verificationStatus)) {
return null;
}
return ProviderVerificationStatus::tryFrom(trim($verificationStatus));
}
private function projectStatus(
ProviderConnectionType $connectionType,
ProviderConsentStatus $consentStatus,
ProviderVerificationStatus $verificationStatus,
): string {
if ($connectionType === ProviderConnectionType::Dedicated && $verificationStatus === ProviderVerificationStatus::Blocked) {
return 'error';
}
if ($consentStatus === ProviderConsentStatus::Failed) {
return 'error';
}
if ($consentStatus !== ProviderConsentStatus::Granted) {
return 'needs_consent';
}
return match ($verificationStatus) {
ProviderVerificationStatus::Blocked,
ProviderVerificationStatus::Error => 'error',
default => 'connected',
};
}
private function projectHealthStatus(ProviderVerificationStatus $verificationStatus): string
{
return match ($verificationStatus) {
ProviderVerificationStatus::Healthy => 'ok',
ProviderVerificationStatus::Degraded => 'degraded',
ProviderVerificationStatus::Blocked,
ProviderVerificationStatus::Error => 'down',
default => 'unknown',
};
}
private function effectiveReasonCodeForVerification(
ProviderConsentStatus $currentConsentStatus,
?string $reasonCode,
): ?string {
if (! is_string($reasonCode) || $reasonCode === '') {
return null;
}
if (
$currentConsentStatus === ProviderConsentStatus::Granted
&& $reasonCode === ProviderReasonCodes::ProviderConsentMissing
) {
return ProviderReasonCodes::ProviderConsentRevoked;
}
return $reasonCode;
}
private function consentStatusAfterVerification(
ProviderConsentStatus $currentConsentStatus,
?string $reasonCode,
bool $healthy,
): ProviderConsentStatus {
if ($healthy) {
return ProviderConsentStatus::Granted;
}
return match ($reasonCode) {
ProviderReasonCodes::ProviderConsentMissing => ProviderConsentStatus::Required,
ProviderReasonCodes::ProviderConsentFailed => ProviderConsentStatus::Failed,
ProviderReasonCodes::ProviderConsentRevoked => ProviderConsentStatus::Revoked,
default => $currentConsentStatus,
};
}
private function verificationStatusAfterVerification(
?string $reasonCode,
bool $healthy,
string $healthStatus,
): ProviderVerificationStatus {
if ($healthy) {
return ProviderVerificationStatus::Healthy;
}
if ($healthStatus === 'degraded' || $reasonCode === ProviderReasonCodes::RateLimited) {
return ProviderVerificationStatus::Degraded;
}
if (in_array($reasonCode, [
ProviderReasonCodes::ProviderConsentMissing,
ProviderReasonCodes::ProviderConsentFailed,
ProviderReasonCodes::ProviderConsentRevoked,
ProviderReasonCodes::PlatformIdentityMissing,
ProviderReasonCodes::PlatformIdentityIncomplete,
ProviderReasonCodes::DedicatedCredentialMissing,
ProviderReasonCodes::DedicatedCredentialInvalid,
ProviderReasonCodes::ProviderConnectionInvalid,
ProviderReasonCodes::ProviderConnectionReviewRequired,
ProviderReasonCodes::ProviderConnectionTypeInvalid,
ProviderReasonCodes::TenantTargetMismatch,
], true)) {
return ProviderVerificationStatus::Blocked;
}
return ProviderVerificationStatus::Error;
}
}