UpdateKernel.php 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. <?php
  2. namespace Drupal\Core\Update;
  3. use Drupal\Core\DrupalKernel;
  4. use Drupal\Core\Session\AnonymousUserSession;
  5. use Drupal\Core\Site\Settings;
  6. use Symfony\Cmf\Component\Routing\RouteObjectInterface;
  7. use Symfony\Component\HttpFoundation\ParameterBag;
  8. use Symfony\Component\HttpFoundation\Request;
  9. use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
  10. /**
  11. * Defines a kernel which is used primarily to run the update of Drupal.
  12. *
  13. * We use a dedicated kernel + front controller (update.php) in order to be able
  14. * to repair Drupal if it is in a broken state.
  15. *
  16. * @see update.php
  17. * @see \Drupal\system\Controller\DbUpdateController
  18. */
  19. class UpdateKernel extends DrupalKernel {
  20. /**
  21. * {@inheritdoc}
  22. */
  23. public function discoverServiceProviders() {
  24. parent::discoverServiceProviders();
  25. $this->serviceProviderClasses['app']['update_kernel'] = 'Drupal\Core\Update\UpdateServiceProvider';
  26. }
  27. /**
  28. * {@inheritdoc}
  29. */
  30. protected function initializeContainer() {
  31. // Always force a container rebuild, in order to be able to override some
  32. // services, see \Drupal\Core\Update\UpdateServiceProvider.
  33. $this->containerNeedsRebuild = TRUE;
  34. $container = parent::initializeContainer();
  35. return $container;
  36. }
  37. /**
  38. * {@inheritdoc}
  39. */
  40. protected function cacheDrupalContainer(array $container_definition) {
  41. // Don't save this particular container to cache, so it does not leak into
  42. // the main site at all.
  43. return FALSE;
  44. }
  45. /**
  46. * {@inheritdoc}
  47. */
  48. public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) {
  49. try {
  50. static::bootEnvironment();
  51. // First boot up basic things, like loading the include files.
  52. $this->initializeSettings($request);
  53. $this->boot();
  54. $container = $this->getContainer();
  55. /** @var \Symfony\Component\HttpFoundation\RequestStack $request_stack */
  56. $request_stack = $container->get('request_stack');
  57. $request_stack->push($request);
  58. $this->preHandle($request);
  59. // Handle the actual request. We need the session both for authentication
  60. // as well as the DB update, like
  61. // \Drupal\system\Controller\DbUpdateController::batchFinished.
  62. $this->bootSession($request, $type);
  63. $result = $this->handleRaw($request);
  64. $this->shutdownSession($request);
  65. return $result;
  66. }
  67. catch (\Exception $e) {
  68. return $this->handleException($e, $request, $type);
  69. }
  70. }
  71. /**
  72. * Generates the actual result of update.php.
  73. *
  74. * The actual logic of the update is done in the db update controller.
  75. *
  76. * @param \Symfony\Component\HttpFoundation\Request $request
  77. * The incoming request.
  78. *
  79. * @return \Symfony\Component\HttpFoundation\Response
  80. * A response object.
  81. *
  82. * @see \Drupal\system\Controller\DbUpdateController
  83. */
  84. protected function handleRaw(Request $request) {
  85. $container = $this->getContainer();
  86. $this->handleAccess($request, $container);
  87. /** @var \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver */
  88. $controller_resolver = $container->get('controller_resolver');
  89. /** @var callable $db_update_controller */
  90. $db_update_controller = $controller_resolver->getControllerFromDefinition('\Drupal\system\Controller\DbUpdateController::handle');
  91. $this->setupRequestMatch($request);
  92. /** @var \Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface $argument_resolver */
  93. $argument_resolver = $container->get('http_kernel.controller.argument_resolver');
  94. $arguments = $argument_resolver->getArguments($request, $db_update_controller);
  95. return call_user_func_array($db_update_controller, $arguments);
  96. }
  97. /**
  98. * Boots up the session.
  99. *
  100. * This method + shutdownSession() basically simulates what
  101. * \Drupal\Core\StackMiddleware\Session does.
  102. *
  103. * @param \Symfony\Component\HttpFoundation\Request $request
  104. * The incoming request.
  105. */
  106. protected function bootSession(Request $request) {
  107. $container = $this->getContainer();
  108. /** @var \Symfony\Component\HttpFoundation\Session\SessionInterface $session */
  109. $session = $container->get('session');
  110. $session->start();
  111. $request->setSession($session);
  112. }
  113. /**
  114. * Ensures that the session is saved.
  115. *
  116. * @param \Symfony\Component\HttpFoundation\Request $request
  117. * The incoming request.
  118. */
  119. protected function shutdownSession(Request $request) {
  120. if ($request->hasSession()) {
  121. $request->getSession()->save();
  122. }
  123. }
  124. /**
  125. * Set up the request with fake routing data for update.php.
  126. *
  127. * This fake routing data is needed in order to make batch API work properly.
  128. *
  129. * @param \Symfony\Component\HttpFoundation\Request $request
  130. * The incoming request.
  131. */
  132. protected function setupRequestMatch(Request $request) {
  133. $path = $request->getPathInfo();
  134. $args = explode('/', ltrim($path, '/'));
  135. $request->attributes->set(RouteObjectInterface::ROUTE_NAME, 'system.db_update');
  136. $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, $this->getContainer()->get('router.route_provider')->getRouteByName('system.db_update'));
  137. $op = $args[0] ?: 'info';
  138. $request->attributes->set('op', $op);
  139. $request->attributes->set('_raw_variables', new ParameterBag(['op' => $op]));
  140. }
  141. /**
  142. * Checks if the current user has rights to access updates page.
  143. *
  144. * If the current user does not have the rights, an exception is thrown.
  145. *
  146. * @param \Symfony\Component\HttpFoundation\Request $request
  147. * The incoming request.
  148. *
  149. * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
  150. * Thrown when update.php should not be accessible.
  151. */
  152. protected function handleAccess(Request $request) {
  153. /** @var \Drupal\Core\Authentication\AuthenticationManager $authentication_manager */
  154. $authentication_manager = $this->getContainer()->get('authentication');
  155. $account = $authentication_manager->authenticate($request) ?: new AnonymousUserSession();
  156. /** @var \Drupal\Core\Session\AccountProxyInterface $current_user */
  157. $current_user = $this->getContainer()->get('current_user');
  158. $current_user->setAccount($account);
  159. /** @var \Drupal\system\Access\DbUpdateAccessCheck $db_update_access */
  160. $db_update_access = $this->getContainer()->get('access_check.db_update');
  161. if (!Settings::get('update_free_access', FALSE) && !$db_update_access->access($account)->isAllowed()) {
  162. throw new AccessDeniedHttpException('In order to run update.php you need to either have "Administer software updates" permission or have set $settings[\'update_free_access\'] in your settings.php.');
  163. }
  164. }
  165. }