claimTokens($claim); $registryScoped = $this->isRegistryScopedStatement($tokens); if ($this->hasUnsafeBroadCoverageClaim($tokens, $registryScoped)) { return ClaimState::ClaimBlocked; } if ($internalOperatorOnly && $registryScoped) { return ClaimState::InternalOnly; } return $registryScoped ? ClaimState::ClaimBlocked : ClaimState::ClaimLimited; } public function evaluate( ?string $scopeKey, CoverageLevel|string $requestedLevel, CoverageLevel|string $actualLevel, bool $scopeComplete, bool $customerFacing = false, bool $customerClaimsAllowed = true, bool $unscoped = false, ?int $percentage = null, SourceClass|string|null $sourceClass = null, RestoreTier|string|null $restoreTier = null, IdentityState|string|null $identityState = null, bool $restoreClaim = false, bool $allowsBetaClaims = false, bool $allowsCertifiedClaims = false, bool $allowsDerivedIdentityClaims = false, ): ClaimState { $requested = $this->coverageLevel($requestedLevel); $actual = $this->coverageLevel($actualLevel); $source = $this->sourceClass($sourceClass); $restore = $this->restoreTier($restoreTier); $identity = $this->identityState($identityState); if (($scopeKey === null || $unscoped) && $percentage === 100) { return ClaimState::ClaimBlocked; } if (in_array($identity, [ IdentityState::IdentityConflict, IdentityState::MissingExternalId, IdentityState::UnsupportedIdentity, ], true)) { return ClaimState::ClaimBlocked; } if ($identity === IdentityState::Derived && ! $allowsDerivedIdentityClaims) { return $customerFacing ? ClaimState::ClaimBlocked : ClaimState::ClaimLimited; } if ($source?->isBetaExperimental() === true) { if (! $allowsBetaClaims) { return ClaimState::ClaimBlocked; } if ($requested === CoverageLevel::Certified && ! $allowsCertifiedClaims) { return ClaimState::ClaimBlocked; } } if (($restoreClaim || $requested->meets(CoverageLevel::Restorable)) && $restore !== RestoreTier::Restorable) { return ClaimState::ClaimBlocked; } if ($customerFacing && (! $scopeComplete || ! $customerClaimsAllowed)) { return ClaimState::ClaimBlocked; } if ($scopeKey === null || ! $actual->meets($requested)) { return ClaimState::ClaimLimited; } return ClaimState::ClaimAllowed; } private function coverageLevel(CoverageLevel|string $level): CoverageLevel { return $level instanceof CoverageLevel ? $level : CoverageLevel::from($level); } private function sourceClass(SourceClass|string|null $sourceClass): ?SourceClass { if ($sourceClass === null || $sourceClass instanceof SourceClass) { return $sourceClass; } return SourceClass::from($sourceClass); } private function restoreTier(RestoreTier|string|null $restoreTier): ?RestoreTier { if ($restoreTier === null || $restoreTier instanceof RestoreTier) { return $restoreTier; } return RestoreTier::from($restoreTier); } private function identityState(IdentityState|string|null $identityState): ?IdentityState { if ($identityState === null || $identityState instanceof IdentityState) { return $identityState; } return IdentityState::from($identityState); } /** * @return list */ private function claimTokens(string $claim): array { $normalized = strtolower($claim); $normalized = str_replace('%', ' percent ', $normalized); $normalized = (string) preg_replace('/[^a-z0-9]+/', ' ', $normalized); $normalized = (string) preg_replace('/\s+/', ' ', trim($normalized)); if ($normalized === '') { return []; } return explode(' ', $normalized); } /** * @param list $tokens */ private function isRegistryScopedStatement(array $tokens): bool { return $this->hasToken($tokens, 'registry') && $this->hasToken($tokens, 'coverage') && $this->hasToken($tokens, 'seeded') && $this->hasToken($tokens, 'resource') && $this->hasToken($tokens, 'type') && $this->hasToken($tokens, 'entries'); } /** * @param list $tokens */ private function hasUnsafeBroadCoverageClaim(array $tokens, bool $registryScoped): bool { $hasCoverageSurface = $this->hasAnyToken($tokens, [ 'coverage', 'resource', 'resources', 'support', 'supported', 'tenant', ]); $hasWorkloadReference = $this->hasMicrosoft365Reference($tokens) || $this->hasAnyToken($tokens, [ 'entra', 'exchange', 'teams', 'defender', 'purview', 'tcm', ]) || ($this->hasToken($tokens, 'security') && $this->hasToken($tokens, 'compliance')); if ($this->hasHundredPercent($tokens) && ! $registryScoped) { return true; } if ($this->hasCertificationTerm($tokens) && ($hasWorkloadReference || $hasCoverageSurface || $registryScoped)) { return true; } if ($this->hasRestoreReadyTerm($tokens) && ($hasWorkloadReference || $hasCoverageSurface || $registryScoped)) { return true; } if ($this->hasAnyToken($tokens, ['full', 'complete', 'all']) && ($hasCoverageSurface || $this->hasToken($tokens, 'tenant')) && ($hasWorkloadReference || $this->hasToken($tokens, 'tenant'))) { return true; } return false; } /** * @param list $tokens */ private function hasMicrosoft365Reference(array $tokens): bool { return $this->hasToken($tokens, 'm365') || ($this->hasToken($tokens, 'microsoft') && $this->hasToken($tokens, '365')); } /** * @param list $tokens */ private function hasHundredPercent(array $tokens): bool { return $this->hasToken($tokens, '100') && $this->hasToken($tokens, 'percent'); } /** * @param list $tokens */ private function hasCertificationTerm(array $tokens): bool { return $this->hasAnyToken($tokens, [ 'certified', 'certification', 'certify', 'certifies', ]); } /** * @param list $tokens */ private function hasRestoreReadyTerm(array $tokens): bool { return $this->hasToken($tokens, 'restorable') || ($this->hasToken($tokens, 'restore') && $this->hasAnyToken($tokens, ['ready', 'readiness'])) || ($this->hasToken($tokens, 'restore') && $this->hasToken($tokens, 'coverage')); } /** * @param list $tokens */ private function hasAnyToken(array $tokens, array $expectedTokens): bool { foreach ($expectedTokens as $token) { if ($this->hasToken($tokens, $token)) { return true; } } return false; } /** * @param list $tokens */ private function hasToken(array $tokens, string $expectedToken): bool { return in_array($expectedToken, $tokens, true); } }