TenantAtlas/app/Services/Providers/ProviderConnectionClassifier.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

119 lines
4.8 KiB
PHP

<?php
namespace App\Services\Providers;
use App\Models\ProviderConnection;
use App\Models\ProviderCredential;
use App\Models\Tenant;
use App\Support\Providers\ProviderConnectionType;
final class ProviderConnectionClassifier
{
public function classify(
ProviderConnection $connection,
string $source = 'migration_scan',
): ProviderConnectionClassificationResult {
$connection->loadMissing(['credential', 'tenant']);
$tenant = $connection->tenant;
$credential = $connection->credential;
$legacyIdentity = $tenant instanceof Tenant ? $tenant->legacyProviderIdentity() : ['client_id' => null, 'has_secret' => false];
$tenantClientId = trim((string) ($legacyIdentity['client_id'] ?? ''));
$credentialClientId = $this->credentialClientId($credential);
$currentConnectionType = $connection->connection_type instanceof ProviderConnectionType
? $connection->connection_type->value
: (is_string($connection->connection_type) ? $connection->connection_type : null);
$hasLegacyTenantIdentity = $tenantClientId !== '' || (bool) ($legacyIdentity['has_secret'] ?? false);
$hasDedicatedCredential = $credential instanceof ProviderCredential;
$suggestedConnectionType = ProviderConnectionType::Platform;
$reviewRequired = false;
if ($hasDedicatedCredential && ! $hasLegacyTenantIdentity) {
$suggestedConnectionType = ProviderConnectionType::Dedicated;
} elseif ($hasDedicatedCredential && $hasLegacyTenantIdentity) {
$suggestedConnectionType = ProviderConnectionType::Dedicated;
$reviewRequired = $tenantClientId === '' || $credentialClientId === '' || $tenantClientId !== $credentialClientId;
} elseif (! $hasDedicatedCredential && $hasLegacyTenantIdentity) {
$suggestedConnectionType = ProviderConnectionType::Platform;
$reviewRequired = true;
}
return new ProviderConnectionClassificationResult(
providerConnectionId: (int) $connection->getKey(),
suggestedConnectionType: $suggestedConnectionType,
reviewRequired: $reviewRequired,
signals: [
'has_dedicated_credential' => $hasDedicatedCredential,
'credential_client_id' => $credentialClientId !== '' ? $credentialClientId : null,
'has_legacy_tenant_identity' => $hasLegacyTenantIdentity,
'tenant_client_id' => $tenantClientId !== '' ? $tenantClientId : null,
'tenant_has_secret' => (bool) ($legacyIdentity['has_secret'] ?? false),
'current_connection_type' => $currentConnectionType,
'consent_status' => $this->enumValue($connection->consent_status),
'verification_status' => $this->enumValue($connection->verification_status),
'status' => is_string($connection->status) ? $connection->status : null,
'last_error_reason_code' => is_string($connection->last_error_reason_code) ? $connection->last_error_reason_code : null,
],
effectiveApp: $this->effectiveAppMetadata(
suggestedConnectionType: $suggestedConnectionType,
reviewRequired: $reviewRequired,
credentialClientId: $credentialClientId,
),
source: $source,
);
}
private function credentialClientId(?ProviderCredential $credential): string
{
if (! $credential instanceof ProviderCredential || ! is_array($credential->payload)) {
return '';
}
return trim((string) ($credential->payload['client_id'] ?? ''));
}
/**
* @return array{app_id: ?string, source: string}
*/
private function effectiveAppMetadata(
ProviderConnectionType $suggestedConnectionType,
bool $reviewRequired,
string $credentialClientId,
): array {
if ($reviewRequired) {
return [
'app_id' => null,
'source' => 'review_required',
];
}
if ($suggestedConnectionType === ProviderConnectionType::Dedicated) {
return [
'app_id' => $credentialClientId !== '' ? $credentialClientId : null,
'source' => 'dedicated_credential',
];
}
$platformClientId = trim((string) config('graph.client_id'));
return [
'app_id' => $platformClientId !== '' ? $platformClientId : null,
'source' => 'platform_config',
];
}
private function enumValue(mixed $value): ?string
{
if ($value instanceof \BackedEnum) {
return $value->value;
}
if (is_string($value) && trim($value) !== '') {
return trim($value);
}
return null;
}
}