TenantAtlas/apps/platform/app/Services/TenantConfiguration/CoverageResourceUpserter.php
Ahmed Darrazi 5ceecdeb62
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 6m40s
feat: implement canonical identity engine
2026-06-26 08:46:18 +02:00

156 lines
5.8 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\TenantConfiguration;
use App\Models\ManagedEnvironment;
use App\Models\ProviderConnection;
use App\Models\TenantConfigurationResource;
use App\Models\TenantConfigurationResourceType;
use App\Support\TenantConfiguration\ClaimState;
use App\Support\TenantConfiguration\EvidenceState;
use App\Support\TenantConfiguration\SourceClass;
use InvalidArgumentException;
final class CoverageResourceUpserter
{
public function __construct(
private readonly CanonicalIdentityResolver $identityResolver,
private readonly CoverageResourceIdentityEvaluator $identityEvaluator,
private readonly ClaimGuard $claimGuard,
) {}
/**
* @param array<string, mixed> $payload
* @param array<string, mixed> $sourceMetadata
*/
public function upsert(
ManagedEnvironment $tenant,
ProviderConnection $providerConnection,
TenantConfigurationResourceType $resourceType,
array $payload,
array $sourceMetadata = [],
): TenantConfigurationResource {
$this->assertScoped($tenant, $providerConnection);
$identity = $this->identityEvaluator->evaluate(
result: $this->identityResolver->resolve($resourceType, $payload, $sourceMetadata),
tenant: $tenant,
providerConnection: $providerConnection,
resourceType: $resourceType,
);
$canonicalType = (string) $resourceType->canonical_type;
$sourceClass = $resourceType->source_class instanceof SourceClass
? $resourceType->source_class->value
: (string) $resourceType->source_class;
$claimState = $this->claimStateFor($resourceType, $identity);
$resource = TenantConfigurationResource::query()->firstOrNew([
'workspace_id' => (int) $tenant->workspace_id,
'managed_environment_id' => (int) $tenant->getKey(),
'provider_connection_id' => (int) $providerConnection->getKey(),
'resource_type_id' => (int) $resourceType->getKey(),
'canonical_resource_key' => $identity->canonicalResourceKey,
]);
$resource->fill([
'source_class' => $sourceClass,
'canonical_type' => $canonicalType,
'canonical_key_kind' => $identity->keyKind->value,
'source_resource_id' => $identity->sourceResourceId,
'source_display_name' => $this->extractDisplayName($payload),
'source_metadata' => $this->jsonObject($sourceMetadata),
'identity_strategy' => $identity->strategyIdentifier,
'source_identity' => $identity->sourceIdentity,
'secondary_identity_keys' => $this->jsonObject($identity->secondaryKeys),
'identity_diagnostics' => $this->jsonObject($identity->diagnostics),
'identity_evaluated_at' => now(),
'latest_identity_state' => $identity->identityState->value,
'latest_claim_state' => $claimState->value,
]);
if (! $resource->exists) {
$resource->forceFill([
'latest_evidence_state' => EvidenceState::NotCaptured->value,
]);
}
$resource->save();
return $resource;
}
private function claimStateFor(
TenantConfigurationResourceType $resourceType,
CanonicalIdentityResult $identity,
): ClaimState {
$guarded = $this->claimGuard->evaluate(
scopeKey: 'intune_tcm_core',
requestedLevel: $resourceType->default_coverage_level,
actualLevel: $resourceType->default_coverage_level,
scopeComplete: true,
sourceClass: $resourceType->source_class,
restoreTier: $resourceType->restore_tier,
identityState: $identity->identityState,
allowsBetaClaims: (bool) $resourceType->allows_beta_claims,
allowsCertifiedClaims: (bool) $resourceType->allows_certified_claims,
allowsDerivedIdentityClaims: $identity->derivedClaimsAllowed,
);
$default = $resourceType->default_claim_state;
$default = $default instanceof ClaimState ? $default : ClaimState::from((string) $default);
return $this->mostRestrictiveClaimState($guarded, $default);
}
private function mostRestrictiveClaimState(ClaimState $guarded, ClaimState $default): ClaimState
{
return $this->claimStateRank($guarded) >= $this->claimStateRank($default)
? $guarded
: $default;
}
private function claimStateRank(ClaimState $claimState): int
{
return match ($claimState) {
ClaimState::ClaimAllowed => 0,
ClaimState::ClaimLimited => 10,
ClaimState::InternalOnly => 20,
ClaimState::ClaimBlocked => 30,
};
}
private function jsonObject(array $value): array|object
{
return $value === [] ? (object) [] : $value;
}
private function assertScoped(ManagedEnvironment $tenant, ProviderConnection $providerConnection): void
{
if ((int) $providerConnection->managed_environment_id !== (int) $tenant->getKey()) {
throw new InvalidArgumentException('Provider connection does not belong to the managed environment.');
}
if ((int) $providerConnection->workspace_id !== (int) $tenant->workspace_id) {
throw new InvalidArgumentException('Provider connection does not belong to the managed environment workspace.');
}
}
/**
* @param array<string, mixed> $payload
*/
private function extractDisplayName(array $payload): ?string
{
$displayName = $payload['displayName'] ?? $payload['name'] ?? null;
if (! is_scalar($displayName)) {
return null;
}
$displayName = trim((string) $displayName);
return $displayName !== '' ? $displayName : null;
}
}