211 lines
7.4 KiB
PHP
211 lines
7.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\TenantConfiguration;
|
|
|
|
final class EntraRenderableSummaryBuilder
|
|
{
|
|
public function __construct(
|
|
private readonly EntraComparablePayloadNormalizer $normalizer,
|
|
) {}
|
|
|
|
public function supports(string $canonicalType): bool
|
|
{
|
|
return $this->normalizer->supports($canonicalType);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $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<string, mixed> $payload
|
|
* @param array<string, mixed> $context
|
|
* @return array<string, mixed>|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<string> $include
|
|
* @param list<string> $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<string> $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<string, mixed> $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<string, mixed> $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;
|
|
}
|
|
}
|