TenantAtlas/app/Support/Verification/VerificationLinkBehavior.php
2026-03-14 02:59:06 +01:00

150 lines
4.1 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Support\Verification;
use App\Support\Providers\ProviderReasonCodes;
final class VerificationLinkBehavior
{
/**
* @return array{
* label:string,
* url:string,
* kind:'external'|'internal-diagnostic'|'internal-inline-safe',
* opens_in_new_tab:bool,
* show_new_tab_hint:bool
* }
*/
public function describe(?string $label, ?string $url): array
{
$normalizedLabel = is_string($label) && trim($label) !== ''
? trim($label)
: 'Open link';
$normalizedUrl = is_string($url) && trim($url) !== ''
? trim($url)
: '';
$kind = $this->classify($normalizedUrl);
$opensInNewTab = $kind !== 'internal-inline-safe' && $normalizedUrl !== '';
return [
'label' => $normalizedLabel,
'url' => $normalizedUrl,
'kind' => $kind,
'opens_in_new_tab' => $opensInNewTab,
'show_new_tab_hint' => $opensInNewTab,
];
}
/**
* @param array<string, mixed> $check
*/
public function shouldRouteThroughAssist(array $check, bool $assistVisible): bool
{
if (! $assistVisible) {
return false;
}
$key = $check['key'] ?? null;
$key = is_string($key) ? trim($key) : '';
if ($key !== '' && str_starts_with($key, 'permissions.')) {
return true;
}
$reasonCode = $check['reason_code'] ?? null;
$reasonCode = is_string($reasonCode) ? trim($reasonCode) : '';
return in_array($reasonCode, [
ProviderReasonCodes::ProviderConsentMissing,
ProviderReasonCodes::ProviderConsentFailed,
ProviderReasonCodes::ProviderConsentRevoked,
ProviderReasonCodes::ProviderPermissionMissing,
ProviderReasonCodes::ProviderPermissionDenied,
ProviderReasonCodes::ProviderPermissionRefreshFailed,
ProviderReasonCodes::IntuneRbacPermissionMissing,
], true);
}
/**
* @return 'external'|'internal-diagnostic'|'internal-inline-safe'
*/
private function classify(string $url): string
{
if ($url === '') {
return 'internal-inline-safe';
}
$path = $this->extractPath($url);
if ($path !== null && $this->isInternalDiagnosticPath($path)) {
return 'internal-diagnostic';
}
if ($this->isExternalUrl($url)) {
return 'external';
}
return 'internal-inline-safe';
}
private function extractPath(string $url): ?string
{
$path = parse_url($url, PHP_URL_PATH);
if (is_string($path) && $path !== '') {
return '/'.ltrim($path, '/');
}
if (str_starts_with($url, '/')) {
return '/'.ltrim($url, '/');
}
return null;
}
private function isExternalUrl(string $url): bool
{
$scheme = parse_url($url, PHP_URL_SCHEME);
if (! is_string($scheme) || ! in_array(strtolower($scheme), ['http', 'https'], true)) {
return false;
}
$host = parse_url($url, PHP_URL_HOST);
if (! is_string($host) || $host === '') {
return true;
}
$applicationHost = parse_url(url('/'), PHP_URL_HOST);
$applicationPort = parse_url(url('/'), PHP_URL_PORT);
$urlPort = parse_url($url, PHP_URL_PORT);
if (! is_string($applicationHost) || $applicationHost === '') {
return true;
}
if (strcasecmp($host, $applicationHost) !== 0) {
return true;
}
if ($applicationPort === null || $urlPort === null) {
return false;
}
return (int) $urlPort !== (int) $applicationPort;
}
private function isInternalDiagnosticPath(string $path): bool
{
return (bool) preg_match(
'/^\/admin\/(?:tenants\/[^\/]+\/required-permissions|tenants\/[^\/]+\/provider-connections(?:\/create|\/[^\/]+(?:\/edit)?)?|provider-connections(?:\/create|\/[^\/]+(?:\/edit)?)?)$/',
$path,
);
}
}