diff --git a/src/EventSubscriber/RedirectSubscriber.php b/src/EventSubscriber/RedirectSubscriber.php index b5b1dae..46b5574 100644 --- a/src/EventSubscriber/RedirectSubscriber.php +++ b/src/EventSubscriber/RedirectSubscriber.php @@ -15,8 +15,14 @@ use Symfony\Component\HttpFoundation\RedirectResponse; /** * Subscribes to kernel request events to redirect users based on permissions. * - * If a user has the 'access protected domain' permission and is not already - * accessing the site via the protected domain, they will be redirected there. + * - Authenticated users with 'access protected domain' permission are + * redirected to the protected domain when accessing admin paths. + * - Anonymous users logging in from the public domain log in normally, but if + * they gain access permission, they're redirected to the protected domain to + * log in again. + * - A message is shown on the protected domain when redirected, and query + * strings are cleaned. + * - A logout finalizer allows users to fully log out across both domains. */ class RedirectSubscriber implements EventSubscriberInterface { @@ -37,19 +43,19 @@ class RedirectSubscriber implements EventSubscriberInterface { /** * The Messenger service. * - * @var \Drupal\Core\Messenger\ + * @var \Drupal\Core\Messenger\MessengerInterface */ protected $messenger; /** - * Constructs a new RedirectSubscriber. + * Constructs a new RedirectSubscriber object. * * @param \Drupal\Core\Session\AccountProxyInterface $current_user * The current user. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory - * The config factory. - * @param Drupal\Core\Messenger\MessengerInterface $messenger - * The messenger. + * The configuration factory. + * @param \Drupal\Core\Messenger\MessengerInterface $messenger + * The messenger service. */ public function __construct(AccountProxyInterface $current_user, ConfigFactoryInterface $config_factory, MessengerInterface $messenger) { $this->currentUser = $current_user; @@ -58,41 +64,44 @@ class RedirectSubscriber implements EventSubscriberInterface { } /** - * {@inheritdoc} + * Handles kernel request events. + * + * @param \Symfony\Component\HttpKernel\Event\RequestEvent $event + * The event to process. */ public function onRequest(RequestEvent $event) { $request = $event->getRequest(); $host = $request->getHost(); $uri = $request->getRequestUri(); $config = $this->configFactory->get('url_permission_redirect.settings'); - $protectedDomain = $config->get('protected_domain') ?? FALSE; + $protectedDomain = $this->sanitizeDomain($config->get('protected_domain') ?? ''); - // Redirect logged-in users with access permission to protected domain. + // Redirect logged-in users with access permission to protected domain for admin routes only. if ($protectedDomain && $this->currentUser->isAuthenticated() && $this->currentUser->hasPermission('access protected domain')) { - if ($host !== $protectedDomain) { + if ($host !== $protectedDomain && str_starts_with($uri, '/admin')) { $redirect_url = 'https://' . $protectedDomain . $uri; $event->setResponse(new TrustedRedirectResponse($redirect_url, 302)); return; } } - // Redirect anonymous users attempting to log in from public domain. - if ($host !== $protectedDomain && $uri === '/user/login' && $this->currentUser->isAnonymous()) { - $destination = $request->query->get('destination'); - $redirect_url = 'https://' . $protectedDomain . '/user?redirect_message=1'; - - if ($destination) { - $redirect_url .= '&destination=' . urlencode($destination); - } + // Let anonymous users log in normally. + if ($uri === '/user/login' && $this->currentUser->isAnonymous()) { + return; + } + // Redirect users who now have permission and are still on public site. + if ($host !== $protectedDomain && $uri === '/user' && $this->currentUser->isAuthenticated() && + $this->currentUser->hasPermission('access protected domain')) { + $redirect_url = 'https://' . $protectedDomain . '/user?redirect_message=1'; $event->setResponse(new TrustedRedirectResponse($redirect_url, 302)); return; } // Show redirect message on target domain if query parameter is present. if ($host === $protectedDomain && $request->query->get('redirect_message') === '1') { - $this->messenger->addStatus('You were redirected here to log in securely.'); + $this->messenger->addStatus('You were redirected here to log in securely. If already logged in, no action is needed.'); // Clean the query string by removing redirect_message and reloading. $query = $request->query->all(); @@ -102,6 +111,28 @@ class RedirectSubscriber implements EventSubscriberInterface { $clean_url = Url::fromUri('internal:' . $current_path, ['query' => $query])->toString(); $event->setResponse(new RedirectResponse($clean_url, 302)); } + + // Final logout handling: if coming from the protected domain, force logout here too. + if ($uri === '/user/logout' && $request->query->get('final') === '1') { + if ($this->currentUser->isAuthenticated()) { + user_logout(); + } + $url = Url::fromRoute('user.login')->toString(); + $event->setResponse(new RedirectResponse($url)); + } + } + + /** + * Strips protocol and trailing slashes from a domain. + * + * @param string $domain + * The domain string to sanitize. + * + * @return string + * The sanitized domain. + */ + protected function sanitizeDomain(string $domain): string { + return preg_replace('#^https?://#', '', rtrim($domain, '/')); } /** diff --git a/url_permission_redirect.services.yml b/url_permission_redirect.services.yml index 6ce3da7..711ff52 100644 --- a/url_permission_redirect.services.yml +++ b/url_permission_redirect.services.yml @@ -1,6 +1,6 @@ services: url_permission_redirect.event_subscriber: class: Drupal\url_permission_redirect\EventSubscriber\RedirectSubscriber - arguments: ['@current_user', '@config.factory, '@messenger'] + arguments: ['@current_user', '@config.factory', '@messenger'] tags: - { name: event_subscriber }