Skip to content

Commit

Permalink
Permissions implementation (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jampire authored May 27, 2023
1 parent 0ab1a87 commit 3c368f0
Show file tree
Hide file tree
Showing 13 changed files with 176 additions and 33 deletions.
4 changes: 0 additions & 4 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@
<include>
<directory suffix=".php">./src</directory>
</include>
<exclude>
<file>./src/Rules/CanImpersonate.php</file>
<file>./src/Rules/CanBeImpersonated.php</file>
</exclude>
<report>
<clover outputFile="./build/coverage/log/coverage.xml"/>
<html outputDirectory="./build/coverage/html" lowUpperBound="35" highLowerBound="80"/>
Expand Down
13 changes: 0 additions & 13 deletions src/Enums/Permission.php

This file was deleted.

2 changes: 1 addition & 1 deletion src/Http/Concerns/WithMoonShineAuthorization.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ trait WithMoonShineAuthorization
{
public function authorize(): bool
{
return MoonShineAuth::guard()->check(); // TODO: check 'impersonate' permission as well
return MoonShineAuth::guard()->check();
}
}
14 changes: 14 additions & 0 deletions src/Services/Contracts/BeImpersonable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Jampire\MoonshineImpersonate\Services\Contracts;

/**
* @author Dzianis Kotau <me@dzianiskotau.com>
*/
interface BeImpersonable
{
/**
* True if user can be impersonated, false otherwise
*/
public function canBeImpersonated(): bool;
}
14 changes: 14 additions & 0 deletions src/Services/Contracts/Impersonable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Jampire\MoonshineImpersonate\Services\Contracts;

/**
* @author Dzianis Kotau <me@dzianiskotau.com>
*/
interface Impersonable
{
/**
* True if user can impersonate, false otherwise
*/
public function canImpersonate(): bool;
}
12 changes: 4 additions & 8 deletions src/Services/ImpersonateManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
namespace Jampire\MoonshineImpersonate\Services;

use Illuminate\Contracts\Auth\Authenticatable;
use Jampire\MoonshineImpersonate\Services\Contracts\BeImpersonable;
use Jampire\MoonshineImpersonate\Services\Contracts\Impersonable;
use Jampire\MoonshineImpersonate\Support\Settings;

/**
Expand Down Expand Up @@ -53,11 +55,9 @@ public function canEnter(Authenticatable $userToImpersonate): bool
return false;
}

// @codeCoverageIgnoreStart
if (!$this->canImpersonate()) {
return false;
}
// @codeCoverageIgnoreEnd

return $this->canBeImpersonated($userToImpersonate);
}
Expand All @@ -78,16 +78,12 @@ public function isImpersonating(): bool

public function canImpersonate(): bool
{
// TODO: implement Permission::IMPERSONATE permission

return true;
return !$this->moonshineUser instanceof Impersonable || $this->moonshineUser->canImpersonate();
}

public function canBeImpersonated(Authenticatable $userToImpersonate): bool
{
// TODO: implement which users are allowed to be impersonated

return $userToImpersonate->getAuthIdentifier() > 0;
return !$userToImpersonate instanceof BeImpersonable || $userToImpersonate->canBeImpersonated();
}

public function saveAuthInSession(Authenticatable $user): void
Expand Down
34 changes: 29 additions & 5 deletions tests/Feature/Actions/EnterActionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,22 @@
enableMoonShineGuard();
});

test('enter action validation works correctly', function (): void {
Event::fake();

$user = User::factory()->create();
$moonShineUser = MoonshineUser::factory()->create();
actingAs($moonShineUser, Settings::moonShineGuard());

$action = app(EnterAction::class);

expect($action->execute($user->getKey(), true))
->toBeTrue()
->and($action->execute($user->getKey(), true))
->toBeFalse()
;
});

it('cannot execute enter action if user does not found', function (): void {
$moonShineUser = MoonshineUser::factory()->create();
actingAs($moonShineUser, Settings::moonShineGuard());
Expand All @@ -28,18 +44,26 @@
;
});

test('enter action validation works correctly', function (): void {
Event::fake();
test('enter action cannot be executed on non-impersonated user', function (): void {
$user = User::factory()->notImpersonated()->create();
$moonShineUser = MoonshineUser::factory()->create();
actingAs($moonShineUser, Settings::moonShineGuard());

$action = app(EnterAction::class);

expect($action->execute($user->getKey(), true))
->toBeFalse()
;
});

test('enter action cannot be executed by admin with no permission', function (): void {
$user = User::factory()->create();
$moonShineUser = MoonshineUser::factory()->create();
$moonShineUser = MoonshineUser::factory()->cannotImpersonate()->create();
actingAs($moonShineUser, Settings::moonShineGuard());

$action = app(EnterAction::class);

expect($action->execute($user->getKey(), true))
->toBeTrue()
->and($action->execute($user->getKey(), true))
->toBeFalse()
;
});
36 changes: 36 additions & 0 deletions tests/Feature/Http/ImpersonateEnterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,39 @@
'id' => trans_impersonate('validation.enter.is_impersonating'),
]);
});

it('cannot impersonate non-impersonated user', function (): void {
$user = User::factory()->notImpersonated()->create();
$moonShineUser = MoonshineUser::factory()->create();

actingAs($moonShineUser, Settings::moonShineGuard());
$response = post(route_impersonate('enter'), [
'id' => $user->id,
]);

$response->assertSessionHasErrors([
'id' => trans_impersonate('validation.enter.cannot_be_impersonated'),
]);

expect(session()->get(config_impersonate('key')))
->toBeEmpty()
;
});

test('admin cannot impersonate with no permissions', function (): void {
$user = User::factory()->create();
$moonShineUser = MoonshineUser::factory()->cannotImpersonate()->create();

actingAs($moonShineUser, Settings::moonShineGuard());
$response = post(route_impersonate('enter'), [
'id' => $user->id,
]);

$response->assertSessionHasErrors([
'id' => trans_impersonate('validation.enter.cannot_impersonate'),
]);

expect(session()->get(config_impersonate('key')))
->toBeEmpty()
;
});
7 changes: 7 additions & 0 deletions tests/Stubs/Database/Factories/MoonshineUserFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,11 @@ public function definition(): array
'remember_token' => Str::random(10),
];
}

public function cannotImpersonate(): self
{
return $this->state(fn (): array => [
'moonshine_user_role_id' => 2,
]);
}
}
7 changes: 7 additions & 0 deletions tests/Stubs/Database/Factories/UserFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,11 @@ public function definition(): array
'remember_token' => Str::random(10),
];
}

public function notImpersonated(): self
{
return $this->state(fn (): array => [
'name' => fake()->name().' - Non-Impersonable',
]);
}
}
8 changes: 7 additions & 1 deletion tests/Stubs/Models/MoonshineUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Jampire\MoonshineImpersonate\Tests\Stubs\Models;

use Jampire\MoonshineImpersonate\Services\Contracts\Impersonable;
use Jampire\MoonshineImpersonate\Tests\Stubs\Database\Factories\MoonshineUserFactory;
use MoonShine\Models\MoonshineUser as BaseUser;

Expand All @@ -12,12 +13,17 @@
*
* @author Dzianis Kotau <me@dzianiskotau.com>
*/
class MoonshineUser extends BaseUser
class MoonshineUser extends BaseUser implements Impersonable
{
protected $table = 'moonshine_users';

protected static function newFactory(): MoonshineUserFactory
{
return MoonshineUserFactory::new();
}

public function canImpersonate(): bool
{
return $this->moonshine_user_role_id === 1;
}
}
9 changes: 8 additions & 1 deletion tests/Stubs/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as BaseUser;
use Illuminate\Support\Str;
use Jampire\MoonshineImpersonate\Services\Contracts\BeImpersonable;
use Jampire\MoonshineImpersonate\Tests\Stubs\Database\Factories\UserFactory;
use MoonShine\Traits\Models\HasMoonShineChangeLog;

Expand All @@ -14,7 +16,7 @@
*
* @author Dzianis Kotau <me@dzianiskotau.com>
*/
class User extends BaseUser
class User extends BaseUser implements BeImpersonable
{
use HasFactory;
use HasMoonShineChangeLog;
Expand All @@ -25,4 +27,9 @@ protected static function newFactory(): UserFactory
{
return UserFactory::new();
}

public function canBeImpersonated(): bool
{
return !Str::endsWith($this->name, 'Non-Impersonable');
}
}
49 changes: 49 additions & 0 deletions tests/Unit/PermissionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

use Jampire\MoonshineImpersonate\Services\ImpersonateManager;
use Jampire\MoonshineImpersonate\Tests\Stubs\Models\MoonshineUser;
use Jampire\MoonshineImpersonate\Tests\Stubs\Models\User;

it('can impersonate', function (): void {
$moonShineUser = MoonshineUser::factory()->create();

$manager = new ImpersonateManager($moonShineUser);

expect($manager->canImpersonate())
->toBeTrue()
;
});

it('can be impersonated', function (): void {
$user = User::factory()->create();
$moonShineUser = MoonshineUser::factory()->create();

$manager = new ImpersonateManager($moonShineUser);

expect($manager->canBeImpersonated($user))
->toBeTrue()
;
});

it('cannot impersonate with no permissions', function (): void {
$moonShineUser = MoonshineUser::factory()->cannotImpersonate()->create();

$manager = new ImpersonateManager($moonShineUser);

expect($manager->canImpersonate())
->toBeFalse()
;
});

it('cannot be impersonated with no permissions', function (): void {
$user = User::factory()->notImpersonated()->create();
$moonShineUser = MoonshineUser::factory()->create();

$manager = new ImpersonateManager($moonShineUser);

expect($manager->canBeImpersonated($user))
->toBeFalse()
;
});

0 comments on commit 3c368f0

Please sign in to comment.