authorize.php 7.16 KB
Newer Older
1 2 3 4
<?php

/**
 * @file
5 6
 * Administrative script for running authorized file operations.
 *
7 8 9 10 11 12 13 14
 * Using this script, the site owner (the user actually owning the files on the
 * webserver) can authorize certain file-related operations to proceed with
 * elevated privileges, for example to deploy and upgrade modules or themes.
 * Users should not visit this page directly, but instead use an administrative
 * user interface which knows how to redirect the user to this script as part of
 * a multistep process. This script actually performs the selected operations
 * without loading all of Drupal, to be able to more gracefully recover from
 * errors. Access to the script is controlled by a global killswitch in
15
 * settings.php ('allow_authorize_operations') and via the 'administer software
16
 * updates' permission.
17
 *
18 19 20
 * There are helper functions for setting up an operation to run via this
 * system in modules/system/system.module. For more information, see:
 * @link authorize Authorized operation helper functions @endlink
21 22
 */

23
use Drupal\Core\DrupalKernel;
24
use Drupal\Core\Form\EnforcedResponseException;
25
use Drupal\Core\Url;
26
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
27
use Symfony\Component\HttpFoundation\Request;
28
use Symfony\Component\HttpFoundation\Response;
29
use Drupal\Core\Site\Settings;
30 31
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\Routing\Route;
32

33 34 35
// Change the directory to the Drupal root.
chdir('..');

36
$autoloader = require_once 'autoload.php';
37

38
/**
39 40 41
 * Global flag to identify update.php and authorize.php runs.
 *
 * Identifies update.php and authorize.php runs, avoiding unwanted operations
42 43
 * such as css/js preprocessing and translation, and solves some theming issues.
 * The flag is checked in other places in Drupal code (not just authorize.php).
44
 */
45
const MAINTENANCE_MODE = 'update';
46 47

/**
48
 * Determines if the current user is allowed to run authorize.php.
49 50 51 52
 *
 * The killswitch in settings.php overrides all else, otherwise, the user must
 * have access to the 'administer software updates' permission.
 *
53
 * @param \Symfony\Component\HttpFoundation\Request $request
54
 *   The incoming request.
55
 *
56
 * @return bool
57
 *   TRUE if the current user can run authorize.php, and FALSE if not.
58
 */
59 60 61 62 63
function authorize_access_allowed(Request $request) {
  $account = \Drupal::service('authentication')->authenticate($request);
  if ($account) {
    \Drupal::currentUser()->setAccount($account);
  }
64
  return Settings::get('allow_authorize_operations', TRUE) && \Drupal::currentUser()->hasPermission('administer software updates');
65 66
}

67 68 69
try {
  $request = Request::createFromGlobals();
  $kernel = DrupalKernel::createFromRequest($request, $autoloader, 'prod');
70 71 72 73 74 75 76 77 78
  $kernel->boot();
  // A route is required for route matching.
  $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route('<none>'));
  $request->attributes->set(RouteObjectInterface::ROUTE_NAME, '<none>');
  $kernel->preHandle($request);
  // Ensure our request includes the session if appropriate.
  if (PHP_SAPI !== 'cli') {
    $request->setSession($kernel->getContainer()->get('session'));
  }
79 80 81 82 83 84
}
catch (HttpExceptionInterface $e) {
  $response = new Response('', $e->getStatusCode());
  $response->prepare($request)->send();
  exit;
}
85 86

// We have to enable the user and system modules, even to check access and
87
// display errors via the maintenance theme.
88 89
\Drupal::moduleHandler()->addModule('system', 'core/modules/system');
\Drupal::moduleHandler()->addModule('user', 'core/modules/user');
90 91
\Drupal::moduleHandler()->load('system');
\Drupal::moduleHandler()->load('user');
92 93 94 95

// Initialize the maintenance theme for this administrative script.
drupal_maintenance_theme();

96
$content = [];
97 98
$show_messages = TRUE;

99 100 101 102
$is_allowed = authorize_access_allowed($request);

// Build content.
if ($is_allowed) {
103
  // Load both the Form API and Batch API.
104 105
  require_once __DIR__ . '/includes/form.inc';
  require_once __DIR__ . '/includes/batch.inc';
106

107 108
  if (isset($_SESSION['authorize_page_title'])) {
    $page_title = $_SESSION['authorize_page_title'];
109 110
  }
  else {
111
    $page_title = t('Authorize file system changes');
112 113 114 115 116 117 118 119
  }

  // See if we've run the operation and need to display a report.
  if (isset($_SESSION['authorize_results']) && $results = $_SESSION['authorize_results']) {

    // Clear the session out.
    unset($_SESSION['authorize_results']);
    unset($_SESSION['authorize_operation']);
120
    unset($_SESSION['authorize_filetransfer_info']);
121 122

    if (!empty($results['page_title'])) {
123
      $page_title = $results['page_title'];
124 125
    }
    if (!empty($results['page_message'])) {
126
      \Drupal::messenger()->addMessage($results['page_message']['message'], $results['page_message']['type']);
127 128
    }

129
    $content['authorize_report'] = [
130 131
      '#theme' => 'authorize_report',
      '#messages' => $results['messages'],
132
    ];
133

134
    if (is_array($results['tasks'])) {
135
      $links = $results['tasks'];
136
    }
137
    else {
138
      // Since this is being called outside of the primary front controller,
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
      // the base_url needs to be set explicitly to ensure that links are
      // relative to the site root.
      // @todo Simplify with https://www.drupal.org/node/2548095
      $default_options = [
        '#type' => 'link',
        '#options' => [
          'absolute' => TRUE,
          'base_url' => $GLOBALS['base_url'],
        ],
      ];
      $links = [
        $default_options + [
          '#url' => Url::fromRoute('system.admin'),
          '#title' => t('Administration pages'),
        ],
        $default_options + [
          '#url' => Url::fromRoute('<front>'),
          '#title' => t('Front page'),
        ],
      ];
159
    }
160

161
    $content['next_steps'] = [
162 163 164
      '#theme' => 'item_list',
      '#items' => $links,
      '#title' => t('Next steps'),
165
    ];
166 167
  }
  // If a batch is running, let it run.
168
  elseif ($request->query->has('batch')) {
169 170 171 172 173 174 175
    $content = _batch_page($request);
    // If _batch_page() returns a response object (likely a JsonResponse for
    // JavaScript-based batch processing), send it immediately.
    if ($content instanceof Response) {
      $content->send();
      exit;
    }
176 177
  }
  else {
178
    if (empty($_SESSION['authorize_operation']) || empty($_SESSION['authorize_filetransfer_info'])) {
179
      $content = ['#markup' => t('It appears you have reached this page in error.')];
180 181 182
    }
    elseif (!$batch = batch_get()) {
      // We have a batch to process, show the filetransfer form.
183 184 185 186 187 188 189
      try {
        $content = \Drupal::formBuilder()->getForm('Drupal\Core\FileTransfer\Form\FileTransferAuthorizeForm');
      }
      catch (EnforcedResponseException $e) {
        $e->getResponse()->send();
        exit;
      }
190 191 192 193 194 195
    }
  }
  // We defer the display of messages until all operations are done.
  $show_messages = !(($batch = batch_get()) && isset($batch['running']));
}
else {
196
  \Drupal::logger('access denied')->warning('authorize.php');
197
  $page_title = t('Access denied');
198
  $content = ['#markup' => t('You are not allowed to access this page.')];
199 200
}

201
$bare_html_page_renderer = \Drupal::service('bare_html_page_renderer');
202
$response = $bare_html_page_renderer->renderBarePage($content, $page_title, 'maintenance_page', [
203
  '#show_messages' => $show_messages,
204
]);
205 206
if (!$is_allowed) {
  $response->setStatusCode(403);
207
}
208
$response->send();