UserAccessControlHandlerTest.php

Same filename in other branches
  1. 9 core/modules/user/tests/src/Unit/UserAccessControlHandlerTest.php
  2. 8.9.x core/modules/user/tests/src/Unit/UserAccessControlHandlerTest.php
  3. 11.x core/modules/user/tests/src/Unit/UserAccessControlHandlerTest.php

Namespace

Drupal\Tests\user\Unit

File

core/modules/user/tests/src/Unit/UserAccessControlHandlerTest.php

View source
<?php

declare (strict_types=1);
namespace Drupal\Tests\user\Unit;

use Drupal\Core\Access\AccessResult;
use Drupal\Core\Cache\Context\CacheContextsManager;
use Drupal\Core\DependencyInjection\Container;
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\user\UserInterface
     */
    protected $viewer;
    
    /**
     * The mock user account with 'view user email addresses' permission.
     *
     * @var \Drupal\user\UserInterface
     */
    protected $emailViewer;
    
    /**
     * The mock user account that is able to change their own account name.
     *
     * @var \Drupal\user\UserInterface
     */
    protected $owner;
    
    /**
     * The mock administrative test user.
     *
     * @var \Drupal\user\UserInterface
     */
    protected $admin;
    
    /**
     * The mocked test field items.
     *
     * @var \Drupal\Core\Field\FieldItemList
     */
    protected $items;
    
    /**
     * {@inheritdoc}
     */
    protected function setUp() : void {
        parent::setUp();
        $cache_contexts_manager = $this->prophesize(CacheContextsManager::class);
        $cache_contexts_manager->assertValidTokens()
            ->willReturn(TRUE);
        $cache_contexts_manager->reveal();
        $container = new Container();
        $container->set('cache_contexts_manager', $cache_contexts_manager);
        \Drupal::setContainer($container);
        $this->viewer = $this->createMock('\\Drupal\\user\\UserInterface');
        $this->viewer
            ->expects($this->any())
            ->method('hasPermission')
            ->willReturn(FALSE);
        $this->viewer
            ->expects($this->any())
            ->method('id')
            ->willReturn(1);
        $this->owner = $this->createMock('\\Drupal\\user\\UserInterface');
        $this->owner
            ->expects($this->any())
            ->method('hasPermission')
            ->willReturnMap([
            [
                'administer users',
                FALSE,
            ],
            [
                'change own username',
                TRUE,
            ],
        ]);
        $this->owner
            ->expects($this->any())
            ->method('id')
            ->willReturn(2);
        $this->admin = $this->createMock('\\Drupal\\user\\UserInterface');
        $this->admin
            ->expects($this->any())
            ->method('hasPermission')
            ->willReturn(TRUE);
        $this->emailViewer = $this->createMock('\\Drupal\\user\\UserInterface');
        $this->emailViewer
            ->expects($this->any())
            ->method('hasPermission')
            ->willReturnMap([
            [
                'view user email addresses',
                TRUE,
            ],
        ]);
        $this->emailViewer
            ->expects($this->any())
            ->method('id')
            ->willReturn(3);
        $entity_type = $this->createMock('Drupal\\Core\\Entity\\EntityTypeInterface');
        $this->accessControlHandler = new UserAccessControlHandler($entity_type);
        $module_handler = $this->createMock('Drupal\\Core\\Extension\\ModuleHandlerInterface');
        $this->accessControlHandler
            ->setModuleHandler($module_handler);
        $this->items = $this->getMockBuilder('Drupal\\Core\\Field\\FieldItemList')
            ->disableOriginalConstructor()
            ->getMock();
        $this->items
            ->expects($this->any())
            ->method('defaultAccess')
            ->willReturn(AccessResult::allowed());
    }
    
    /**
     * Asserts correct field access grants for a field.
     *
     * @internal
     */
    public function assertFieldAccess(string $field, string $viewer, string $target, bool $view, bool $edit) : void {
        $field_definition = $this->createMock('Drupal\\Core\\Field\\FieldDefinitionInterface');
        $field_definition->expects($this->any())
            ->method('getName')
            ->willReturn($field);
        $this->items
            ->expects($this->any())
            ->method('getEntity')
            ->willReturn($this->{$target});
        foreach ([
            'view' => $view,
            'edit' => $edit,
        ] as $operation => $result) {
            $result_text = !isset($result) ? 'null' : ($result ? 'true' : 'false');
            $message = "User '{$field}' field access returns '{$result_text}' with operation '{$operation}' for '{$viewer}' accessing '{$target}'";
            $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) : void {
        $this->assertFieldAccess('name', $viewer, $target, $view, $edit);
    }
    
    /**
     * Provides test data for testUserNameAccess().
     */
    public static function userNameProvider() {
        $name_access = [
            // The viewer user is allowed to see user names on all accounts.
[
                'viewer' => 'viewer',
                'target' => 'viewer',
                'view' => TRUE,
                'edit' => FALSE,
            ],
            [
                'viewer' => 'owner',
                'target' => 'viewer',
                'view' => TRUE,
                'edit' => FALSE,
            ],
            [
                'viewer' => 'viewer',
                'target' => 'owner',
                'view' => TRUE,
                'edit' => FALSE,
            ],
            // The owner user is allowed to change its own user name.
[
                'viewer' => 'owner',
                'target' => 'owner',
                'view' => TRUE,
                'edit' => TRUE,
            ],
            // The users-administrator user has full access.
[
                'viewer' => 'admin',
                'target' => 'owner',
                'view' => TRUE,
                'edit' => TRUE,
            ],
        ];
        return $name_access;
    }
    
    /**
     * Tests that private user settings cannot be viewed by other users.
     *
     * @dataProvider hiddenUserSettingsProvider
     */
    public function testHiddenUserSettings($field, $viewer, $target, $view, $edit) : void {
        $this->assertFieldAccess($field, $viewer, $target, $view, $edit);
    }
    
    /**
     * Provides test data for testHiddenUserSettings().
     */
    public static function hiddenUserSettingsProvider() {
        $access_info = [];
        $fields = [
            'preferred_langcode',
            'preferred_admin_langcode',
            'timezone',
            'mail',
        ];
        foreach ($fields as $field) {
            $access_info[] = [
                'field' => $field,
                'viewer' => 'viewer',
                'target' => 'viewer',
                'view' => TRUE,
                'edit' => TRUE,
            ];
            $access_info[] = [
                '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,
            ];
            $access_info[] = [
                'field' => $field,
                'viewer' => 'owner',
                'target' => 'owner',
                'view' => TRUE,
                'edit' => TRUE,
            ];
            $access_info[] = [
                'field' => $field,
                'viewer' => 'admin',
                'target' => 'owner',
                'view' => TRUE,
                'edit' => TRUE,
            ];
            $access_info[] = [
                'field' => $field,
                'viewer' => 'emailViewer',
                'target' => 'owner',
                'view' => $field === 'mail',
                // See note above.
'edit' => TRUE,
            ];
        }
        return $access_info;
    }
    
    /**
     * Tests that private user settings cannot be viewed by other users.
     *
     * @dataProvider adminFieldAccessProvider
     */
    public function testAdminFieldAccess($field, $viewer, $target, $view, $edit) : void {
        $this->assertFieldAccess($field, $viewer, $target, $view, $edit);
    }
    
    /**
     * Provides test data for testAdminFieldAccess().
     */
    public static function adminFieldAccessProvider() {
        $access_info = [];
        $fields = [
            'roles',
            'status',
            'access',
            'login',
            'init',
        ];
        foreach ($fields as $field) {
            $access_info[] = [
                'field' => $field,
                'viewer' => 'viewer',
                'target' => 'viewer',
                'view' => FALSE,
                'edit' => FALSE,
            ];
            $access_info[] = [
                'field' => $field,
                'viewer' => 'viewer',
                'target' => 'owner',
                'view' => FALSE,
                'edit' => FALSE,
            ];
            $access_info[] = [
                'field' => $field,
                'viewer' => 'admin',
                'target' => 'owner',
                'view' => TRUE,
                'edit' => TRUE,
            ];
        }
        return $access_info;
    }
    
    /**
     * Tests that passwords cannot be viewed, just edited.
     *
     * @dataProvider passwordAccessProvider
     */
    public function testPasswordAccess($viewer, $target, $view, $edit) : void {
        $this->assertFieldAccess('pass', $viewer, $target, $view, $edit);
    }
    
    /**
     * Provides test data for passwordAccessProvider().
     */
    public static function passwordAccessProvider() {
        $pass_access = [
            [
                'viewer' => 'viewer',
                'target' => 'viewer',
                'view' => FALSE,
                'edit' => TRUE,
            ],
            [
                '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,
            ],
            [
                'viewer' => 'owner',
                'target' => 'viewer',
                'view' => FALSE,
                'edit' => TRUE,
            ],
            [
                'viewer' => 'admin',
                'target' => 'owner',
                'view' => FALSE,
                'edit' => TRUE,
            ],
        ];
        return $pass_access;
    }
    
    /**
     * Tests the user created field access.
     *
     * @dataProvider createdAccessProvider
     */
    public function testCreatedAccess($viewer, $target, $view, $edit) : void {
        $this->assertFieldAccess('created', $viewer, $target, $view, $edit);
    }
    
    /**
     * Provides test data for testCreatedAccess().
     */
    public static function createdAccessProvider() {
        $created_access = [
            [
                'viewer' => 'viewer',
                'target' => 'viewer',
                'view' => TRUE,
                'edit' => FALSE,
            ],
            [
                'viewer' => 'owner',
                'target' => 'viewer',
                'view' => TRUE,
                'edit' => FALSE,
            ],
            [
                'viewer' => 'admin',
                'target' => 'owner',
                'view' => TRUE,
                'edit' => TRUE,
            ],
        ];
        return $created_access;
    }
    
    /**
     * Tests access to a non-existing base field.
     *
     * @dataProvider NonExistingFieldAccessProvider
     */
    public function testNonExistingFieldAccess($viewer, $target, $view, $edit) : void {
        // 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 static function NonExistingFieldAccessProvider() {
        $created_access = [
            [
                'viewer' => 'viewer',
                'target' => 'viewer',
                'view' => TRUE,
                'edit' => TRUE,
            ],
            [
                'viewer' => 'owner',
                'target' => 'viewer',
                'view' => TRUE,
                'edit' => TRUE,
            ],
            [
                'viewer' => 'admin',
                'target' => 'owner',
                'view' => TRUE,
                'edit' => TRUE,
            ],
        ];
        return $created_access;
    }

}

Classes

Title Deprecated Summary
UserAccessControlHandlerTest Tests the user access controller.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.