UserAccessControlHandlerTest.php 11.3 KB
Newer Older
1 2 3 4 5
<?php

namespace Drupal\Tests\user\Unit;

use Drupal\Core\Access\AccessResult;
6 7
use Drupal\Core\Cache\Context\CacheContextsManager;
use Drupal\Core\DependencyInjection\Container;
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
use Drupal\Tests\UnitTestCase;
use Drupal\user\UserAccessControlHandler;

/**
 * Tests the user access controller.
 *
 * @group Drupal
 * @group User
 *
 * @coversDefaultClass \Drupal\user\UserAccessControlHandler
 */
class UserAccessControlHandlerTest extends UnitTestCase {

  /**
   * The user access controller to test.
   *
   * @var \Drupal\user\UserAccessControlHandler
   */
  protected $accessControlHandler;

  /**
   * The mock user account with view access.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  protected $viewer;

35 36 37 38 39 40 41
  /**
   * The mock user account with 'access user mail' permission.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  protected $emailViewer;

42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
  /**
   * The mock user account that is able to change their own account name.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  protected $owner;

  /**
   * The mock administrative test user.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  protected $admin;

  /**
   * The mocked test field items.
   *
   * @var \Drupal\Core\Field\FieldItemList
   */
  protected $items;

  /**
   * {@inheritdoc}
   */
66
  protected function setUp(): void {
67
    parent::setUp();
68

69 70 71
    $cache_contexts_manager = $this->prophesize(CacheContextsManager::class);
    $cache_contexts_manager->assertValidTokens()->willReturn(TRUE);
    $cache_contexts_manager->reveal();
72 73 74 75
    $container = new Container();
    $container->set('cache_contexts_manager', $cache_contexts_manager);
    \Drupal::setContainer($container);

76
    $this->viewer = $this->createMock('\Drupal\Core\Session\AccountInterface');
77 78 79 80 81 82 83 84 85
    $this->viewer
      ->expects($this->any())
      ->method('hasPermission')
      ->will($this->returnValue(FALSE));
    $this->viewer
      ->expects($this->any())
      ->method('id')
      ->will($this->returnValue(1));

86
    $this->owner = $this->createMock('\Drupal\Core\Session\AccountInterface');
87 88 89
    $this->owner
      ->expects($this->any())
      ->method('hasPermission')
90 91 92 93
      ->will($this->returnValueMap([
        ['administer users', FALSE],
        ['change own username', TRUE],
      ]));
94 95 96 97 98 99

    $this->owner
      ->expects($this->any())
      ->method('id')
      ->will($this->returnValue(2));

100
    $this->admin = $this->createMock('\Drupal\Core\Session\AccountInterface');
101 102 103 104 105
    $this->admin
      ->expects($this->any())
      ->method('hasPermission')
      ->will($this->returnValue(TRUE));

106 107 108 109 110 111 112 113 114 115 116 117
    $this->emailViewer = $this->createMock('\Drupal\Core\Session\AccountInterface');
    $this->emailViewer
      ->expects($this->any())
      ->method('hasPermission')
      ->will($this->returnValueMap([
        ['access user mail', TRUE],
      ]));
    $this->emailViewer
      ->expects($this->any())
      ->method('id')
      ->will($this->returnValue(3));

118
    $entity_type = $this->createMock('Drupal\Core\Entity\EntityTypeInterface');
119 120

    $this->accessControlHandler = new UserAccessControlHandler($entity_type);
121
    $module_handler = $this->createMock('Drupal\Core\Extension\ModuleHandlerInterface');
122 123
    $module_handler->expects($this->any())
      ->method('getImplementations')
124
      ->will($this->returnValue([]));
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
    $this->accessControlHandler->setModuleHandler($module_handler);

    $this->items = $this->getMockBuilder('Drupal\Core\Field\FieldItemList')
      ->disableOriginalConstructor()
      ->getMock();
    $this->items
      ->expects($this->any())
      ->method('defaultAccess')
      ->will($this->returnValue(AccessResult::allowed()));
  }

  /**
   * Asserts correct field access grants for a field.
   */
  public function assertFieldAccess($field, $viewer, $target, $view, $edit) {
140
    $field_definition = $this->createMock('Drupal\Core\Field\FieldDefinitionInterface');
141 142 143 144 145 146 147 148 149
    $field_definition->expects($this->any())
      ->method('getName')
      ->will($this->returnValue($field));

    $this->items
      ->expects($this->any())
      ->method('getEntity')
      ->will($this->returnValue($this->{$target}));

150
    foreach (['view' => $view, 'edit' => $edit] as $operation => $result) {
151 152
      $result_text = !isset($result) ? 'null' : ($result ? 'true' : 'false');
      $message = "User '$field' field access returns '$result_text' with operation '$operation' for '$viewer' accessing '$target'";
153 154 155 156 157 158 159 160 161 162 163 164 165 166
      $this->assertSame($result, $this->accessControlHandler->fieldAccess($operation, $field_definition, $this->{$viewer}, $this->items), $message);
    }
  }

  /**
   * Ensures user name access is working properly.
   *
   * @dataProvider userNameProvider
   */
  public function testUserNameAccess($viewer, $target, $view, $edit) {
    $this->assertFieldAccess('name', $viewer, $target, $view, $edit);
  }

  /**
167
   * Provides test data for testUserNameAccess().
168 169
   */
  public function userNameProvider() {
170
    $name_access = [
171
      // The viewer user is allowed to see user names on all accounts.
172
      [
173 174 175 176
        'viewer' => 'viewer',
        'target' => 'viewer',
        'view' => TRUE,
        'edit' => FALSE,
177 178
      ],
      [
179 180 181 182
        'viewer' => 'owner',
        'target' => 'viewer',
        'view' => TRUE,
        'edit' => FALSE,
183 184
      ],
      [
185 186 187 188
        'viewer' => 'viewer',
        'target' => 'owner',
        'view' => TRUE,
        'edit' => FALSE,
189
      ],
190
      // The owner user is allowed to change its own user name.
191
      [
192 193 194 195
        'viewer' => 'owner',
        'target' => 'owner',
        'view' => TRUE,
        'edit' => TRUE,
196
      ],
197
      // The users-administrator user has full access.
198
      [
199 200 201 202
        'viewer' => 'admin',
        'target' => 'owner',
        'view' => TRUE,
        'edit' => TRUE,
203 204
      ],
    ];
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
    return $name_access;
  }

  /**
   * Tests that private user settings cannot be viewed by other users.
   *
   * @dataProvider hiddenUserSettingsProvider
   */
  public function testHiddenUserSettings($field, $viewer, $target, $view, $edit) {
    $this->assertFieldAccess($field, $viewer, $target, $view, $edit);
  }

  /**
   * Provides test data for testHiddenUserSettings().
   */
  public function hiddenUserSettingsProvider() {
221
    $access_info = [];
222

223
    $fields = [
224 225 226 227
      'preferred_langcode',
      'preferred_admin_langcode',
      'timezone',
      'mail',
228
    ];
229 230

    foreach ($fields as $field) {
231
      $access_info[] = [
232 233 234 235 236
        'field' => $field,
        'viewer' => 'viewer',
        'target' => 'viewer',
        'view' => TRUE,
        'edit' => TRUE,
237 238
      ];
      $access_info[] = [
239 240 241 242 243 244 245 246
        'field' => $field,
        'viewer' => 'viewer',
        'target' => 'owner',
        'view' => FALSE,
        // Anyone with edit access to the user can also edit these fields. In
        // reality edit access will already be checked on entity level and the
        // user without view access will typically not be able to edit.
        'edit' => TRUE,
247 248
      ];
      $access_info[] = [
249 250 251 252 253
        'field' => $field,
        'viewer' => 'owner',
        'target' => 'owner',
        'view' => TRUE,
        'edit' => TRUE,
254 255
      ];
      $access_info[] = [
256 257 258 259 260
        'field' => $field,
        'viewer' => 'admin',
        'target' => 'owner',
        'view' => TRUE,
        'edit' => TRUE,
261
      ];
262 263 264 265 266 267 268 269
      $access_info[] = [
        'field' => $field,
        'viewer' => 'emailViewer',
        'target' => 'owner',
        'view' => $field === 'mail',
        // See note above.
        'edit' => TRUE,
      ];
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
    }

    return $access_info;
  }

  /**
   * Tests that private user settings cannot be viewed by other users.
   *
   * @dataProvider adminFieldAccessProvider
   */
  public function testAdminFieldAccess($field, $viewer, $target, $view, $edit) {
    $this->assertFieldAccess($field, $viewer, $target, $view, $edit);
  }

  /**
   * Provides test data for testAdminFieldAccess().
   */
  public function adminFieldAccessProvider() {
288
    $access_info = [];
289

290
    $fields = [
291 292 293 294 295
      'roles',
      'status',
      'access',
      'login',
      'init',
296
    ];
297 298

    foreach ($fields as $field) {
299
      $access_info[] = [
300 301 302 303 304
        'field' => $field,
        'viewer' => 'viewer',
        'target' => 'viewer',
        'view' => FALSE,
        'edit' => FALSE,
305 306
      ];
      $access_info[] = [
307 308 309 310 311
        'field' => $field,
        'viewer' => 'viewer',
        'target' => 'owner',
        'view' => FALSE,
        'edit' => FALSE,
312 313
      ];
      $access_info[] = [
314 315 316 317 318
        'field' => $field,
        'viewer' => 'admin',
        'target' => 'owner',
        'view' => TRUE,
        'edit' => TRUE,
319
      ];
320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
    }

    return $access_info;
  }

  /**
   * Tests that passwords cannot be viewed, just edited.
   *
   * @dataProvider passwordAccessProvider
   */
  public function testPasswordAccess($viewer, $target, $view, $edit) {
    $this->assertFieldAccess('pass', $viewer, $target, $view, $edit);
  }

  /**
   * Provides test data for passwordAccessProvider().
   */
  public function passwordAccessProvider() {
338 339
    $pass_access = [
      [
340 341 342 343
        'viewer' => 'viewer',
        'target' => 'viewer',
        'view' => FALSE,
        'edit' => TRUE,
344 345
      ],
      [
346 347 348 349 350 351 352
        'viewer' => 'viewer',
        'target' => 'owner',
        'view' => FALSE,
        // Anyone with edit access to the user can also edit these fields. In
        // reality edit access will already be checked on entity level and the
        // user without view access will typically not be able to edit.
        'edit' => TRUE,
353 354
      ],
      [
355 356 357 358
        'viewer' => 'owner',
        'target' => 'viewer',
        'view' => FALSE,
        'edit' => TRUE,
359 360
      ],
      [
361 362
        'viewer' => 'admin',
        'target' => 'owner',
363
        'view' => FALSE,
364
        'edit' => TRUE,
365 366
      ],
    ];
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382
    return $pass_access;
  }

  /**
   * Tests the user created field access.
   *
   * @dataProvider createdAccessProvider
   */
  public function testCreatedAccess($viewer, $target, $view, $edit) {
    $this->assertFieldAccess('created', $viewer, $target, $view, $edit);
  }

  /**
   * Provides test data for testCreatedAccess().
   */
  public function createdAccessProvider() {
383 384
    $created_access = [
      [
385 386 387 388
        'viewer' => 'viewer',
        'target' => 'viewer',
        'view' => TRUE,
        'edit' => FALSE,
389 390
      ],
      [
391 392 393 394
        'viewer' => 'owner',
        'target' => 'viewer',
        'view' => TRUE,
        'edit' => FALSE,
395 396
      ],
      [
397 398 399 400
        'viewer' => 'admin',
        'target' => 'owner',
        'view' => TRUE,
        'edit' => TRUE,
401 402
      ],
    ];
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
    return $created_access;
  }

  /**
   * Tests access to a non-existing base field.
   *
   * @dataProvider NonExistingFieldAccessProvider
   */
  public function testNonExistingFieldAccess($viewer, $target, $view, $edit) {
    // By default everyone has access to all fields that do not have explicit
    // access control.
    // @see EntityAccessControlHandler::checkFieldAccess()
    $this->assertFieldAccess('some_non_existing_field', $viewer, $target, $view, $edit);
  }

  /**
   * Provides test data for testNonExistingFieldAccess().
   */
  public function NonExistingFieldAccessProvider() {
422 423
    $created_access = [
      [
424 425 426 427
        'viewer' => 'viewer',
        'target' => 'viewer',
        'view' => TRUE,
        'edit' => TRUE,
428 429
      ],
      [
430 431 432 433
        'viewer' => 'owner',
        'target' => 'viewer',
        'view' => TRUE,
        'edit' => TRUE,
434 435
      ],
      [
436 437 438 439
        'viewer' => 'admin',
        'target' => 'owner',
        'view' => TRUE,
        'edit' => TRUE,
440 441
      ],
    ];
442 443 444 445
    return $created_access;
  }

}