Skip to content

Commit

Permalink
Merge pull request #13 from ARCANEDEV/update-guard-impersonation
Browse files Browse the repository at this point in the history
Adding the ability to impersonate different guard
  • Loading branch information
arcanedev-maroc committed Jul 25, 2020
2 parents 498a70e + 1114488 commit 30444a9
Show file tree
Hide file tree
Showing 12 changed files with 264 additions and 211 deletions.
2 changes: 1 addition & 1 deletion composer.json
Expand Up @@ -22,7 +22,7 @@
"require-dev": {
"arcanedev/laravel-policies": "^2.0",
"orchestra/testbench": "^5.0",
"phpunit/phpunit": "^8.5|^9.0"
"phpunit/phpunit": "^8.5"
},
"autoload": {
"psr-4": {
Expand Down
3 changes: 2 additions & 1 deletion config/impersonator.php
Expand Up @@ -15,7 +15,8 @@
*/

'session' => [
'key' => 'impersonator_id',
'key' => 'impersonator_id',
'guard' => 'impersonator_guard',
],

];
40 changes: 22 additions & 18 deletions src/Contracts/Impersonator.php
Expand Up @@ -22,14 +22,28 @@ interface Impersonator
*
* @return string
*/
public function getSessionKey();
public function getSessionKey(): string;

/**
* Get the session guard.
*
* @return string
*/
public function getSessionGuard(): string;

/**
* Get the impersonator id.
*
* @return int|null
*/
public function getImpersonatorId();
public function getImpersonatorId(): ?int;

/**
* Get the impersonator guard.
*
* @return string|null
*/
public function getImpersonatorGuard(): ?string;

/* -----------------------------------------------------------------
| Main Methods
Expand All @@ -41,33 +55,23 @@ public function getImpersonatorId();
*
* @param \Arcanedev\LaravelImpersonator\Contracts\Impersonatable|mixed $impersonater
* @param \Arcanedev\LaravelImpersonator\Contracts\Impersonatable|mixed $impersonated
* @param string|null $guard
*
* @return bool
*/
public function start(Impersonatable $impersonater, Impersonatable $impersonated);
public function start(Impersonatable $impersonater, Impersonatable $impersonated, $guard = null): bool;

/**
* Stop the impersonation.
*
* @return bool
*/
public function stop();
public function stop(): bool;

/**
* Clear the impersonation.
*/
public function clear();

/**
* Find a user by the given id.
*
* @param int|string $id
*
* @return \Arcanedev\LaravelImpersonator\Contracts\Impersonatable
*
* @throws \Exception
*/
public function findUserById($id);
public function clear(): void;

/* -----------------------------------------------------------------
| Check Methods
Expand All @@ -79,12 +83,12 @@ public function findUserById($id);
*
* @return bool
*/
public function isEnabled();
public function isEnabled(): bool;

/**
* Check if the impersonator is impersonating.
*
* @return bool
*/
public function isImpersonating();
public function isImpersonating(): bool;
}
3 changes: 2 additions & 1 deletion src/Exceptions/ImpersonationException.php
Expand Up @@ -5,14 +5,15 @@
namespace Arcanedev\LaravelImpersonator\Exceptions;

use Arcanedev\LaravelImpersonator\Contracts\Impersonatable;
use Exception;

/**
* Class ImpersonationException
*
* @package Arcanedev\LaravelImpersonator\Exceptions
* @author ARCANEDEV <arcanedev.maroc@gmail.com>
*/
class ImpersonationException extends \Exception
class ImpersonationException extends Exception
{
/* -----------------------------------------------------------------
| Main Methods
Expand Down
132 changes: 91 additions & 41 deletions src/Impersonator.php
Expand Up @@ -91,21 +91,42 @@ protected function events()
*
* @return string
*/
public function getSessionKey()
public function getSessionKey(): string
{
return $this->config()->get('impersonator.session.key', 'impersonator_id');
}

/**
* Get the session guard.
*
* @return string
*/
public function getSessionGuard(): string
{
return $this->config()->get('impersonator.session.guard', 'impersonator_guard');
}

/**
* Get the impersonator id.
*
* @return int|null
* @return int|null
*/
public function getImpersonatorId()
public function getImpersonatorId(): ?int
{
return $this->session()->get($this->getSessionKey(), null);
}

/**
* Get the impersonator guard.
*
* @return string|null
*/
public function getImpersonatorGuard(): ?string
{
return $this->session()->get($this->getSessionGuard(), null)
?: $this->auth()->getDefaultDriver();
}

/* -----------------------------------------------------------------
| Main Methods
| -----------------------------------------------------------------
Expand All @@ -114,22 +135,25 @@ public function getImpersonatorId()
/**
* Start the impersonation.
*
* @param \Arcanedev\LaravelImpersonator\Contracts\Impersonatable|mixed $impersonater
* @param \Arcanedev\LaravelImpersonator\Contracts\Impersonatable|mixed $impersonator
* @param \Arcanedev\LaravelImpersonator\Contracts\Impersonatable|mixed $impersonated
* @param string|null $guard
*
* @return bool
*/
public function start(Impersonatable $impersonater, Impersonatable $impersonated)
public function start(Impersonatable $impersonator, Impersonatable $impersonated, $guard = null): bool
{
$this->checkImpersonation($impersonater, $impersonated);
$this->checkImpersonation($impersonator, $impersonated);

try {
session()->put($this->getSessionKey(), $impersonater->getAuthIdentifier());
$this->auth()->silentLogout();
$this->auth()->silentLogin($impersonated);
$this->rememberImpersonater($impersonator);

$auth = $this->auth();
$auth->guard()->silentLogout();
$auth->guard($guard)->silentLogin($impersonated);

$this->events()->dispatch(
new Events\ImpersonationStarted($impersonater, $impersonated)
new Events\ImpersonationStarted($impersonator, $impersonated)
);

return true;
Expand All @@ -144,18 +168,20 @@ public function start(Impersonatable $impersonater, Impersonatable $impersonated
*
* @return bool
*/
public function stop()
public function stop(): bool
{
try {
$impersonated = $this->auth()->user();
$impersonater = $this->findUserById($this->getImpersonatorId());
$auth = $this->auth();

$impersonated = $auth->user();
$impersonator = $this->getImpersonatorFromSession();

$this->auth()->silentLogout();
$this->auth()->silentLogin($impersonater);
$auth->silentLogout();
$auth->guard($this->getImpersonatorGuard())->silentLogin($impersonator);
$this->clear();

$this->events()->dispatch(
new Events\ImpersonationStopped($impersonater, $impersonated)
new Events\ImpersonationStopped($impersonator, $impersonated)
);

return true;
Expand All @@ -168,23 +194,31 @@ public function stop()
/**
* Clear the impersonation.
*/
public function clear()
public function clear(): void
{
$this->session()->forget($this->getSessionKey());
$this->session()->forget([
$this->getSessionKey(),
$this->getSessionGuard(),
]);
}

/**
* Find a user by the given id.
* Get the impersonator from session.
*
* @param int|string $id
*
* @return \Arcanedev\LaravelImpersonator\Contracts\Impersonatable
* @return \Arcanedev\LaravelImpersonator\Contracts\Impersonatable|mixed|null
*
* @throws \Exception
*/
public function findUserById($id)
protected function getImpersonatorFromSession()
{
return call_user_func([$this->config()->get('auth.providers.users.model'), 'findOrFail'], $id);
$user = $this->auth()
->guard($this->getImpersonatorGuard())
->getProvider()
->retrieveById($this->getImpersonatorId());

abort_if(is_null($user), 404, 'User not found');

return $user;
}

/* -----------------------------------------------------------------
Expand All @@ -197,17 +231,20 @@ public function findUserById($id)
*
* @return bool
*/
public function isImpersonating()
public function isImpersonating(): bool
{
return $this->session()->has($this->getSessionKey());
return $this->session()->has([
$this->getSessionKey(),
$this->getSessionGuard(),
]);
}

/**
* Check if the impersonations is enabled.
*
* @return bool
*/
public function isEnabled()
public function isEnabled(): bool
{
return $this->config()->get('impersonator.enabled', false);
}
Expand All @@ -220,14 +257,14 @@ public function isEnabled()
/**
* Check the impersonation.
*
* @param \Arcanedev\LaravelImpersonator\Contracts\Impersonatable|mixed $impersonater
* @param \Arcanedev\LaravelImpersonator\Contracts\Impersonatable|mixed $impersonator
* @param \Arcanedev\LaravelImpersonator\Contracts\Impersonatable|mixed $impersonated
*/
private function checkImpersonation(Impersonatable $impersonater, Impersonatable $impersonated): void
private function checkImpersonation(Impersonatable $impersonator, Impersonatable $impersonated): void
{
$this->mustBeEnabled();
$this->mustBeDifferentImpersonatable($impersonater, $impersonated);
$this->checkImpersonater($impersonater);
$this->mustBeDifferentImpersonatable($impersonator, $impersonated);
$this->checkImpersonater($impersonator);
$this->checkImpersonated($impersonated);
}

Expand All @@ -245,31 +282,31 @@ private function mustBeEnabled(): void
}

/**
* Check the impersonater and the impersonated are different.
* Check the impersonator and the impersonated are different.
*
* @param \Arcanedev\LaravelImpersonator\Contracts\Impersonatable|mixed $impersonater
* @param \Arcanedev\LaravelImpersonator\Contracts\Impersonatable|mixed $impersonator
* @param \Arcanedev\LaravelImpersonator\Contracts\Impersonatable|mixed $impersonated
*
* @throws \Arcanedev\LaravelImpersonator\Exceptions\ImpersonationException
*/
private function mustBeDifferentImpersonatable(Impersonatable $impersonater, Impersonatable $impersonated): void
private function mustBeDifferentImpersonatable(Impersonatable $impersonator, Impersonatable $impersonated): void
{
if ($impersonater->isSamePerson($impersonated)) {
throw Exceptions\ImpersonationException::impersonaterAndImpersonatedAreSame();
if ($impersonator->isSamePerson($impersonated)) {
throw Exceptions\ImpersonationException::selfImpersonation();
}
}

/**
* Check the impersonater.
* Check the impersonator.
*
* @param \Arcanedev\LaravelImpersonator\Contracts\Impersonatable|mixed $impersonater
* @param \Arcanedev\LaravelImpersonator\Contracts\Impersonatable|mixed $impersonator
*
* @throws \Arcanedev\LaravelImpersonator\Exceptions\ImpersonationException
*/
private function checkImpersonater(Impersonatable $impersonater): void
private function checkImpersonater(Impersonatable $impersonator): void
{
if ( ! $impersonater->canImpersonate())
throw ImpersonationException::cannotImpersonate($impersonater);
if ( ! $impersonator->canImpersonate())
throw ImpersonationException::cannotImpersonate($impersonator);
}

/**
Expand All @@ -284,4 +321,17 @@ private function checkImpersonated(Impersonatable $impersonated): void
if ( ! $impersonated->canBeImpersonated())
throw ImpersonationException::cannotBeImpersonated($impersonated);
}

/**
* Remember the impersonator.
*
* @param \Arcanedev\LaravelImpersonator\Contracts\Impersonatable $impersonator
*/
private function rememberImpersonater(Impersonatable $impersonator)
{
$this->session()->put([
$this->getSessionKey() => $impersonator->getAuthIdentifier(),
$this->getSessionGuard() => $this->auth()->getDefaultDriver(),
]);
}
}

0 comments on commit 30444a9

Please sign in to comment.