We are migrating from Drupal 7 to Drupal 8 and are currently at the last hurdle - relying on Drupal's session to authenticate for custom scripts.

Previously we used to rely on drupal_bootstrap but now I have implemented a similar Drupal 8 aware version for bootstrapping and checking the user is authenticated (and which roles they have etc) - this is broadly based on core/authorize.php.

However - this only works in the drupal root directory or the core/ directory - in every other directory this fails to authenticate the user.

I believe this is down to the routing, and authentication only working for pre-defined routes.

Specifically I want to be able to authenticate users in any script in a folder (recursively). EG

  • external_scripts/script1.php
  • external_scripts/script1.php
  • external_scripts/other_scripts/script3.php

How do I add a route to allow this? Or can I just bypass this route checking when using the cookie? Can I spoof the route?

<?php

/**
 * @file
 * Locates the Drupal root directory and bootstraps the kernel.
 */

use Drupal\Core\DrupalKernel;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\Routing\Route;

function _find_autoloader($dir) {
  $autoloadFile = $dir . '/vendor/autoload.php';
  if (file_exists($autoloadFile)) {
    return include_once($autoloadFile);
  }
  elseif (empty($dir) || $dir === DIRECTORY_SEPARATOR) {
    return FALSE;
  }
  return _find_autoloader(dirname($dir));
}

// Immediately return if classes are discoverable (already booted).
if (class_exists('\Drupal\Core\DrupalKernel') && class_exists('\Drupal')) {
  return \Drupal::service('kernel');
}

$autoloader = _find_autoloader(empty($_SERVER['PWD']) ? getcwd() : $_SERVER['PWD']); // defined in start.inc
if (!$autoloader || !class_exists('\Drupal\Core\DrupalKernel')) {
  print "This script must be invoked inside a Drupal 8 environment. Unable to continue.\n";
  exit();
}

const MAINTENANCE_MODE = 'update';

try {
  $request = Request::createFromGlobals();
  $kernel = DrupalKernel::createFromRequest($request, $autoloader, 'prod');
  $kernel->boot();
  $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route('<none>'));
  $request->attributes->set(RouteObjectInterface::ROUTE_NAME, '<none>');
  $kernel->preHandle($request);
  if (PHP_SAPI !== 'cli') {
    $request->setSession($kernel->getContainer()->get('session'));
  }
}
catch (HttpExceptionInterface $e) {
  $response = new Response('', $e->getStatusCode());
  $response->prepare($request)->send();
  exit;
}

\Drupal::moduleHandler()->addModule('system', 'core/modules/system');
\Drupal::moduleHandler()->addModule('user', 'core/modules/user');
\Drupal::moduleHandler()->load('system');
\Drupal::moduleHandler()->load('user');

$content = [];
$show_messages = TRUE;

$account = \Drupal::service('authentication')->authenticate($request);

if ($account) {
  \Drupal::currentUser()->setAccount($account);
}

var_dump(\Drupal::currentUser());
有帮助吗?

解决方案

I tried many different ways of loading the user, using the session as the starting point. In every singe scenario it fell down.

So in the end I used as much of the Drupal scaffold as I could, and copied the relevant bits from the relevant classes and functions in order to control the loading of a user record in an ajax handler that exists outside of Drupal's page space.

It uses the cookie to load the session, retrieve the user id, details and roles and create an account object before setting the bootstrapped Drupal kernel current user to the account object.

We deal with access control at a higher level (relying on \Drupal::currentUser() and leveraging $user->isAuthenticated() for example) so this script gets up to a consistent point in both contexts (inside Drupal, and non-drupal pages inside the domain) for user management etc.

<?php

/**
 * @file
 * Bootstraps the Drupal kernel and loads the user for the incoming request via cookie
 */

use Drupal\Core\DrupalKernel;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\UserSession;
use Symfony\Component\HttpFoundation\Request;

function _find_autoloader($dir) {
  $autoloadFile = $dir . '/vendor/autoload.php';
  if (file_exists($autoloadFile)) {
    return include_once($autoloadFile);
  }
  elseif (empty($dir) || $dir === DIRECTORY_SEPARATOR) {
    return FALSE;
  }
  return _find_autoloader(dirname($dir));
}

// Immediately return if classes are discoverable (already booted).
if (class_exists('\Drupal\Core\DrupalKernel') && class_exists('\Drupal')) {
  return \Drupal::service('kernel');
}

// Load autoload
$autoloader = _find_autoloader(empty($_SERVER['PWD']) ? getcwd() : $_SERVER['PWD']); // defined in start.inc
if (!$autoloader || !class_exists('\Drupal\Core\DrupalKernel')) {
  print "This script must be invoked inside a Drupal 8 environment. Unable to continue.\n";
  exit();
}

const MAINTENANCE_MODE = 'update'; // Don't bother theming

if (PHP_SAPI == 'cli') { // CLI won't have a cookie, stop now
  return;
}

$request = Request::createFromGlobals();
$kernel = DrupalKernel::createFromRequest($request, $autoloader, 'prod');
$kernel->boot();
$connection = $kernel->getContainer()->get('database');

// Get session id from session cookie
$sid = reset($_COOKIE);

// Get uid from sessions, get user from uid
$query = $connection->queryRange('SELECT uid FROM {sessions} WHERE sid = :sid', 0, 1, [':sid' => Crypt::hashBase64($sid)]);
$uid = (string)$query->fetchField();
$values = $connection->query('SELECT * FROM {users_field_data} u WHERE u.uid = :uid AND u.default_langcode = 1', [':uid' => $uid])->fetchAssoc();

// Get roles from uid, and set account
if (!empty($values) && $values['status'] == 1) {
  $rids = $connection->query('SELECT roles_target_id FROM {user__roles} WHERE entity_id = :uid', [':uid' => $values['uid']])->fetchCol();
  $values['roles'] = array_merge([AccountInterface::AUTHENTICATED_ROLE], $rids);
  $account = new UserSession($values);
  \Drupal::currentUser()->setAccount($account);
}
许可以下: CC-BY-SA归因
scroll top