Commit a6bcc3cc authored by bojanz's avatar bojanz Committed by bojanz

Issue #2499645 by bojanz: Start using the entity query access API on orders, products and stores

parent bf6abe3f
......@@ -32,3 +32,9 @@ services:
arguments: ['@commerce_cart.cart_provider']
tags:
- { name: event_subscriber }
commerce_cart.query_access_subscriber:
class: Drupal\commerce_cart\EventSubscriber\QueryAccessSubscriber
arguments: ['@commerce_cart.cart_provider', '@commerce_cart.cart_session']
tags:
- { name: event_subscriber, priority: 100 }
......@@ -213,7 +213,8 @@ class CartProvider implements CartProviderInterface {
->condition('state', 'draft')
->condition('cart', TRUE)
->condition('uid', $account->id())
->sort('order_id', 'DESC');
->sort('order_id', 'DESC')
->accessCheck(FALSE);
$cart_ids = $query->execute();
}
else {
......
<?php
namespace Drupal\commerce_cart\EventSubscriber;
use Drupal\commerce_cart\CartProviderInterface;
use Drupal\commerce_cart\CartSessionInterface;
use Drupal\entity\QueryAccess\QueryAccessEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class QueryAccessSubscriber implements EventSubscriberInterface {
/**
* The cart provider.
*
* @var \Drupal\commerce_cart\CartProviderInterface
*/
protected $cartProvider;
/**
* The cart session.
*
* @var \Drupal\commerce_cart\CartSessionInterface
*/
protected $cartSession;
/**
* Constructs a new QueryAccessSubscriber object.
*
* @param \Drupal\commerce_cart\CartProviderInterface $cart_provider
* The cart provider.
* @param \Drupal\commerce_cart\CartSessionInterface $cart_session
* The cart session.
*/
public function __construct(CartProviderInterface $cart_provider, CartSessionInterface $cart_session) {
$this->cartProvider = $cart_provider;
$this->cartSession = $cart_session;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
return [
'entity.query_access.commerce_order' => 'onQueryAccess',
];
}
/**
* Modifies the access conditions for cart orders.
*
* @param \Drupal\entity\QueryAccess\QueryAccessEvent $event
* The event.
*/
public function onQueryAccess(QueryAccessEvent $event) {
if ($event->getOperation() != 'view') {
return;
}
$conditions = $event->getConditions();
// The user already has full access due to a "administer commerce_order"
// or "view commerce_order" permission.
if (!$conditions->count() && !$conditions->alwaysFalse()) {
return;
}
$account = $event->getAccount();
// Any user can view their own active carts, regardless of any permissions.
// Authenticated users can also see their own completed carts.
$cart_ids = $this->cartProvider->getCartIds($account);
if ($account->isAuthenticated()) {
$completed_cart_ids = $this->cartSession->getCartIds(CartSessionInterface::COMPLETED);
$cart_ids = array_merge($cart_ids, $completed_cart_ids);
}
if (!empty($cart_ids)) {
$conditions->addCondition('order_id', $cart_ids);
$conditions->alwaysFalse(FALSE);
}
}
}
......@@ -46,7 +46,8 @@ class AddToCartFormTest extends CartBrowserTestBase {
// Find the newly created anonymous cart.
$query = \Drupal::entityQuery('commerce_order')
->condition('cart', TRUE)
->condition('uid', 0);
->condition('uid', 0)
->accessCheck(FALSE);
$result = $query->execute();
$cart_id = reset($result);
$cart = Order::load($cart_id);
......
......@@ -32,6 +32,7 @@ use Drupal\profile\Entity\ProfileInterface;
* "event" = "Drupal\commerce_order\Event\OrderEvent",
* "storage" = "Drupal\commerce_order\OrderStorage",
* "access" = "Drupal\commerce_order\OrderAccessControlHandler",
* "query_access" = "Drupal\commerce_order\OrderQueryAccessHandler",
* "permission_provider" = "Drupal\commerce_order\OrderPermissionProvider",
* "list_builder" = "Drupal\commerce_order\OrderListBuilder",
* "views_data" = "Drupal\commerce\CommerceEntityViewsData",
......
<?php
namespace Drupal\commerce_order;
use Drupal\Core\Session\AccountInterface;
use Drupal\entity\QueryAccess\ConditionGroup;
use Drupal\entity\QueryAccess\QueryAccessHandlerBase;
/**
* Controls query access based on the Order entity permissions.
*
* @see \Drupal\commerce_order\OrderAccessControlHandler
* @see \Drupal\commerce_order\OrderPermissionProvider
*/
class OrderQueryAccessHandler extends QueryAccessHandlerBase {
/**
* {@inheritdoc}
*/
protected function buildEntityConditions($operation, AccountInterface $account) {
// Orders don't implement EntityOwnerInterface, but they do have a
// "view own" permission.
if ($operation == 'view') {
$conditions = new ConditionGroup('OR');
$conditions->addCacheContexts(['user.permissions']);
// The $entity_type permission.
if ($account->hasPermission('view commerce_order')) {
// The user has full access, no conditions needed.
return $conditions;
}
// Own $entity_type permission.
if ($account->hasPermission('view own commerce_order')) {
$conditions->addCacheContexts(['user']);
$conditions->addCondition('uid', $account->id());
}
$bundles = array_keys($this->bundleInfo->getBundleInfo('commerce_order'));
$bundles_with_any_permission = [];
foreach ($bundles as $bundle) {
if ($account->hasPermission("view $bundle commerce_order")) {
$bundles_with_any_permission[] = $bundle;
}
}
// The $bundle permission.
if ($bundles_with_any_permission) {
$conditions->addCondition('type', $bundles_with_any_permission);
}
return $conditions->count() ? $conditions : NULL;
}
return parent::buildEntityConditions($operation, $account);
}
}
<?php
namespace Drupal\Tests\commerce_order\Kernel;
use Drupal\commerce_order\Entity\OrderType;
use Drupal\commerce_order\OrderQueryAccessHandler;
use Drupal\entity\QueryAccess\Condition;
use Drupal\Tests\commerce\Kernel\CommerceKernelTestBase;
/**
* Tests the order query access handler.
*
* @coversDefaultClass \Drupal\commerce_order\OrderQueryAccessHandler
* @group commerce
*/
class OrderQueryAccessHandlerTest extends CommerceKernelTestBase {
/**
* The query access handler.
*
* @var \Drupal\commerce_order\OrderQueryAccessHandler
*/
protected $handler;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [
'entity_reference_revisions',
'path',
'profile',
'state_machine',
'commerce_product',
'commerce_order',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('profile');
$this->installEntitySchema('commerce_order');
$this->installEntitySchema('commerce_order_item');
$this->installEntitySchema('commerce_product');
$this->installEntitySchema('commerce_product_variation');
$this->installConfig(['commerce_product', 'commerce_order']);
// Create uid: 1 here so that it's skipped in test cases.
$admin_user = $this->createUser();
$entity_type_manager = $this->container->get('entity_type.manager');
$entity_type = $entity_type_manager->getDefinition('commerce_order');
$this->handler = OrderQueryAccessHandler::createInstance($this->container, $entity_type);
OrderType::create([
'id' => 'first',
'label' => 'First',
'workflow' => 'order_default',
])->save();
OrderType::create([
'id' => 'second',
'label' => 'Second',
'workflow' => 'order_default',
])->save();
}
/**
* @covers ::getConditions
*/
public function testNoAccess() {
foreach (['view', 'update', 'delete'] as $operation) {
$user = $this->createUser([], ['access content']);
$conditions = $this->handler->getConditions($operation, $user);
$this->assertEquals(0, $conditions->count());
$this->assertEquals(['user.permissions'], $conditions->getCacheContexts());
$this->assertTrue($conditions->isAlwaysFalse());
}
}
/**
* @covers ::getConditions
*/
public function testAdmin() {
foreach (['view', 'update', 'delete'] as $operation) {
$user = $this->createUser([], ['administer commerce_order']);
$conditions = $this->handler->getConditions($operation, $user);
$this->assertEquals(0, $conditions->count());
$this->assertEquals(['user.permissions'], $conditions->getCacheContexts());
$this->assertFalse($conditions->isAlwaysFalse());
}
}
/**
* @covers ::getConditions
*/
public function testView() {
// Entity type permission.
$user = $this->createUser([], ['view commerce_order']);
$conditions = $this->handler->getConditions('view', $user);
$this->assertEquals(0, $conditions->count());
$this->assertEquals(['user.permissions'], $conditions->getCacheContexts());
$this->assertFalse($conditions->isAlwaysFalse());
// Own permission.
$user = $this->createUser([], ['view own commerce_order']);
$conditions = $this->handler->getConditions('view', $user);
$expected_conditions = [
new Condition('uid', $user->id()),
];
$this->assertEquals('OR', $conditions->getConjunction());
$this->assertEquals(1, $conditions->count());
$this->assertEquals($expected_conditions, $conditions->getConditions());
$this->assertEquals(['user', 'user.permissions'], $conditions->getCacheContexts());
$this->assertFalse($conditions->isAlwaysFalse());
// Bundle permission.
$user = $this->createUser([], ['view first commerce_order']);
$conditions = $this->handler->getConditions('view', $user);
$expected_conditions = [
new Condition('type', ['first']),
];
$this->assertEquals('OR', $conditions->getConjunction());
$this->assertEquals(1, $conditions->count());
$this->assertEquals($expected_conditions, $conditions->getConditions());
$this->assertEquals(['user.permissions'], $conditions->getCacheContexts());
$this->assertFalse($conditions->isAlwaysFalse());
}
/**
* @covers ::getConditions
*/
public function testUpdateDelete() {
foreach (['update', 'delete'] as $operation) {
// Entity type permission.
$user = $this->createUser([], ["$operation commerce_order"]);
$conditions = $this->handler->getConditions($operation, $user);
$this->assertEquals(0, $conditions->count());
$this->assertEquals(['user.permissions'], $conditions->getCacheContexts());
$this->assertFalse($conditions->isAlwaysFalse());
// Bundle permission.
$user = $this->createUser([], [
"$operation first commerce_order",
"$operation second commerce_order",
]);
$conditions = $this->handler->getConditions($operation, $user);
$expected_conditions = [
new Condition('type', ['first', 'second']),
];
$this->assertEquals('OR', $conditions->getConjunction());
$this->assertEquals(1, $conditions->count());
$this->assertEquals($expected_conditions, $conditions->getConditions());
$this->assertEquals(['user.permissions'], $conditions->getCacheContexts());
$this->assertFalse($conditions->isAlwaysFalse());
}
}
}
......@@ -29,6 +29,7 @@ use Drupal\user\UserInterface;
* "event" = "Drupal\commerce_product\Event\ProductEvent",
* "storage" = "Drupal\commerce\CommerceContentEntityStorage",
* "access" = "Drupal\entity\EntityAccessControlHandler",
* "query_access" = "Drupal\entity\QueryAccess\QueryAccessHandler",
* "permission_provider" = "Drupal\entity\EntityPermissionProvider",
* "view_builder" = "Drupal\commerce_product\ProductViewBuilder",
* "list_builder" = "Drupal\commerce_product\ProductListBuilder",
......
......@@ -167,7 +167,9 @@ class ProductAdminTest extends ProductBrowserTestBase {
$this->assertSession()->pageTextContains('You are not authorized to access this page.');
$this->assertNotEmpty(!$this->getSession()->getPage()->hasLink('Add product'));
// Login and confirm access for 'access commerce_product overview' permission.
// Login and confirm access for 'access commerce_product overview'
// permission. The second product should no longer be visible because
// it is unpublished.
$user = $this->drupalCreateUser(['access commerce_product overview']);
$this->drupalLogin($user);
$this->drupalGet('admin/commerce/products');
......@@ -175,14 +177,12 @@ class ProductAdminTest extends ProductBrowserTestBase {
$this->assertSession()->pageTextNotContains('You are not authorized to access this page.');
$this->assertNotEmpty(!$this->getSession()->getPage()->hasLink('Add product'));
$row_count = $this->getSession()->getPage()->findAll('xpath', '//table/tbody/tr');
$this->assertEquals(3, count($row_count), 'Table has 3 rows.');
$this->assertEquals(2, count($row_count), 'Table has 3 rows.');
// Confirm that product titles are displayed.
$page = $this->getSession()->getPage();
$product_count = $page->findAll('xpath', '//table/tbody/tr/td/a[text()="First product"]');
$this->assertEquals(1, count($product_count), 'First product is displayed.');
$product_count = $page->findAll('xpath', '//table/tbody/tr/td/a[text()="Second product"]');
$this->assertEquals(1, count($product_count), 'Second product is displayed.');
$product_count = $page->findAll('xpath', '//table/tbody/tr/td/a[text()="Third product"]');
$this->assertEquals(1, count($product_count), 'Third product is displayed.');
......@@ -190,11 +190,11 @@ class ProductAdminTest extends ProductBrowserTestBase {
$product_count = $page->findAll('xpath', '//table/tbody/tr/td[starts-with(text(), "Default")]');
$this->assertEquals(1, count($product_count), 'Default product type exists in the table.');
$product_count = $page->findAll('xpath', '//table/tbody/tr/td[starts-with(text(), "Random")]');
$this->assertEquals(2, count($product_count), 'Random product types exist in the table.');
$this->assertEquals(1, count($product_count), 'Random product type exist in the table.');
// Confirm that product statuses are displayed.
// Confirm that the right product statuses are displayed.
$product_count = $page->findAll('xpath', '//table/tbody/tr/td[starts-with(text(), "Unpublished")]');
$this->assertEquals(1, count($product_count), 'Unpublished product exists in the table.');
$this->assertEquals(0, count($product_count), 'Unpublished product do not exist in the table.');
$product_count = $page->findAll('xpath', '//table/tbody/tr/td[starts-with(text(), "Published")]');
$this->assertEquals(2, count($product_count), 'Published products exist in the table.');
}
......
......@@ -29,6 +29,7 @@ use Drupal\Core\Field\BaseFieldDefinition;
* "event" = "Drupal\commerce_store\Event\StoreEvent",
* "storage" = "Drupal\commerce_store\StoreStorage",
* "access" = "Drupal\entity\EntityAccessControlHandler",
* "query_access" = "Drupal\entity\QueryAccess\QueryAccessHandler",
* "permission_provider" = "Drupal\entity\EntityPermissionProvider",
* "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
* "list_builder" = "Drupal\commerce_store\StoreListBuilder",
......
......@@ -72,6 +72,7 @@ class EntitySelect extends FormElement {
throw new \InvalidArgumentException('Missing required #target_type parameter.');
}
/** @var \Drupal\Core\Entity\EntityStorageInterface $storage */
$storage = \Drupal::service('entity_type.manager')->getStorage($element['#target_type']);
$entity_count = $storage->getQuery()->count()->execute();
$element['#tree'] = TRUE;
......@@ -87,7 +88,9 @@ class EntitySelect extends FormElement {
}
if ($entity_count <= $element['#autocomplete_threshold']) {
$entities = $storage->loadMultiple();
// Start with a query to get only access-filtered results.
$entity_ids = $storage->getQuery()->execute();
$entities = $storage->loadMultiple($entity_ids);
$entity_labels = EntityHelper::extractLabels($entities);
// Radio buttons don't have a None option by default.
if (!$element['#multiple'] && !$element['#required']) {
......
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