Skip to content
Snippets Groups Projects
Commit 9b3e441c authored by Jess's avatar Jess
Browse files

SA-CORE-2019-003 by samuel.mortenson, Berdir, pwolanin, dawehner,...

SA-CORE-2019-003 by samuel.mortenson, Berdir, pwolanin, dawehner, cashwilliams, Wim Leers, xjm, larowlan, alexpott, plach, damiankloip, tstoeckler, tedbow, DamienMcKenna, effulgentsia, RobLoach, gabesullice, drumm, heshanlk, dsnopek, fago, miro_dietiker, truls1502
parent 74e8c205
No related branches found
No related tags found
No related merge requests found
Showing
with 131 additions and 41 deletions
......@@ -16,6 +16,10 @@
* Disables any extensions that are incompatible with the current core version.
*/
function update_fix_compatibility() {
// Fix extension objects if the update is being done via Drush 8. In non-Drush
// environments this will already be fixed by the UpdateKernel this point.
UpdateKernel::fixSerializedExtensionObjects(\Drupal::getContainer());
$extension_config = \Drupal::configFactory()->getEditable('core.extension');
$save = FALSE;
foreach (['module', 'theme'] as $type) {
......@@ -30,10 +34,6 @@ function update_fix_compatibility() {
$extension_config->set('module', module_config_sort($extension_config->get('module')));
$extension_config->save();
}
// Fix extension objects if the update is being done via Drush 8. In non-Drush
// environments this will already be fixed by the UpdateKernel this point.
UpdateKernel::fixSerializedExtensionObjects(\Drupal::getContainer());
}
/**
......
......@@ -89,12 +89,15 @@ public function applies(Route $route) {
public function access(Request $request, AccountInterface $account) {
$method = $request->getMethod();
// Read-only operations are always allowed.
if (in_array($method, ['GET', 'HEAD', 'OPTIONS', 'TRACE'], TRUE)) {
return AccessResult::allowed();
}
// This check only applies if
// 1. this is a write operation
// 2. the user was successfully authenticated and
// 3. the request comes with a session cookie.
if (!in_array($method, ['GET', 'HEAD', 'OPTIONS', 'TRACE'])
&& $account->isAuthenticated()
// 1. the user was successfully authenticated and
// 2. the request comes with a session cookie.
if ($account->isAuthenticated()
&& $this->sessionConfiguration->hasSession($request)
) {
if (!$request->headers->has('X-CSRF-Token')) {
......
......@@ -78,13 +78,15 @@ public function access(Route $route, RouteMatchInterface $route_match, AccountIn
if ($entity_type->getBundleEntityType()) {
$access->addCacheTags($this->entityTypeManager->getDefinition($entity_type->getBundleEntityType())->getListCacheTags());
// Check if the user is allowed to create new bundles. If so, allow
// access, so the add page can show a link to create one.
// @see \Drupal\Core\Entity\Controller\EntityController::addPage()
$bundle_access_control_handler = $this->entityTypeManager->getAccessControlHandler($entity_type->getBundleEntityType());
$access = $access->orIf($bundle_access_control_handler->createAccess(NULL, $account, [], TRUE));
if ($access->isAllowed()) {
return $access;
if (empty($route->getOption('_ignore_create_bundle_access'))) {
// Check if the user is allowed to create new bundles. If so, allow
// access, so the add page can show a link to create one.
// @see \Drupal\Core\Entity\Controller\EntityController::addPage()
$bundle_access_control_handler = $this->entityTypeManager->getAccessControlHandler($entity_type->getBundleEntityType());
$access = $access->orIf($bundle_access_control_handler->createAccess(NULL, $account, [], TRUE));
if ($access->isAllowed()) {
return $access;
}
}
}
......
......@@ -124,6 +124,21 @@ public function onExceptionSendChallenge(GetResponseForExceptionEvent $event) {
}
}
/**
* Detect disallowed authentication methods on access denied exceptions.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
*/
public function _onExceptionAccessDenied(GetResponseForExceptionEvent $event) {
if (isset($this->filter) && $event->isMasterRequest()) {
$request = $event->getRequest();
$exception = $event->getException();
if ($exception instanceof AccessDeniedHttpException && $this->authenticationProvider->applies($request) && !$this->filter->appliesToRoutedRequest($request, TRUE)) {
$event->setException(new AccessDeniedHttpException('The used authentication method is not allowed on this route.', $exception));
}
}
}
/**
* {@inheritdoc}
*/
......@@ -137,6 +152,7 @@ public static function getSubscribedEvents() {
// Access check must be performed after routing.
$events[KernelEvents::REQUEST][] = ['onKernelRequestFilterProvider', 31];
$events[KernelEvents::EXCEPTION][] = ['onExceptionSendChallenge', 75];
$events[KernelEvents::EXCEPTION][] = ['_onExceptionAccessDenied', 80];
return $events;
}
......
......@@ -64,7 +64,12 @@ public function setValue($values, $notify = TRUE) {
$values = $values->getValue();
}
else {
$values = unserialize($values);
if (version_compare(PHP_VERSION, '7.0.0', '>=')) {
$values = unserialize($values, ['allowed_classes' => FALSE]);
}
else {
$values = unserialize($values);
}
}
}
......
......@@ -4,6 +4,8 @@
use Drupal\Core\Access\AccessManagerInterface;
use Drupal\Core\Access\AccessResultReasonInterface;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Http\Exception\CacheableAccessDeniedHttpException;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
......@@ -111,7 +113,12 @@ protected function checkAccess(Request $request) {
$request->attributes->set(AccessAwareRouterInterface::ACCESS_RESULT, $access_result);
}
if (!$access_result->isAllowed()) {
throw new AccessDeniedHttpException($access_result instanceof AccessResultReasonInterface ? $access_result->getReason() : NULL);
if ($access_result instanceof CacheableDependencyInterface && $request->isMethodCacheable()) {
throw new CacheableAccessDeniedHttpException($access_result, $access_result instanceof AccessResultReasonInterface ? $access_result->getReason() : NULL);
}
else {
throw new AccessDeniedHttpException($access_result instanceof AccessResultReasonInterface ? $access_result->getReason() : NULL);
}
}
}
......
......@@ -219,6 +219,9 @@ public static function fixSerializedExtensionObjects(ContainerInterface $contain
// will be PHP warnings. This silently fixes Drupal so that the update can
// continue.
$callable = function () use ($container) {
// Reset static caches in profile list so the module list is rebuilt
// correctly.
$container->get('extension.list.profile')->reset();
foreach ($container->getParameter('cache_bins') as $service_id => $bin) {
$container->get($service_id)->deleteAll();
}
......
......@@ -12,6 +12,7 @@
use Drupal\Core\Http\Exception\CacheableUnauthorizedHttpException;
use Drupal\user\UserAuthInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
/**
* HTTP Basic authentication provider.
......@@ -155,7 +156,9 @@ public function challengeException(Request $request, \Exception $previous) {
$cacheability = CacheableMetadata::createFromObject($site_config)
->addCacheTags(['config:user.role.anonymous'])
->addCacheContexts(['user.roles:anonymous']);
return new CacheableUnauthorizedHttpException($cacheability, (string) $challenge, 'No authentication credentials provided.', $previous);
return $request->isMethodCacheable()
? new CacheableUnauthorizedHttpException($cacheability, (string) $challenge, 'No authentication credentials provided.', $previous)
: new UnauthorizedHttpException((string) $challenge, 'No authentication credentials provided.', $previous);
}
}
......@@ -136,7 +136,10 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter
}
}
else {
$access = AccessResult::forbidden();
$reason = count($conditions) > 1
? "One of the block visibility conditions ('%s') denied access."
: "The block visibility condition '%s' denied access.";
$access = AccessResult::forbidden(sprintf($reason, implode("', '", array_keys($conditions))));
}
$this->mergeCacheabilityFromConditions($access, $conditions);
......
......@@ -3,6 +3,7 @@
namespace Drupal\Tests\block\Functional\Rest;
use Drupal\block\Entity\Block;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
abstract class BlockResourceTestBase extends EntityResourceTestBase {
......@@ -135,7 +136,7 @@ protected function getExpectedUnauthorizedAccessMessage($method) {
switch ($method) {
case 'GET':
return "You are not authorized to view this block entity.";
return "The block visibility condition 'user_role' denied access.";
default:
return parent::getExpectedUnauthorizedAccessMessage($method);
}
......@@ -143,17 +144,25 @@ protected function getExpectedUnauthorizedAccessMessage($method) {
/**
* {@inheritdoc}
*
* @todo Fix this in https://www.drupal.org/node/2820315.
*/
protected function getExpectedUnauthorizedAccessCacheability() {
return (new CacheableMetadata())
->setCacheTags(['4xx-response', 'http_response'])
->setCacheContexts(['user.roles']);
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedEntityAccessCacheability($is_authenticated) {
// @see \Drupal\block\BlockAccessControlHandler::checkAccess()
return parent::getExpectedUnauthorizedAccessCacheability()
->setCacheTags([
'4xx-response',
return parent::getExpectedUnauthorizedEntityAccessCacheability($is_authenticated)
->addCacheTags([
'config:block.block.llama',
'http_response',
static::$auth ? 'user:2' : 'user:0',
])
->setCacheContexts(['user.roles']);
$is_authenticated ? 'user:2' : 'user:0',
]);
}
}
......@@ -180,9 +180,9 @@ protected function getExpectedUnauthorizedAccessMessage($method) {
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessCacheability() {
protected function getExpectedUnauthorizedEntityAccessCacheability($is_authenticated) {
// @see \Drupal\block_content\BlockContentAccessControlHandler()
return parent::getExpectedUnauthorizedAccessCacheability()
return parent::getExpectedUnauthorizedEntityAccessCacheability($is_authenticated)
->addCacheTags(['block_content:1']);
}
......
......@@ -337,8 +337,10 @@ protected function getExpectedUnauthorizedAccessMessage($method) {
return "The 'post comments' permission is required.";
case 'PATCH';
return "The 'edit own comments' permission is required, the user must be the comment author, and the comment must be published.";
default:
return parent::getExpectedUnauthorizedAccessMessage($method);
case 'DELETE':
// \Drupal\comment\CommentAccessControlHandler::checkAccess() does not
// specify a reason for not allowing a comment to be deleted.
return '';
}
}
......@@ -378,9 +380,9 @@ public function testPostSkipCommentApproval() {
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessCacheability() {
protected function getExpectedUnauthorizedEntityAccessCacheability($is_authenticated) {
// @see \Drupal\comment\CommentAccessControlHandler::checkAccess()
return parent::getExpectedUnauthorizedAccessCacheability()
return parent::getExpectedUnauthorizedEntityAccessCacheability($is_authenticated)
->addCacheTags(['comment:1']);
}
......
......@@ -70,4 +70,20 @@ protected function getNormalizedPostEntity() {
// @todo Update in https://www.drupal.org/node/2300677.
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessMessage($method) {
if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) {
return parent::getExpectedUnauthorizedAccessMessage($method);
}
switch ($method) {
case 'GET':
return "The 'view config_test' permission is required.";
default:
return parent::getExpectedUnauthorizedAccessMessage($method);
}
}
}
......@@ -66,7 +66,7 @@ public function testWatchdog() {
$request_options = $this->getAuthenticationRequestOptions('GET');
$response = $this->request('GET', $url, $request_options);
$this->assertResourceErrorResponse(403, "The 'restful get dblog' permission is required.", $response);
$this->assertResourceErrorResponse(403, "The 'restful get dblog' permission is required.", $response, ['4xx-response', 'http_response'], ['user.permissions'], FALSE, FALSE);
// Create a user account that has the required permissions to read
// the watchdog resource via the REST API.
......
......@@ -218,6 +218,9 @@ public function testPost() {
*/
protected function getExpectedUnauthorizedAccessMessage($method) {
if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) {
if ($method === 'DELETE') {
return 'Only the file owner can update or delete the file entity.';
}
return parent::getExpectedUnauthorizedAccessMessage($method);
}
......
......@@ -4,6 +4,7 @@
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\TypedData\TypedDataInternalPropertiesHelper;
use Drupal\serialization\Normalizer\SerializedColumnNormalizerTrait;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
/**
......@@ -11,6 +12,8 @@
*/
class FieldItemNormalizer extends NormalizerBase {
use SerializedColumnNormalizerTrait;
/**
* The interface or class that this Normalizer supports.
*
......@@ -44,6 +47,7 @@ public function denormalize($data, $class, $format = NULL, array $context = [])
}
$field_item = $context['target_instance'];
$this->checkForSerializedStrings($data, $class, $field_item);
// If this field is translatable, we need to create a translated instance.
if (isset($data['lang'])) {
......@@ -71,6 +75,19 @@ public function denormalize($data, $class, $format = NULL, array $context = [])
* The value to use in Entity::setValue().
*/
protected function constructValue($data, $context) {
/** @var \Drupal\Core\Field\FieldItemInterface $field_item */
$field_item = $context['target_instance'];
$serialized_property_names = $this->getCustomSerializedPropertyNames($field_item);
// Explicitly serialize the input, unlike properties that rely on
// being automatically serialized, manually managed serialized properties
// expect to receive serialized input.
foreach ($serialized_property_names as $serialized_property_name) {
if (!empty($data[$serialized_property_name])) {
$data[$serialized_property_name] = serialize($data[$serialized_property_name]);
}
}
return $data;
}
......
......@@ -3,6 +3,7 @@
namespace Drupal\Tests\hal\Kernel;
use Drupal\Core\Url;
use Drupal\entity_test\Entity\EntitySerializedField;
use Drupal\field\Entity\FieldConfig;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
......
......@@ -3,14 +3,14 @@
namespace Drupal\Tests\language\Functional\Hal;
use Drupal\Tests\language\Functional\Rest\ConfigurableLanguageResourceTestBase;
use Drupal\Tests\rest\Functional\BasicAuthResourceWithInterfaceTranslationTestTrait;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group hal
*/
class ConfigurableLanguageHalJsonBasicAuthTest extends ConfigurableLanguageResourceTestBase {
use BasicAuthResourceWithInterfaceTranslationTestTrait;
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
......
......@@ -3,14 +3,14 @@
namespace Drupal\Tests\language\Functional\Hal;
use Drupal\Tests\language\Functional\Rest\ContentLanguageSettingsResourceTestBase;
use Drupal\Tests\rest\Functional\BasicAuthResourceWithInterfaceTranslationTestTrait;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group hal
*/
class ContentLanguageSettingsHalJsonBasicAuthTest extends ContentLanguageSettingsResourceTestBase {
use BasicAuthResourceWithInterfaceTranslationTestTrait;
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
......
......@@ -2,14 +2,14 @@
namespace Drupal\Tests\language\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceWithInterfaceTranslationTestTrait;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class ConfigurableLanguageJsonBasicAuthTest extends ConfigurableLanguageResourceTestBase {
use BasicAuthResourceWithInterfaceTranslationTestTrait;
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment