Skip to content

Commit

Permalink
Allow to use backed enumerations as permission name in `CurrentUser::…
Browse files Browse the repository at this point in the history
…can()` method (#97)

* Allow to use backed enumerations as permission name in `CurrentUser::can()` method

* Apply fixes from StyleCI

---------

Co-authored-by: StyleCI Bot <bot@styleci.io>
  • Loading branch information
vjik and StyleCIBot committed Apr 14, 2024
1 parent 5ebd946 commit 701d62c
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 25 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 2.2.0 under development

- Chg #96: Raise minimum PHP version to 8.1 (@vjik)
- Enh #93: Allow to use backed enumerations as permission name in `CurrentUser::can()` method (@vjik)

## 2.1.0 December 05, 2023

Expand Down
10 changes: 8 additions & 2 deletions src/CurrentUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Yiisoft\User;

use BackedEnum;
use Psr\EventDispatcher\EventDispatcherInterface;
use Yiisoft\Access\AccessCheckerInterface;
use Yiisoft\Auth\IdentityInterface;
Expand Down Expand Up @@ -149,18 +150,23 @@ public function isGuest(): bool
* Note that you must provide access checker via {@see withAccessChecker()} in order to use this
* method. Otherwise, it will always return `false`.
*
* @param string $permissionName The name of the permission (e.g. "edit post") that needs access check.
* @param BackedEnum|string $permissionName The name of the permission (e.g. "edit post") that needs access check.
* You can use backed enumerations as permission name, in this case the value of the enumeration will be used.
* @param array $params Name-value pairs that would be passed to the rules associated with the roles and
* permissions assigned to the user.
*
* @return bool Whether the user can perform the operation as specified by the given permission.
*/
public function can(string $permissionName, array $params = []): bool
public function can(string|BackedEnum $permissionName, array $params = []): bool
{
if ($this->accessChecker === null) {
return false;
}

if ($permissionName instanceof BackedEnum) {
$permissionName = (string) $permissionName->value;
}

return $this->accessChecker->userHasPermission($this->getId(), $permissionName, $params);
}

Expand Down
44 changes: 24 additions & 20 deletions tests/CurrentUserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@

namespace Yiisoft\User\Tests;

use BackedEnum;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Yiisoft\Access\AccessCheckerInterface;
use Yiisoft\Auth\IdentityInterface;
use Yiisoft\Auth\IdentityRepositoryInterface;
use Yiisoft\Test\Support\EventDispatcher\SimpleEventDispatcher;
Expand All @@ -14,7 +17,8 @@
use Yiisoft\User\Event\BeforeLogin;
use Yiisoft\User\Event\BeforeLogout;
use Yiisoft\User\Guest\GuestIdentity;
use Yiisoft\User\Tests\Support\MockAccessChecker;
use Yiisoft\User\Tests\Support\AccessCheckerStub;
use Yiisoft\User\Tests\Support\Permission;
use Yiisoft\User\Tests\Support\MockArraySessionStorage;
use Yiisoft\User\Tests\Support\MockIdentity;
use Yiisoft\User\Tests\Support\MockIdentityRepository;
Expand Down Expand Up @@ -368,31 +372,36 @@ public function testLogoutWithSetAuthExpire(): void
$this->assertInstanceOf(GuestIdentity::class, $currentUser->getIdentity());
}

public function testCanWithoutAccessChecker(): void
public static function dataCan(): iterable
{
$currentUser = (new CurrentUser($this->createIdentityRepository(), $this->createEventDispatcher()))
->withSession($this->createSession())
;

$this->assertFalse($currentUser->can('permission'));
$accessChecker = new AccessCheckerStub(['edit']);
yield 'without access checker' => [false, 'edit', null];
yield 'string false' => [false, 'delete', $accessChecker];
yield 'string true' => [true, 'edit', $accessChecker];
yield 'enum false' => [false, Permission::DELETE, $accessChecker];
yield 'enum true' => [true, Permission::EDIT, $accessChecker];
}

public function testCanWithAccessChecker(): void
{
$currentUser = (new CurrentUser($this->createIdentityRepository(), $this->createEventDispatcher()))
->withAccessChecker($this->createAccessChecker(true))
->withSession($this->createSession())
;
#[DataProvider('dataCan')]
public function testCan(
bool $expected,
string|BackedEnum $permissionName,
?AccessCheckerInterface $accessChecker
): void {
$currentUser = new CurrentUser($this->createIdentityRepository(), $this->createEventDispatcher());
if ($accessChecker !== null) {
$currentUser = $currentUser->withAccessChecker($accessChecker);
}

$this->assertTrue($currentUser->can('permission'));
$this->assertSame($expected, $currentUser->can($permissionName));
}

public function testImmutable(): void
{
$currentUser = new CurrentUser($this->createIdentityRepository(), $this->createEventDispatcher());

$this->assertNotSame($currentUser, $currentUser->withSession($this->createSession()));
$this->assertNotSame($currentUser, $currentUser->withAccessChecker($this->createAccessChecker()));
$this->assertNotSame($currentUser, $currentUser->withAccessChecker(new AccessCheckerStub()));
$this->assertNotSame($currentUser, $currentUser->withAbsoluteAuthTimeout(3600));
$this->assertNotSame($currentUser, $currentUser->withAuthTimeout(60));
}
Expand All @@ -407,11 +416,6 @@ private function createSession(array $data = []): MockArraySessionStorage
return new MockArraySessionStorage($data);
}

private function createAccessChecker(bool $userHasPermission = false): MockAccessChecker
{
return new MockAccessChecker($userHasPermission);
}

private function createIdentityRepository(?IdentityInterface $identity = null): IdentityRepositoryInterface
{
return new MockIdentityRepository($identity);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@

use Yiisoft\Access\AccessCheckerInterface;

final class MockAccessChecker implements AccessCheckerInterface
final class AccessCheckerStub implements AccessCheckerInterface
{
public function __construct(private bool $userHasPermission)
public function __construct(private array $allowPermissions = [])
{
}

public function userHasPermission($userId, string $permissionName, array $parameters = []): bool
{
return $this->userHasPermission;
return in_array($permissionName, $this->allowPermissions);
}
}
11 changes: 11 additions & 0 deletions tests/Support/Permission.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace Yiisoft\User\Tests\Support;

enum Permission: string
{
case EDIT = 'edit';
case DELETE = 'delete';
}

0 comments on commit 701d62c

Please sign in to comment.