Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/Actions/Impersonate.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ public function visibleTo($item)
return false;
}

return $item instanceof UserContract && $item->id() != User::current()->id();
if (! ($item instanceof UserContract && $item->id() != User::current()->id())) {
return false;
}

return User::current()->can('impersonate', $item);
}

public function visibleToBulk($items)
Expand All @@ -34,7 +38,7 @@ public function visibleToBulk($items)

public function authorize($authed, $user)
{
return $authed->can('impersonate users');
return $authed->can('impersonate', $user);
}

public function run($users, $values)
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/UserPolicy.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,11 @@ public function sendPasswordReset($authed, $user)
{
return $this->edit($authed, $user);
}

public function impersonate($authed, $user)
{
$authed = User::fromUser($authed);

return $authed->hasPermission('impersonate users');
}
}
88 changes: 88 additions & 0 deletions tests/Actions/ImpersonateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,22 @@

namespace Tests\Actions;

use Illuminate\Support\Facades\Gate;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\Test;
use Statamic\Actions\Impersonate as Action;
use Statamic\Facades\User;
use Statamic\Policies\UserPolicy;
use Tests\ElevatesSessions;
use Tests\FakesRoles;
use Tests\PreventSavingStacheItemsToDisk;
use Tests\TestCase;

#[Group('elevated-session')]
class ImpersonateTest extends TestCase
{
use ElevatesSessions;
use FakesRoles;
use PreventSavingStacheItemsToDisk;

private function impersonate($user)
Expand Down Expand Up @@ -41,4 +46,87 @@ public function it_authenticates_as_another_user_and_clears_elevated_session()
$this->assertEquals($impersonated->id(), auth()->id());
$this->assertFalse(request()->hasElevatedSession());
}

#[Test]
public function it_is_visible_to_a_valid_target_user()
{
$impersonator = tap(User::make()->email('admin@example.com')->makeSuper())->save();
$impersonated = tap(User::make()->email('user@example.com'))->save();

$this->actingAs($impersonator);

$this->assertTrue((new Action)->visibleTo($impersonated));
}

#[Test]
public function it_is_not_visible_when_policy_denies_impersonation()
{
$this->setTestRoles(['impersonator' => ['impersonate users']]);

$impersonator = tap(User::make()->email('admin@example.com')->assignRole('impersonator'))->save();
$impersonated = tap(User::make()->email('user@example.com'))->save();

Gate::policy(get_class($impersonated), DenyImpersonationPolicy::class);

$this->actingAs($impersonator);

$this->assertFalse((new Action)->visibleTo($impersonated));
}

#[Test]
public function it_is_authorized_with_the_default_policy()
{
$this->setTestRoles(['impersonator' => ['impersonate users']]);

$impersonator = tap(User::make()->email('admin@example.com')->assignRole('impersonator'))->save();
$impersonated = tap(User::make()->email('user@example.com'))->save();

$this->assertTrue((new Action)->authorize($impersonator, $impersonated));
}

#[Test]
public function it_is_not_authorized_when_policy_denies_impersonation()
{
$this->setTestRoles(['impersonator' => ['impersonate users']]);

$impersonator = tap(User::make()->email('admin@example.com')->assignRole('impersonator'))->save();
$impersonated = tap(User::make()->email('user@example.com'))->save();

Gate::policy(get_class($impersonated), DenyImpersonationPolicy::class);

$this->assertFalse((new Action)->authorize($impersonator, $impersonated));
}

#[Test]
public function it_is_not_authorized_without_permission()
{
$this->setTestRoles(['editor' => ['edit users']]);

$impersonator = tap(User::make()->email('admin@example.com')->assignRole('editor'))->save();
$impersonated = tap(User::make()->email('user@example.com'))->save();

$this->assertFalse((new Action)->authorize($impersonator, $impersonated));
}

#[Test]
public function super_users_bypass_the_policy_check()
{
$impersonator = tap(User::make()->email('admin@example.com')->makeSuper())->save();
$impersonated = tap(User::make()->email('user@example.com'))->save();

Gate::policy(get_class($impersonated), DenyImpersonationPolicy::class);

$this->actingAs($impersonator);

$this->assertTrue((new Action)->visibleTo($impersonated));
$this->assertTrue((new Action)->authorize($impersonator, $impersonated));
}
}

class DenyImpersonationPolicy extends UserPolicy
{
public function impersonate($authed, $user)
{
return false;
}
}
Loading