Skip to content

Instantly share code, notes, and snippets.

@clrockwell
Last active June 9, 2017 01:54
Show Gist options
  • Select an option

  • Save clrockwell/b17a83d61972ee403c1dccaf68af1ea5 to your computer and use it in GitHub Desktop.

Select an option

Save clrockwell/b17a83d61972ee403c1dccaf68af1ea5 to your computer and use it in GitHub Desktop.
<?php
namespace Drupal\require_login\EventSubscriber;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Path\PathValidator;
use Drupal\Core\Routing\CurrentRouteMatch;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Drupal\Component\Utility\Xss;
/**
* Subscribe to kernal request event to check authentication.
*/
class RequireLoginSubscriber implements EventSubscriberInterface {
protected $configFactory;
protected $lang;
protected $path;
protected $aliased_path;
protected $config;
protected $auth_path;
protected $pathValidator;
protected $account;
protected $routeMatch;
public function __construct(
ConfigFactory $configFactory,
PathValidator $pathValidator,
AccountInterface $account,
CurrentRouteMatch $routeMatch
) {
$this->routeMatch = $routeMatch;
$this->configFactory = $configFactory;
$this->pathValidator = $pathValidator;
$this->account = $account;
$this->lang = \Drupal::languageManager()->getCurrentLanguage()->getId();
$this->path = \Drupal::service('path.current')->getPath();
$this->aliased_path = trim(\Drupal::service('path.alias_manager')->getAliasByPath($this->path, $this->lang));
$this->auth_path = Xss::filterAdmin($this->configFactory->get('require_login.config')->get('auth_path'));
}
/**
* Checks whether redirect should be handled by KernelEvents::REQUEST subscriber
*
* @return bool
* Returns TRUE if user is authenticated and FALSE otherwise.
*/
public function pathIsExcluded() {
// This is not a path the user has access to, let the Exception Subscriber deal with it.
if (!$this->pathValidator->isValid($this->path)) {
return TRUE;
}
if ($this->isDefaultExcluded()) {
return TRUE;
}
if ($this->isAdminExcluded()) {
return TRUE;
}
return FALSE;
}
/**
* Check current path against admin configurable exclusion paths, including
* a custom login path, if defined.
*
* @return bool TRUE if page is excluded, FALSE if not
*/
public function isAdminExcluded() {
$page_settings = $this->configFactory->get('system.site')->get('page');
$exclude_paths = explode(PHP_EOL, $this->configFactory->get('require_login.config')->get('excluded_paths'));
// If alternate login path is defined, add to exclude.
if ($this->auth_path) {
$exclude_paths[] = $this->auth_path;
}
foreach ($exclude_paths as $key => $exclude_path) {
if ($exclude_path == '<front>') {
$front_path = \Drupal::service('path.alias_manager')->getAliasByPath($page_settings['front'], $this->lang);
$exclude_paths[$key] = '/' . trim($front_path, '/ ');
}
}
if (\Drupal::service('path.matcher')->matchPath($this->aliased_path, implode(PHP_EOL, $exclude_paths))) {
return TRUE;
}
return FALSE;
}
/**
* Check current route against some system defined routes.
*
* @return bool TRUE if page is excluded, FALSE if not
*/
public function isDefaultExcluded() {
// Various checks to determine exceptions for current page. Returns TRUE
// when at least one check has evaluated as TRUE.
$checks[] = function_exists('drupal_is_cli') && drupal_is_cli();
$route_name = $this->routeMatch->getRouteName();
if (isset($route_name)) {
$route_checks = [
($route_name == 'system.cron'),
// Update.
($route_name == 'system.db_update'),
// Timezone.
($route_name == 'system.timezone'),
// User Pages.
($route_name == 'user.login' || $route_name == 'user.register' || $route_name == 'user.pass'),
// API
($route_name == 'user.login.http'),
// Twilio
($route_name == 'realty_beacon_twilio.twilio_incoming_sms_receiveSms'),
// Twilio Voice
($route_name == 'realty_beacon_twilio.twilio_incoming_voice_controller_receiveVoiceCall'),
];
$checks = array_merge($checks, $route_checks);
}
foreach ($checks as $check) {
if ($check) {
return TRUE;
}
}
return FALSE;
}
/**
* Get a user defined login path, or the system route.
*
* @return string path to a login page
*/
public function getAuthenticationPath() {
$path = $this->auth_path ? $this->auth_path : Url::fromRoute('user.login')->toString();
return $path . '?destination=' . $this->path;
}
/**
* If a user is not logged in and hits a 404 or 403, redirect them to
* the login page.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
*/
public function handleRequestException(GetResponseEvent $event) {
// For now, only deal with Not Found and Access Denied
$exception = $event->getException();
if (!($exception instanceof AccessDeniedHttpException) && !($exception instanceof NotFoundHttpException)) {
return;
}
$format = $event->getRequest()->get('_format');
if ($format == 'json') {
// API Request
$access_denied = Json::encode([
'error' => 'access denied',
'error_code' => 403,
'message' => 'Access denied for authentication method, please revalidate your user',
]);
throw new AccessDeniedHttpException($access_denied);
}
$this->performRedirect($event);
return;
}
/**
* Set a message and redirect to login page.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
*/
public function performRedirect(GetResponseEvent $event) {
$message = Xss::filterAdmin($this->configFactory->get('require_login.config')->get('deny_message'));
if (!empty($message)) {
drupal_set_message($message, 'warning');
}
// Prepare authentication redirect path.
$redirect = $this->getAuthenticationPath();
$event->setResponse((new RedirectResponse($redirect))->send());
}
/**
* Passes off GetResponseEvent to appropriate method only after ensuring
* user is not already logged in.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* @return bool
*/
public function requireLoginDelegateEventDuty(GetResponseEvent $event) {
// If the account isn't anonymous there is nothing else to do here.
if (!$this->account->isAnonymous()) {
return TRUE;
}
if ($event instanceof GetResponseForExceptionEvent) {
$this->handleRequestException($event);
}
// If these are pages that should be ignored there is nothing else to do here.
if ($this->pathIsExcluded()) {
return TRUE;
}
if ($event instanceof GetResponseEvent) {
$this->performRedirect($event);
}
return TRUE;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[KernelEvents::REQUEST][] = ['requireLoginDelegateEventDuty'];
$events[KernelEvents::EXCEPTION][] = ['requireLoginDelegateEventDuty'];
return $events;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment