c3eb8ef583c0bfe5ccf06bce3b2ee3ee8b1adb82
[yaffs-website] / web / core / tests / Drupal / KernelTests / Core / Entity / EntityAccessControlHandlerTest.php
1 <?php
2
3 namespace Drupal\KernelTests\Core\Entity;
4
5 use Drupal\Core\Language\LanguageInterface;
6 use Drupal\Core\Session\AccountInterface;
7 use Drupal\Core\Access\AccessibleInterface;
8 use Drupal\Core\Entity\EntityAccessControlHandler;
9 use Drupal\Core\Session\AnonymousUserSession;
10 use Drupal\entity_test\Entity\EntityTest;
11 use Drupal\entity_test\Entity\EntityTestStringId;
12 use Drupal\entity_test\Entity\EntityTestDefaultAccess;
13 use Drupal\entity_test\Entity\EntityTestNoUuid;
14 use Drupal\entity_test\Entity\EntityTestLabel;
15 use Drupal\entity_test\Entity\EntityTestRev;
16 use Drupal\language\Entity\ConfigurableLanguage;
17 use Drupal\user\Entity\User;
18
19 /**
20  * Tests the entity access control handler.
21  *
22  * @coversDefaultClass \Drupal\Core\Entity\EntityAccessControlHandler
23  * @group Entity
24  */
25 class EntityAccessControlHandlerTest extends EntityLanguageTestBase {
26
27   /**
28    * {@inheritdoc}
29    */
30   public function setUp() {
31     parent::setUp();
32
33     $this->installEntitySchema('entity_test_no_uuid');
34     $this->installEntitySchema('entity_test_rev');
35     $this->installEntitySchema('entity_test_string_id');
36   }
37
38   /**
39    * Asserts entity access correctly grants or denies access.
40    */
41   public function assertEntityAccess($ops, AccessibleInterface $object, AccountInterface $account = NULL) {
42     foreach ($ops as $op => $result) {
43       $message = format_string("Entity access returns @result with operation '@op'.", [
44         '@result' => !isset($result) ? 'null' : ($result ? 'true' : 'false'),
45         '@op' => $op,
46       ]);
47
48       $this->assertEqual($result, $object->access($op, $account), $message);
49     }
50   }
51
52   /**
53    * Ensures user labels are accessible for everyone.
54    */
55   public function testUserLabelAccess() {
56     // Set up a non-admin user.
57     \Drupal::currentUser()->setAccount($this->createUser(['uid' => 2]));
58
59     $anonymous_user = User::getAnonymousUser();
60     $user = $this->createUser();
61
62     // The current user is allowed to view the anonymous user label.
63     $this->assertEntityAccess([
64       'create' => FALSE,
65       'update' => FALSE,
66       'delete' => FALSE,
67       'view' => FALSE,
68       'view label' => TRUE,
69     ], $anonymous_user);
70
71     // The current user is allowed to view user labels.
72     $this->assertEntityAccess([
73       'create' => FALSE,
74       'update' => FALSE,
75       'delete' => FALSE,
76       'view' => FALSE,
77       'view label' => TRUE,
78     ], $user);
79
80     // Switch to a anonymous user account.
81     $account_switcher = \Drupal::service('account_switcher');
82     $account_switcher->switchTo(new AnonymousUserSession());
83
84     // The anonymous user is allowed to view the anonymous user label.
85     $this->assertEntityAccess([
86       'create' => FALSE,
87       'update' => FALSE,
88       'delete' => FALSE,
89       'view' => FALSE,
90       'view label' => TRUE,
91     ], $anonymous_user);
92
93     // The anonymous user is allowed to view user labels.
94     $this->assertEntityAccess([
95       'create' => FALSE,
96       'update' => FALSE,
97       'delete' => FALSE,
98       'view' => FALSE,
99       'view label' => TRUE,
100     ], $user);
101
102     // Restore user account.
103     $account_switcher->switchBack();
104   }
105
106   /**
107    * Ensures entity access is properly working.
108    */
109   public function testEntityAccess() {
110     // Set up a non-admin user that is allowed to view test entities.
111     \Drupal::currentUser()->setAccount($this->createUser(['uid' => 2], ['view test entity']));
112
113     // Use the 'entity_test_label' entity type in order to test the 'view label'
114     // access operation.
115     $entity = EntityTestLabel::create([
116       'name' => 'test',
117     ]);
118
119     // The current user is allowed to view entities.
120     $this->assertEntityAccess([
121       'create' => FALSE,
122       'update' => FALSE,
123       'delete' => FALSE,
124       'view' => TRUE,
125       'view label' => TRUE,
126     ], $entity);
127
128     // The custom user is not allowed to perform any operation on test entities,
129     // except for viewing their label.
130     $custom_user = $this->createUser();
131     $this->assertEntityAccess([
132       'create' => FALSE,
133       'update' => FALSE,
134       'delete' => FALSE,
135       'view' => FALSE,
136       'view label' => TRUE,
137     ], $entity, $custom_user);
138   }
139
140   /**
141    * Ensures default entity access is checked when necessary.
142    *
143    * This ensures that the default checkAccess() implementation of the
144    * entity access control handler is considered if hook_entity_access() has not
145    * explicitly forbidden access. Therefore the default checkAccess()
146    * implementation can forbid access, even after access was already explicitly
147    * allowed by hook_entity_access().
148    *
149    * @see \Drupal\entity_test\EntityTestAccessControlHandler::checkAccess()
150    * @see entity_test_entity_access()
151    */
152   public function testDefaultEntityAccess() {
153     // Set up a non-admin user that is allowed to view test entities.
154     \Drupal::currentUser()->setAccount($this->createUser(['uid' => 2], ['view test entity']));
155     $entity = EntityTest::create([
156         'name' => 'forbid_access',
157       ]);
158
159     // The user is denied access to the entity.
160     $this->assertEntityAccess([
161         'create' => FALSE,
162         'update' => FALSE,
163         'delete' => FALSE,
164         'view' => FALSE,
165       ], $entity);
166   }
167
168   /**
169    * Ensures that the default handler is used as a fallback.
170    */
171   public function testEntityAccessDefaultController() {
172     // The implementation requires that the global user id can be loaded.
173     \Drupal::currentUser()->setAccount($this->createUser(['uid' => 2]));
174
175     // Check that the default access control handler is used for entities that don't
176     // have a specific access control handler defined.
177     $handler = $this->container->get('entity.manager')->getAccessControlHandler('entity_test_default_access');
178     $this->assertTrue($handler instanceof EntityAccessControlHandler, 'The default entity handler is used for the entity_test_default_access entity type.');
179
180     $entity = EntityTestDefaultAccess::create();
181     $this->assertEntityAccess([
182       'create' => FALSE,
183       'update' => FALSE,
184       'delete' => FALSE,
185       'view' => FALSE,
186     ], $entity);
187   }
188
189   /**
190    * Ensures entity access for entity translations is properly working.
191    */
192   public function testEntityTranslationAccess() {
193
194     // Set up a non-admin user that is allowed to view test entity translations.
195     \Drupal::currentUser()->setAccount($this->createUser(['uid' => 2], ['view test entity translations']));
196
197     // Create two test languages.
198     foreach (['foo', 'bar'] as $langcode) {
199       ConfigurableLanguage::create([
200         'id' => $langcode,
201         'label' => $this->randomString(),
202       ])->save();
203     }
204
205     $entity = EntityTest::create([
206       'name' => 'test',
207       'langcode' => 'foo',
208     ]);
209     $entity->save();
210
211     $translation = $entity->addTranslation('bar');
212     $this->assertEntityAccess([
213       'view' => TRUE,
214     ], $translation);
215   }
216
217   /**
218    * Ensures the static access cache works correctly in the absence of an UUID.
219    *
220    * @see entity_test_entity_access()
221    */
222   public function testEntityWithoutUuidAccessCache() {
223     $account = $this->createUser();
224
225     $entity1 = EntityTestNoUuid::create([
226       'name' => 'Accessible',
227     ]);
228     $entity1->save();
229
230     $entity2 = EntityTestNoUuid::create([
231       'name' => 'Inaccessible',
232     ]);
233     $entity2->save();
234
235     $this->assertTrue($entity1->access('delete', $account), 'Entity 1 can be deleted.');
236     $this->assertFalse($entity2->access('delete', $account), 'Entity 2 CANNOT be deleted.');
237
238     $entity1
239       ->setName('Inaccessible')
240       ->setNewRevision();
241     $entity1->save();
242
243     $this->assertFalse($entity1->access('delete', $account), 'Entity 1 revision 2 CANNOT be deleted.');
244   }
245
246   /**
247    * Ensures the static access cache works correctly with a UUID and revisions.
248    *
249    * @see entity_test_entity_access()
250    */
251   public function testEntityWithUuidAccessCache() {
252     $account = $this->createUser();
253
254     $entity1 = EntityTestRev::create([
255       'name' => 'Accessible',
256     ]);
257     $entity1->save();
258
259     $entity2 = EntityTestRev::create([
260       'name' => 'Inaccessible',
261     ]);
262     $entity2->save();
263
264     $this->assertTrue($entity1->access('delete', $account), 'Entity 1 can be deleted.');
265     $this->assertFalse($entity2->access('delete', $account), 'Entity 2 CANNOT be deleted.');
266
267     $entity1
268       ->setName('Inaccessible')
269       ->setNewRevision();
270     $entity1->save();
271
272     $this->assertFalse($entity1->access('delete', $account), 'Entity 1 revision 2 CANNOT be deleted.');
273   }
274
275   /**
276    * Tests hook invocations.
277    */
278   public function testHooks() {
279     $state = $this->container->get('state');
280     $entity = EntityTest::create([
281       'name' => 'test',
282     ]);
283
284     // Test hook_entity_create_access() and hook_ENTITY_TYPE_create_access().
285     $entity->access('create');
286     $this->assertEqual($state->get('entity_test_entity_create_access'), TRUE);
287     $this->assertIdentical($state->get('entity_test_entity_create_access_context'), [
288       'entity_type_id' => 'entity_test',
289       'langcode' => LanguageInterface::LANGCODE_DEFAULT,
290     ]);
291     $this->assertEqual($state->get('entity_test_entity_test_create_access'), TRUE);
292
293     // Test hook_entity_access() and hook_ENTITY_TYPE_access().
294     $entity->access('view');
295     $this->assertEqual($state->get('entity_test_entity_access'), TRUE);
296     $this->assertEqual($state->get('entity_test_entity_test_access'), TRUE);
297   }
298
299   /**
300    * Tests the default access handling for the ID and UUID fields.
301    *
302    * @covers ::fieldAccess
303    * @dataProvider providerTestFieldAccess
304    */
305   public function testFieldAccess($entity_class, array $entity_create_values, $expected_id_create_access) {
306     // Set up a non-admin user that is allowed to create and update test
307     // entities.
308     \Drupal::currentUser()->setAccount($this->createUser(['uid' => 2], ['administer entity_test content']));
309
310     // Create the entity to test field access with.
311     $entity = $entity_class::create($entity_create_values);
312
313     // On newly-created entities, field access must allow setting the UUID
314     // field.
315     $this->assertTrue($entity->get('uuid')->access('edit'));
316     $this->assertTrue($entity->get('uuid')->access('edit', NULL, TRUE)->isAllowed());
317     // On newly-created entities, field access will not allow setting the ID
318     // field if the ID is of type serial. It will allow access if it is of type
319     // string.
320     $this->assertEquals($expected_id_create_access, $entity->get('id')->access('edit'));
321     $this->assertEquals($expected_id_create_access, $entity->get('id')->access('edit', NULL, TRUE)->isAllowed());
322
323     // Save the entity and check that we can not update the ID or UUID fields
324     // anymore.
325     $entity->save();
326
327     // If the ID has been set as part of the create ensure it has been set
328     // correctly.
329     if (isset($entity_create_values['id'])) {
330       $this->assertSame($entity_create_values['id'], $entity->id());
331     }
332     // The UUID is hard-coded by the data provider.
333     $this->assertSame('60e3a179-79ed-4653-ad52-5e614c8e8fbe', $entity->uuid());
334     $this->assertFalse($entity->get('uuid')->access('edit'));
335     $access_result = $entity->get('uuid')->access('edit', NULL, TRUE);
336     $this->assertTrue($access_result->isForbidden());
337     $this->assertEquals('The entity UUID cannot be changed.', $access_result->getReason());
338
339     // Ensure the ID is still not allowed to be edited.
340     $this->assertFalse($entity->get('id')->access('edit'));
341     $access_result = $entity->get('id')->access('edit', NULL, TRUE);
342     $this->assertTrue($access_result->isForbidden());
343     $this->assertEquals('The entity ID cannot be changed.', $access_result->getReason());
344   }
345
346   public function providerTestFieldAccess() {
347     return [
348       'serial ID entity' => [
349         EntityTest::class,
350         [
351           'name' => 'A test entity',
352           'uuid' => '60e3a179-79ed-4653-ad52-5e614c8e8fbe',
353         ],
354         FALSE,
355       ],
356       'string ID entity' => [
357         EntityTestStringId::class,
358         [
359           'id' => 'a_test_entity',
360           'name' => 'A test entity',
361           'uuid' => '60e3a179-79ed-4653-ad52-5e614c8e8fbe',
362         ],
363         TRUE,
364       ],
365     ];
366   }
367
368 }