src/Cms/CoreBundle/Service/SystemSetup.php line 92

Open in your IDE?
  1. <?php
  2. namespace Cms\CoreBundle\Service;
  3. use Cms\CoreBundle\Model\Contexts\GlobalContext;
  4. use Cms\CoreBundle\Model\Scenes\DashboardScenes\DocumentScene;
  5. use Cms\CoreBundle\Util\Doctrine\EntityManager;
  6. use Cms\DomainBundle\Entity\Domain;
  7. use Cms\TenantBundle\Entity\Tenant;
  8. use Laminas\Uri\Uri;
  9. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  10. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  11. use Symfony\Component\HttpFoundation\RedirectResponse;
  12. use Symfony\Component\HttpFoundation\Response;
  13. use Symfony\Component\HttpKernel\Event\RequestEvent;
  14. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  15. use Symfony\Component\HttpKernel\KernelEvents;
  16. /**
  17.  * One of the most crucial pieces to the system is knowing what tenant is currently active, if any.
  18.  * This service is used to determine what tenant is active and sets up any needed hooks based on that.
  19.  * (One such thing that is set up is a global Doctrine SQL filter that always runs queries based on tenant information.)
  20.  *
  21.  * Class SystemSetup
  22.  * @package Cms\CoreBundle\Service
  23.  */
  24. final class SystemSetup implements EventSubscriberInterface
  25. {
  26.     /**
  27.      * @var GlobalContext
  28.      */
  29.     private GlobalContext $globalContext;
  30.     /**
  31.      * @var EntityManager
  32.      */
  33.     private EntityManager $em;
  34.     /**
  35.      * @var string
  36.      */
  37.     private string $stagingDomain;
  38.     /**
  39.      * @var SceneRenderer
  40.      */
  41.     private SceneRenderer $sceneRenderer;
  42.     /**
  43.      * @var SceneRenderer
  44.      */
  45.     private ParameterBagInterface $params;
  46.     /**
  47.      * @param ContextManager $contextManager
  48.      * @param EntityManager $em
  49.      * @param string $stagingDomain
  50.      * @param SceneRenderer $sceneRenderer
  51.      * @param ParameterBagInterface $params
  52.      */
  53.     public function __construct(
  54.         ContextManager $contextManager,
  55.         EntityManager $em,
  56.         string $stagingDomain,
  57.         SceneRenderer $sceneRenderer,
  58.         ParameterBagInterface $params
  59.     ) {
  60.         $this->globalContext $contextManager->getGlobalContext();
  61.         $this->em $em;
  62.         $this->stagingDomain $stagingDomain;
  63.         $this->sceneRenderer $sceneRenderer;
  64.         $this->params $params;
  65.     }
  66.     /**
  67.      * {@inheritdoc}
  68.      */
  69.     public static function getSubscribedEvents(): array
  70.     {
  71.         return [
  72.             KernelEvents::REQUEST => ['run'9223372036854775806],
  73.         ];
  74.     }
  75.     /**
  76.      * Listens to the "kernel.request" event to execute needed code to get things setup for CMS execution.
  77.      * THIS NEEDS TO RUN AT THE HIGHEST POSSIBLE PRIORITY IN THE LISTENER CHAIN!
  78.      *
  79.      * @param RequestEvent $event
  80.      * @throws \Exception
  81.      */
  82.     public function run(RequestEvent $event): void
  83.     {
  84.         // make sure we only do this on the master request
  85.         if ( ! $event->isMainRequest()) {
  86.             return;
  87.         }
  88.         // make sure that the tenant is not set yet
  89.         if ( ! empty($this->globalContext->getTenant())) {
  90.             throw new \LogicException();
  91.         }
  92.         // not set, process the hostname
  93.         $host $event->getRequest()->getHttpHost();
  94.         $dashboardHost $this->globalContext->getDashboard(true);
  95.         // holder for tenant
  96.         $tenant null;
  97.         $domain null;
  98.         // try to match cases
  99.         if (preg_match('/^([-a-zA-Z0-9]+)\.([-a-zA-Z0-9]+)\.' $this->stagingDomain '$/'$host$matches) === 1) {
  100.             // try and obtain the tenant
  101.             $tenant $this->em->getRepository(Tenant::class)->findOneBySlug($matches[2]);
  102.             if ( ! $tenant) {
  103.                 throw new NotFoundHttpException();
  104.             }
  105.         } else {
  106.             if (preg_match(
  107.                     '/^([0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12})\\.' preg_quote(
  108.                         $dashboardHost,
  109.                         '/',
  110.                     ) . '$/i',
  111.                     $host,
  112.                     $matches
  113.                 ) === 1) {
  114.                 // obtain via uid
  115.                 $tenant $this->em->getRepository(Tenant::class)->findOneByUid($matches[1]);
  116.                 if ( ! $tenant) {
  117.                     throw new NotFoundHttpException();
  118.                 }
  119.             } else {
  120.                 if (preg_match(
  121.                         '/^([-a-zA-Z0-9]+)\\.' preg_quote($dashboardHost'/') . '$/',
  122.                         $host,
  123.                         $matches
  124.                     ) === 1) {
  125.                     // try and obtain the tenant
  126.                     $tenant $this->em->getRepository(Tenant::class)->findOneBySlug($matches[1]);
  127.                     if ( ! $tenant) {
  128.                         throw new NotFoundHttpException();
  129.                     }
  130.                 } else {
  131.                     if ($host === $dashboardHost) {
  132.                         // make sure tenant is null
  133.                         $tenant null;
  134.                     } else {
  135.                         if (preg_match(
  136.                                 '/^([-a-zA-Z0-9]+)\\.' preg_quote('ngrok.io''/') . '$/',
  137.                                 $host,
  138.                                 $matches
  139.                             ) === 1) {
  140.                             // make sure tenant is null
  141.                             $tenant null;
  142.                         } else {
  143.                             // nothing handled specially, try to lookup domain
  144.                             try {
  145.                                 $domain $this->em->getRepository(Domain::class)->findOneByHost($host);
  146.                             } catch (\Exception $e) {
  147.                                 $domain null;
  148.                             }
  149.                             // TODO: need to eventually require all sites that need www redirect to be registered in the system...
  150.                             // make sure we have one, if not either need to www redir or show error page
  151.                             if ( ! $domain) {
  152.                                 // parse url
  153.                                 $url = new Uri($event->getRequest()->getUri());
  154.                                 // see if we are already www, if we are, show error page
  155.                                 if (str_starts_with(
  156.                                     $url->getHost(),
  157.                                     'www.'
  158.                                 )) {
  159.                                     $event->setResponse(
  160.                                         new Response(
  161.                                             $this->sceneRenderer->render(
  162.                                                 new DocumentScene(
  163.                                                     '@CmsCore/setup/noDomain.html.twig',
  164.                                                     [
  165.                                                         'host' => $host,
  166.                                                         'debugging' => [
  167.                                                             'kernel.environment' => $this->params->get('kernel.environment'),
  168.                                                             'app.routing.apexes.system' => $this->params->get('app.routing.apexes.system'),
  169.                                                             'app.routing.cluster' => $this->params->get('app.routing.cluster'),
  170.                                                             'dashboard.hostname' => $this->params->get('dashboard.hostname'),
  171.                                                             '_SERVER' => $_SERVER,
  172.                                                         ],
  173.                                                     ]
  174.                                                 )
  175.                                             ),
  176.                                             Response::HTTP_INTERNAL_SERVER_ERROR,
  177.                                             []
  178.                                         )
  179.                                     );
  180.                                     $event->stopPropagation();
  181.                                     return;
  182.                                 }
  183.                                 // no www, do the www redirection
  184.                                 $url->setHost(
  185.                                     sprintf(
  186.                                         'www.%s',
  187.                                         $url->getHost()
  188.                                     )
  189.                                 );
  190.                                 $event->setResponse(
  191.                                     new RedirectResponse(
  192.                                         $url->toString(),
  193.                                         Response::HTTP_FOUND,
  194.                                         []
  195.                                     )
  196.                                 );
  197.                                 $event->stopPropagation();
  198.                                 return;
  199.                             }
  200.                             // we do, set the tenant
  201.                             $tenant $domain->getTenant();
  202.                         }
  203.                     }
  204.                 }
  205.             }
  206.         }
  207.         // set the tenant
  208.         $this->globalContext->escort(
  209.             function (GlobalContext $globalContext) use ($tenant) {
  210.                 // set on the context
  211.                 $globalContext->setTenant($tenant);
  212.                 // set response header
  213.                 /*
  214.                 if ($tenant !== null) {
  215.                     header(
  216.                         sprintf(
  217.                             'X-CAMPUSSUITE-TENANT: %s',
  218.                             $tenant->getId()
  219.                         ),
  220.                         true
  221.                     );
  222.                 }
  223.                 */
  224.             }
  225.         );
  226.         // TODO FIX THIS IN SSL STUFF!!!
  227.         // TODO: handle redirect domain (this needs cleanup)
  228.         /** @var Domain $domain */
  229.         if ($domain && $domain->getRedirection()->isEnabled()) {
  230.             if (strpos($event->getRequest()->getPathInfo(), '/.well-known/acme-challenge/') !== false) {
  231.                 return;
  232.             }
  233.             $redirect $domain->getRedirection();
  234.             $scheme trim($event->getRequest()->getScheme());
  235.             // if marked as https upgrade, be sure to do that here
  236.             // technically this should be handled before doing the redirect, but we can shortcut an extra hop in the redirect chain doing it this way
  237.             if ($domain->isHttpsUpgrade()) {
  238.                 $scheme 'https';
  239.             }
  240.             $host trim($redirect->getHost());
  241.             $path '/' ltrim(trim($redirect->getPath()), '/');
  242.             if ($redirect->getAppendPath() && ! empty(ltrim(trim($event->getRequest()->getPathInfo()), '/'))) {
  243.                 $path rtrim($path'/') . '/' ltrim(trim($event->getRequest()->getPathInfo()), '/');
  244.             }
  245.             $query = ( ! empty(trim($redirect->getAppendQuery()))) ? trim(
  246.                 $event->getRequest()->getQueryString()
  247.             ) : trim($redirect->getQuery());
  248.             $query ltrim($query'?');
  249.             if (empty($host)) {
  250.                 throw new \RuntimeException();
  251.             }
  252.             if ( ! empty($path)) {
  253.                 $path '/' ltrim($path'/');
  254.             }
  255.             if ( ! empty($query)) {
  256.                 $query '?' $query;
  257.             }
  258.             $url $scheme '://' $host $path $query;
  259.             $event->setResponse(
  260.                 new RedirectResponse(
  261.                     $url,
  262.                     $domain->getRealCode(),
  263.                     []
  264.                 )
  265.             );
  266.             $event->stopPropagation();
  267.         }
  268.         // HACK: testing for root-level redirect here to www subdomain
  269.         // if the domain hostname is the same as the apex, we can assume root-level
  270.         // all we are basically going to do is use the same url, but prepend the www piece to the hostname
  271.         if ($domain && $domain->getApex()->getHost() === $domain->getHost()) {
  272.             // TODO: really need to figure out a better way to whitelist URLs that need to pass through...
  273.             if (strpos($event->getRequest()->getPathInfo(), '/.well-known/acme-challenge/') !== false) {
  274.                 return;
  275.             }
  276.             $url = new Uri($event->getRequest()->getUri());
  277.             $url->setHost(
  278.                 sprintf(
  279.                     'www.%s',
  280.                     $url->getHost()
  281.                 )
  282.             );
  283.             $event->setResponse(
  284.                 new RedirectResponse(
  285.                     $url->toString(),
  286.                     Response::HTTP_FOUND,
  287.                     []
  288.                 )
  289.             );
  290.             $event->stopPropagation();
  291.         }
  292.     }
  293. }