normalizer->supports($canonicalType); } /** * @param array $payload */ public function canBuild(string $canonicalType, array $payload): bool { if (! $this->supports($canonicalType)) { return false; } $normalized = $this->normalizer->normalize($canonicalType, $payload); return ($normalized['supported'] ?? false) === true; } /** * @param array $payload * @param array $context * @return array|null */ public function build(string $canonicalType, array $payload, array $context = []): ?array { if (! $this->supports($canonicalType)) { return null; } $normalized = $this->normalizer->normalize($canonicalType, $payload); if (($normalized['supported'] ?? false) !== true) { return null; } return [ 'resource_type' => 'Conditional Access policy', 'display_name' => $normalized['display_name'] ?? 'Unnamed Conditional Access policy', 'state' => $normalized['state'] ?? 'unknown', 'targets' => [ ['label' => 'Users', 'value' => $this->includeExcludeSummary( data_get($normalized, 'targets.users.include_users', []), data_get($normalized, 'targets.users.exclude_users', []), 'No users included', )], ['label' => 'Groups', 'value' => $this->includeExcludeSummary( data_get($normalized, 'targets.users.include_groups', []), data_get($normalized, 'targets.users.exclude_groups', []), 'No groups included', )], ['label' => 'Roles', 'value' => $this->includeExcludeSummary( data_get($normalized, 'targets.users.include_roles', []), data_get($normalized, 'targets.users.exclude_roles', []), 'No roles included', )], ['label' => 'Applications', 'value' => $this->includeExcludeSummary( data_get($normalized, 'targets.applications.include_applications', []), data_get($normalized, 'targets.applications.exclude_applications', []), 'No applications included', )], ], 'conditions' => [ ['label' => 'Client apps', 'value' => $this->listSummary(data_get($normalized, 'conditions.client_app_types', []), 'Any client app')], ['label' => 'Platforms', 'value' => $this->includeExcludeSummary( data_get($normalized, 'conditions.platforms.include_platforms', []), data_get($normalized, 'conditions.platforms.exclude_platforms', []), 'Any platform', )], ['label' => 'Locations', 'value' => $this->includeExcludeSummary( data_get($normalized, 'conditions.locations.include_locations', []), data_get($normalized, 'conditions.locations.exclude_locations', []), 'Any location', )], ['label' => 'User risk', 'value' => $this->listSummary(data_get($normalized, 'conditions.user_risk_levels', []), 'Any user risk')], ['label' => 'Sign-in risk', 'value' => $this->listSummary(data_get($normalized, 'conditions.sign_in_risk_levels', []), 'Any sign-in risk')], ], 'grant_controls' => $this->grantControlsSummary(data_get($normalized, 'grant_controls', [])), 'session_controls' => $this->sessionControlsSummary(data_get($normalized, 'session_controls', [])), 'claim_state' => $this->stringContext($context, 'claim_state'), 'identity_state' => $this->stringContext($context, 'identity_state'), 'last_captured' => $this->stringContext($context, 'last_captured'), 'unsupported_fields' => data_get($normalized, 'diagnostics.unsupported_fields', []), 'redacted_fields' => data_get($normalized, 'diagnostics.redacted_fields', []), ]; } /** * @param list $include * @param list $exclude */ private function includeExcludeSummary(array $include, array $exclude, string $empty): string { $parts = []; if ($include !== []) { $parts[] = 'Include '.$this->listSummary($include, $empty); } if ($exclude !== []) { $parts[] = 'Exclude '.$this->listSummary($exclude, 'none'); } return $parts === [] ? $empty : implode('; ', $parts); } /** * @param list $values */ private function listSummary(array $values, string $empty): string { $values = array_values(array_filter( array_map(static fn (mixed $value): string => is_scalar($value) ? trim((string) $value) : '', $values), static fn (string $value): bool => $value !== '', )); if ($values === []) { return $empty; } return implode(', ', $values); } /** * @param array $grantControls */ private function grantControlsSummary(array $grantControls): string { $parts = []; $operator = $grantControls['operator'] ?? null; if (is_string($operator) && $operator !== '') { $parts[] = 'Operator '.$operator; } foreach ([ 'built_in_controls' => 'Built-in', 'custom_authentication_factors' => 'Custom factors', 'terms_of_use' => 'Terms of use', ] as $key => $label) { $summary = $this->listSummary(is_array($grantControls[$key] ?? null) ? $grantControls[$key] : [], ''); if ($summary !== '') { $parts[] = $label.': '.$summary; } } return $parts === [] ? 'No grant controls' : implode('; ', $parts); } private function sessionControlsSummary(mixed $sessionControls): string { if (! is_array($sessionControls) || $sessionControls === []) { return 'No session controls'; } $parts = []; foreach ($sessionControls as $key => $value) { $parts[] = str((string) $key)->headline()->toString().': '.$this->stringify($value); } return implode('; ', $parts); } private function stringify(mixed $value): string { if (is_bool($value)) { return $value ? 'yes' : 'no'; } if (is_scalar($value)) { return (string) $value; } return json_encode($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR); } /** * @param array $context */ private function stringContext(array $context, string $key): ?string { $value = $context[$key] ?? null; if ($value instanceof \BackedEnum) { return (string) $value->value; } if (! is_scalar($value)) { return null; } $value = trim((string) $value); return $value !== '' ? $value : null; } }