Automated PR provided by Codex via Gitea API. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #484
156 lines
5.8 KiB
PHP
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;
|
|
}
|
|
}
|