authorize.php 6.71 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 32 33
// Change the directory to the Drupal root.
chdir('..');

34
$autoloader = require_once 'autoload.php';
35

36
/**
37 38 39
 * Global flag to identify update.php and authorize.php runs.
 *
 * Identifies update.php and authorize.php runs, avoiding unwanted operations
40 41
 * 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).
42
 */
43
const MAINTENANCE_MODE = 'update';
44 45

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

65 66 67 68 69 70 71 72 73 74
try {
  $request = Request::createFromGlobals();
  $kernel = DrupalKernel::createFromRequest($request, $autoloader, 'prod');
  $kernel->prepareLegacyRequest($request);
}
catch (HttpExceptionInterface $e) {
  $response = new Response('', $e->getStatusCode());
  $response->prepare($request)->send();
  exit;
}
75 76

// We have to enable the user and system modules, even to check access and
77
// display errors via the maintenance theme.
78 79
\Drupal::moduleHandler()->addModule('system', 'core/modules/system');
\Drupal::moduleHandler()->addModule('user', 'core/modules/user');
80 81
\Drupal::moduleHandler()->load('system');
\Drupal::moduleHandler()->load('user');
82 83 84 85

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

86
$content = [];
87 88
$show_messages = TRUE;

89 90 91 92
$is_allowed = authorize_access_allowed($request);

// Build content.
if ($is_allowed) {
93
  // Load both the Form API and Batch API.
94 95
  require_once __DIR__ . '/includes/form.inc';
  require_once __DIR__ . '/includes/batch.inc';
96

97 98
  if (isset($_SESSION['authorize_page_title'])) {
    $page_title = $_SESSION['authorize_page_title'];
99 100
  }
  else {
101
    $page_title = t('Authorize file system changes');
102 103 104 105 106 107 108 109
  }

  // 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']);
110
    unset($_SESSION['authorize_filetransfer_info']);
111 112

    if (!empty($results['page_title'])) {
113
      $page_title = $results['page_title'];
114 115
    }
    if (!empty($results['page_message'])) {
116
      \Drupal::messenger()->addMessage($results['page_message']['message'], $results['page_message']['type']);
117 118
    }

119
    $content['authorize_report'] = [
120 121
      '#theme' => 'authorize_report',
      '#messages' => $results['messages'],
122
    ];
123

124
    if (is_array($results['tasks'])) {
125
      $links = $results['tasks'];
126
    }
127
    else {
128
      // Since this is being called outside of the primary front controller,
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
      // 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'),
        ],
      ];
149
    }
150

151
    $content['next_steps'] = [
152 153 154
      '#theme' => 'item_list',
      '#items' => $links,
      '#title' => t('Next steps'),
155
    ];
156 157
  }
  // If a batch is running, let it run.
158
  elseif ($request->query->has('batch')) {
159 160 161 162 163 164 165
    $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;
    }
166 167
  }
  else {
168
    if (empty($_SESSION['authorize_operation']) || empty($_SESSION['authorize_filetransfer_info'])) {
169
      $content = ['#markup' => t('It appears you have reached this page in error.')];
170 171 172
    }
    elseif (!$batch = batch_get()) {
      // We have a batch to process, show the filetransfer form.
173 174 175 176 177 178 179
      try {
        $content = \Drupal::formBuilder()->getForm('Drupal\Core\FileTransfer\Form\FileTransferAuthorizeForm');
      }
      catch (EnforcedResponseException $e) {
        $e->getResponse()->send();
        exit;
      }
180 181 182 183 184 185
    }
  }
  // We defer the display of messages until all operations are done.
  $show_messages = !(($batch = batch_get()) && isset($batch['running']));
}
else {
186
  \Drupal::logger('access denied')->warning('authorize.php');
187
  $page_title = t('Access denied');
188
  $content = ['#markup' => t('You are not allowed to access this page.')];
189 190
}

191
$bare_html_page_renderer = \Drupal::service('bare_html_page_renderer');
192
$response = $bare_html_page_renderer->renderBarePage($content, $page_title, 'maintenance_page', [
193
  '#show_messages' => $show_messages,
194
]);
195 196
if (!$is_allowed) {
  $response->setStatusCode(403);
197
}
198
$response->send();