106 lines
3.5 KiB
PHP
106 lines
3.5 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace MageSail\Magesail\Plugin\Backend\App\Area;
|
|
|
|
use Magento\Backend\App\Area\FrontNameResolver;
|
|
use Magento\Framework\App\Request\Http;
|
|
use Magento\Framework\App\RequestInterface;
|
|
use MageSail\Magesail\Model\TunnelRequestDetector;
|
|
use MageSail\Magesail\Model\TunnelStoreState;
|
|
|
|
/**
|
|
* When the tunnel is used, HTTP_HOST is often dev.myshop.local while default backend base URLs point at production.
|
|
* {@see FrontNameResolver::getFrontName(true)} then returns false, so {@see \Magento\Framework\App\AreaList}
|
|
* never maps the admin front name to adminhtml and /admin_* is dispatched as frontend (404).
|
|
*
|
|
* Bypass host validation when: (1) tunnel proxy headers/query are present, or (2) the request host matches the
|
|
* tunnel store base URLs from provisioned state (browser opened admin URL directly without X-MageSail-Store-Code).
|
|
*/
|
|
class TunnelBackendFrontNameResolverPlugin
|
|
{
|
|
public function __construct(
|
|
private readonly RequestInterface $request,
|
|
private readonly TunnelRequestDetector $tunnelRequestDetector,
|
|
private readonly TunnelStoreState $tunnelStoreState
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* @param FrontNameResolver $subject
|
|
* @param callable(bool $checkHost=): string|false $proceed
|
|
* @return string|false
|
|
*/
|
|
public function aroundGetFrontName(FrontNameResolver $subject, callable $proceed, $checkHost = false)
|
|
{
|
|
$result = $proceed($checkHost);
|
|
if ($result !== false) {
|
|
return $result;
|
|
}
|
|
if (!$checkHost || !$this->request instanceof Http) {
|
|
return $result;
|
|
}
|
|
if (!$this->shouldBypassBackendHostCheck($this->request)) {
|
|
return $result;
|
|
}
|
|
if ($subject->isHostBackend()) {
|
|
return $result;
|
|
}
|
|
return $proceed(false);
|
|
}
|
|
|
|
private function shouldBypassBackendHostCheck(Http $request): bool
|
|
{
|
|
if ($this->tunnelRequestDetector->getTunnelStoreCode($request) !== null) {
|
|
return true;
|
|
}
|
|
return $this->requestHostMatchesTunnelStoreUrls($request);
|
|
}
|
|
|
|
/**
|
|
* Hostname-only match (tunnel unsecure/secure may differ in scheme/port from the current request).
|
|
*/
|
|
private function requestHostMatchesTunnelStoreUrls(Http $request): bool
|
|
{
|
|
$state = $this->tunnelStoreState->read();
|
|
if ($state === null) {
|
|
return false;
|
|
}
|
|
$requestHost = $this->hostFromHttpHost($request->getServer('HTTP_HOST'));
|
|
if ($requestHost === null) {
|
|
return false;
|
|
}
|
|
foreach ([$state['unsecure_base_url'], $state['secure_base_url']] as $baseUrl) {
|
|
if ($baseUrl === '') {
|
|
continue;
|
|
}
|
|
$configuredHost = $this->hostFromBaseUrl($baseUrl);
|
|
if ($configuredHost !== null && strcasecmp($requestHost, $configuredHost) === 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private function hostFromHttpHost(?string $httpHost): ?string
|
|
{
|
|
if ($httpHost === null || $httpHost === '') {
|
|
return null;
|
|
}
|
|
$parsed = parse_url('http://' . $httpHost);
|
|
if ($parsed === false || !isset($parsed['host'])) {
|
|
return null;
|
|
}
|
|
return $parsed['host'];
|
|
}
|
|
|
|
private function hostFromBaseUrl(string $baseUrl): ?string
|
|
{
|
|
$parsed = parse_url($baseUrl);
|
|
if ($parsed === false || !isset($parsed['host'])) {
|
|
return null;
|
|
}
|
|
return $parsed['host'];
|
|
}
|
|
}
|