Commit 112e9bf2 authored by alexpott's avatar alexpott

Issue #2558905 by dawehner, pwolanin, cilefen, plach, willzyx: Content...

Issue #2558905 by dawehner, pwolanin, cilefen, plach, willzyx: Content translation module - Information disclosure by insufficient access checking
parent ec41d082
...@@ -267,7 +267,8 @@ function content_translation_menu_links_discovered_alter(array &$links) { ...@@ -267,7 +267,8 @@ function content_translation_menu_links_discovered_alter(array &$links) {
*/ */
function content_translation_translate_access(EntityInterface $entity) { function content_translation_translate_access(EntityInterface $entity) {
$account = \Drupal::currentUser(); $account = \Drupal::currentUser();
$condition = $entity instanceof ContentEntityInterface && !$entity->getUntranslated()->language()->isLocked() && \Drupal::languageManager()->isMultilingual() && $entity->isTranslatable() && $condition = $entity instanceof ContentEntityInterface && $entity->access('view') &&
!$entity->getUntranslated()->language()->isLocked() && \Drupal::languageManager()->isMultilingual() && $entity->isTranslatable() &&
($account->hasPermission('create content translations') || $account->hasPermission('update content translations') || $account->hasPermission('delete content translations')); ($account->hasPermission('create content translations') || $account->hasPermission('update content translations') || $account->hasPermission('delete content translations'));
return AccessResult::allowedIf($condition)->cachePerPermissions()->cacheUntilEntityChanges($entity); return AccessResult::allowedIf($condition)->cachePerPermissions()->cacheUntilEntityChanges($entity);
} }
......
...@@ -69,6 +69,7 @@ protected function alterRoutes(RouteCollection $collection) { ...@@ -69,6 +69,7 @@ protected function alterRoutes(RouteCollection $collection) {
'entity_type_id' => $entity_type_id, 'entity_type_id' => $entity_type_id,
), ),
array( array(
'_entity_access' => $entity_type_id . '.view',
'_access_content_translation_overview' => $entity_type_id, '_access_content_translation_overview' => $entity_type_id,
), ),
array( array(
...@@ -94,6 +95,7 @@ protected function alterRoutes(RouteCollection $collection) { ...@@ -94,6 +95,7 @@ protected function alterRoutes(RouteCollection $collection) {
), ),
array( array(
'_entity_access' => $entity_type_id . '.view',
'_access_content_translation_manage' => 'create', '_access_content_translation_manage' => 'create',
), ),
array( array(
......
...@@ -39,7 +39,7 @@ protected function setUp() { ...@@ -39,7 +39,7 @@ protected function setUp() {
* Overrides \Drupal\content_translation\Tests\ContentTranslationUITestBase::getTranslatorPermission(). * Overrides \Drupal\content_translation\Tests\ContentTranslationUITestBase::getTranslatorPermission().
*/ */
protected function getTranslatorPermissions() { protected function getTranslatorPermissions() {
return array_merge(parent::getTranslatorPermissions(), array('administer entity_test content')); return array_merge(parent::getTranslatorPermissions(), array('administer entity_test content', 'view test entity'));
} }
} }
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
use Drupal\language\Entity\ConfigurableLanguage; use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\node\Tests\NodeTestBase; use Drupal\node\Tests\NodeTestBase;
use Drupal\user\Entity\Role;
/** /**
* Tests the content translation operations available in the content listing. * Tests the content translation operations available in the content listing.
...@@ -75,6 +76,63 @@ function testOperationTranslateLink() { ...@@ -75,6 +76,63 @@ function testOperationTranslateLink() {
$this->drupalLogin($this->baseUser2); $this->drupalLogin($this->baseUser2);
$this->drupalGet('admin/content'); $this->drupalGet('admin/content');
$this->assertLinkByHref('node/' . $node->id() . '/translations'); $this->assertLinkByHref('node/' . $node->id() . '/translations');
// Ensure that an unintended misconfiguration of permissions does not open
// access to the translation form, see https://www.drupal.org/node/2558905.
$this->drupalLogout();
user_role_change_permissions(
Role::AUTHENTICATED_ID,
[
'create content translations' => TRUE,
'access content' => FALSE,
]
);
$this->drupalLogin($this->baseUser1);
$this->drupalGet($node->urlInfo('drupal:content-translation-overview'));
$this->assertResponse(403);
// Ensure that the translation overview is also not accessible when the user
// has 'access content', but the node is not published.
user_role_change_permissions(
Role::AUTHENTICATED_ID,
[
'create content translations' => TRUE,
'access content' => TRUE,
]
);
$node->setPublished(FALSE)->save();
$this->drupalGet($node->urlInfo('drupal:content-translation-overview'));
$this->assertResponse(403);
}
/**
* @see content_translation_translate_access()
*/
public function testContentTranslationOverviewAccess() {
$access_control_handler = \Drupal::entityManager()->getAccessControlHandler('node');
$user = $this->createUser(['create content translations', 'access content']);
$this->drupalLogin($user);
$node = $this->drupalCreateNode(['status' => FALSE, 'type' => 'article']);
$this->assertFalse(content_translation_translate_access($node)->isAllowed());
$access_control_handler->resetCache();
$node->setPublished(TRUE);
$node->save();
$this->assertTrue(content_translation_translate_access($node)->isAllowed());
$access_control_handler->resetCache();
user_role_change_permissions(
Role::AUTHENTICATED_ID,
[
'access content' => FALSE,
]
);
$user = $this->createUser(['create content translations']);
$this->drupalLogin($user);
$this->assertFalse(content_translation_translate_access($node)->isAllowed());
$access_control_handler->resetCache();
} }
} }
...@@ -38,6 +38,16 @@ protected function setUp() { ...@@ -38,6 +38,16 @@ protected function setUp() {
$this->setupEntity(); $this->setupEntity();
} }
/**
* {@inheritdoc}
*/
protected function getTranslatorPermissions() {
$permissions = parent::getTranslatorPermissions();
$permissions[] = 'view test entity';
return $permissions;
}
/** /**
* Overrides \Drupal\content_translation\Tests\ContentTranslationTestBase::getEditorPermissions(). * Overrides \Drupal\content_translation\Tests\ContentTranslationTestBase::getEditorPermissions().
*/ */
...@@ -109,7 +119,7 @@ function testWorkflows() { ...@@ -109,7 +119,7 @@ function testWorkflows() {
$ops = array('create' => t('Add'), 'update' => t('Edit'), 'delete' => t('Delete')); $ops = array('create' => t('Add'), 'update' => t('Edit'), 'delete' => t('Delete'));
$translations_url = $this->entity->urlInfo('drupal:content-translation-overview'); $translations_url = $this->entity->urlInfo('drupal:content-translation-overview');
foreach ($ops as $current_op => $item) { foreach ($ops as $current_op => $item) {
$user = $this->drupalCreateUser(array($this->getTranslatePermission(), "$current_op content translations")); $user = $this->drupalCreateUser(array($this->getTranslatePermission(), "$current_op content translations", 'view test entity'));
$this->drupalLogin($user); $this->drupalLogin($user);
$this->drupalGet($translations_url); $this->drupalGet($translations_url);
......
...@@ -54,6 +54,15 @@ protected function setUp() { ...@@ -54,6 +54,15 @@ protected function setUp() {
ViewTestData::createTestViews(get_class($this), array('content_translation_test_views')); ViewTestData::createTestViews(get_class($this), array('content_translation_test_views'));
} }
/**
* {@inheritdoc}
*/
protected function getTranslatorPermissions() {
$permissions = parent::getTranslatorPermissions();
$permissions[] = 'access user profiles';
return $permissions;
}
/** /**
* Tests the content translation overview link field handler. * Tests the content translation overview link field handler.
*/ */
......
...@@ -54,8 +54,9 @@ public static function createInstance(ContainerInterface $container, EntityTypeI ...@@ -54,8 +54,9 @@ public static function createInstance(ContainerInterface $container, EntityTypeI
protected function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) { protected function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) {
switch ($operation) { switch ($operation) {
case 'view': case 'view':
// There is no direct view. // There is no direct viewing of a menu link, but still for purposes of
return AccessResult::neutral(); // content_translation we need a generic way to check access.
return AccessResult::allowedIfHasPermission($account, 'administer menu');
case 'update': case 'update':
if (!$account->hasPermission('administer menu')) { if (!$account->hasPermission('administer menu')) {
......
...@@ -6,6 +6,8 @@ ...@@ -6,6 +6,8 @@
*/ */
namespace Drupal\rest\Tests; namespace Drupal\rest\Tests;
use Drupal\Core\Session\AccountInterface;
use Drupal\user\Entity\Role;
/** /**
* Tests the structure of a REST resource. * Tests the structure of a REST resource.
...@@ -38,6 +40,10 @@ protected function setUp() { ...@@ -38,6 +40,10 @@ protected function setUp() {
// Create an entity programmatically. // Create an entity programmatically.
$this->entity = $this->entityCreate('entity_test'); $this->entity = $this->entityCreate('entity_test');
$this->entity->save(); $this->entity->save();
Role::load(AccountInterface::ANONYMOUS_ROLE)
->grantPermission('view test entity')
->save();
} }
/** /**
......
...@@ -39,6 +39,7 @@ protected function setUp() { ...@@ -39,6 +39,7 @@ protected function setUp() {
$this->entities[] = $entity_test; $this->entities[] = $entity_test;
} }
$this->drupalLogin($this->drupalCreateUser(['view test entity']));
} }
/** /**
......
...@@ -4,7 +4,7 @@ entity.entity_test.canonical: ...@@ -4,7 +4,7 @@ entity.entity_test.canonical:
_entity_view: 'entity_test.full' _entity_view: 'entity_test.full'
_title: 'Test full view mode' _title: 'Test full view mode'
requirements: requirements:
_access: 'TRUE' _entity_access: 'entity_test.view'
entity.entity_test.render_options: entity.entity_test.render_options:
path: '/entity_test_converter/{foo}' path: '/entity_test_converter/{foo}'
...@@ -15,7 +15,7 @@ entity.entity_test.render_options: ...@@ -15,7 +15,7 @@ entity.entity_test.render_options:
defaults: defaults:
_entity_view: 'entity_test.full' _entity_view: 'entity_test.full'
requirements: requirements:
_access: 'TRUE' _entity_access: 'foo.view'
entity.entity_test.render_no_view_mode: entity.entity_test.render_no_view_mode:
path: '/entity_test_no_view_mode/{entity_test}' path: '/entity_test_no_view_mode/{entity_test}'
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
use Drupal\Core\Session\AccountInterface; use Drupal\Core\Session\AccountInterface;
use Drupal\entity_test\Entity\EntityTest; use Drupal\entity_test\Entity\EntityTest;
use Drupal\simpletest\UserCreationTrait; use Drupal\simpletest\UserCreationTrait;
use Drupal\user\Entity\Role;
use Drupal\views\Tests\ViewKernelTestBase; use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views; use Drupal\views\Views;
...@@ -51,6 +52,7 @@ protected function setUpFixtures() { ...@@ -51,6 +52,7 @@ protected function setUpFixtures() {
$this->installEntitySchema('user'); $this->installEntitySchema('user');
$this->installEntitySchema('entity_test'); $this->installEntitySchema('entity_test');
$this->installConfig(['user']);
// Create some test entities. // Create some test entities.
for ($i = 0; $i < 5; $i++) { for ($i = 0; $i < 5; $i++) {
...@@ -58,7 +60,11 @@ protected function setUpFixtures() { ...@@ -58,7 +60,11 @@ protected function setUpFixtures() {
} }
// Create and admin user. // Create and admin user.
$this->adminUser = $this->createUser([], FALSE, TRUE); $this->adminUser = $this->createUser(['view test entity'], FALSE, TRUE);
Role::load(AccountInterface::ANONYMOUS_ROLE)
->grantPermission('view test entity')
->save();
} }
/** /**
......
...@@ -9,6 +9,9 @@ ...@@ -9,6 +9,9 @@
use Drupal\Core\Cache\Context\CacheContextsManager; use Drupal\Core\Cache\Context\CacheContextsManager;
use Drupal\Core\DependencyInjection\Container; use Drupal\Core\DependencyInjection\Container;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\node\NodeInterface;
use Symfony\Component\HttpFoundation\ParameterBag; use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\Routing\Route; use Symfony\Component\Routing\Route;
use Drupal\Core\Access\AccessResult; use Drupal\Core\Access\AccessResult;
...@@ -18,37 +21,67 @@ ...@@ -18,37 +21,67 @@
/** /**
* Unit test of entity access checking system. * Unit test of entity access checking system.
* *
* @coversDefaultClass \Drupal\Core\Entity\EntityAccessCheck
*
* @group Access * @group Access
* @group Entity * @group Entity
*/ */
class EntityAccessCheckTest extends UnitTestCase { class EntityAccessCheckTest extends UnitTestCase {
/** /**
* Tests the method for checking access to routes. * {@inheritdoc}
*/ */
public function testAccess() { protected function setUp() {
$cache_contexts_manager = $this->prophesize(CacheContextsManager::class)->reveal(); $cache_contexts_manager = $this->prophesize(CacheContextsManager::class)->reveal();
$container = new Container(); $container = new Container();
$container->set('cache_contexts_manager', $cache_contexts_manager); $container->set('cache_contexts_manager', $cache_contexts_manager);
\Drupal::setContainer($container); \Drupal::setContainer($container);
}
/**
* Tests the method for checking access to routes.
*/
public function testAccess() {
$route = new Route('/foo/{var_name}', [], ['_entity_access' => 'var_name.update'], ['parameters' => ['var_name' => ['type' => 'entity:node']]]);
/** @var \Drupal\Core\Session\AccountInterface $account */
$account = $this->prophesize(AccountInterface::class)->reveal();
/** @var \Drupal\node\NodeInterface|\Prophecy\Prophecy\ObjectProphecy $route_match */
$node = $this->prophesize(NodeInterface::class);
$node->access('update', $account, TRUE)->willReturn(AccessResult::allowed());
$node = $node->reveal();
/** @var \Drupal\Core\Routing\RouteMatchInterface|\Prophecy\Prophecy\ObjectProphecy $route_match */
$route_match = $this->prophesize(RouteMatchInterface::class);
$route_match->getRawParameters()->willReturn(new ParameterBag(['var_name' => 1]));
$route_match->getParameters()->willReturn(new ParameterBag(['var_name' => $node]));
$route_match = $route_match->reveal();
$access_check = new EntityAccessCheck();
$this->assertEquals(AccessResult::allowed(), $access_check->access($route, $route_match, $account));
}
/**
* @covers ::access
*/
public function testAccessWithTypePlaceholder() {
$route = new Route('/foo/{entity_type}/{var_name}', [], ['_entity_access' => 'var_name.update'], ['parameters' => ['var_name' => ['type' => 'entity:{entity_type}']]]);
/** @var \Drupal\Core\Session\AccountInterface $account */
$account = $this->prophesize(AccountInterface::class)->reveal();
/** @var \Drupal\node\NodeInterface|\Prophecy\Prophecy\ObjectProphecy $node */
$node = $this->prophesize(NodeInterface::class);
$node->access('update', $account, TRUE)->willReturn(AccessResult::allowed());
$node = $node->reveal();
/** @var \Drupal\Core\Routing\RouteMatchInterface|\Prophecy\Prophecy\ObjectProphecy $route_match */
$route_match = $this->prophesize(RouteMatchInterface::class);
$route_match->getRawParameters()->willReturn(new ParameterBag(['entity_type' => 'node', 'var_name' => 1]));
$route_match->getParameters()->willReturn(new ParameterBag(['entity_type' => 'node', 'var_name' => $node]));
$route_match = $route_match->reveal();
$route = new Route('/foo', array(), array('_entity_access' => 'node.update'));
$upcasted_arguments = new ParameterBag();
$route_match = $this->getMock('Drupal\Core\Routing\RouteMatchInterface');
$route_match->expects($this->once())
->method('getParameters')
->will($this->returnValue($upcasted_arguments));
$node = $this->getMockBuilder('Drupal\node\Entity\Node')
->disableOriginalConstructor()
->getMock();
$node->expects($this->any())
->method('access')
->will($this->returnValue(AccessResult::allowed()->cachePerPermissions()));
$access_check = new EntityAccessCheck(); $access_check = new EntityAccessCheck();
$upcasted_arguments->set('node', $node); $this->assertEquals(AccessResult::allowed(), $access_check->access($route, $route_match, $account));
$account = $this->getMock('Drupal\Core\Session\AccountInterface');
$access = $access_check->access($route, $route_match, $account);
$this->assertEquals(AccessResult::allowed()->cachePerPermissions(), $access);
} }
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment