diff --git a/app/config/routes.yml b/app/config/routes.yml index 8974a32..2e905c6 100644 --- a/app/config/routes.yml +++ b/app/config/routes.yml @@ -73,6 +73,9 @@ router: backend-user-edit: pattern: '/admin/user/{id:([0-9]+)}' path: backend::user::edit + backend-user-impersonate: + pattern: '/admin/impersonate/{id:([0-9]+)}' + path: backend::user::impersonate backend-user-activation-email: pattern: '/admin/user/{id:([0-9]+)}/activation' path: backend::user::activation-email diff --git a/app/controllers/backend/UserController.php b/app/controllers/backend/UserController.php index 8737728..bad595e 100644 --- a/app/controllers/backend/UserController.php +++ b/app/controllers/backend/UserController.php @@ -113,4 +113,17 @@ class UserController extends \Phalcon\Mvc\Controller } $this->response->redirect('/admin'); } + + public function impersonateAction($id) + { + $user = User::findFirstById($id); + + try { + $this->auth->impersonate($user); + $this->response->redirect('/'); + } catch (\Exception $ex) { + $this->flash->error($ex->getMessage()); + $this->response->redirect('/admin'); + } + } } diff --git a/app/library/Auth.php b/app/library/Auth.php index 81750da..80ea7bd 100644 --- a/app/library/Auth.php +++ b/app/library/Auth.php @@ -10,6 +10,7 @@ use App\Model\Data\User, class Auth extends Injectable { const SESSION_KEY = 'auth'; + const IMPERSONATOR_ID = 'auth.impersonator'; /** * Login using email/user + password combination. @@ -86,6 +87,40 @@ class Auth extends Injectable $this->eventsManager->fire('auth:onLogin', $this, 'System'); } + public function getImpersonator() + { + $id = $this->session->get(self::IMPERSONATOR_ID); + return $id !== null ? User::findFirst($id) : null; + } + + /** + * Impersonate a user + * + * @param User $user + */ + public function impersonate(User $user) + { + $current = $this->getIdentity(); + if ($current === null) { + throw new \InvalidArgumentException("Need to be authenticated to be able to impersonate someone"); + } + + if ($current->getId() === $user->getId()) { + // Same user + throw new \DomainException("Can't impersonate yourself"); + } + + $this->session->set(self::IMPERSONATOR_ID, $current->getId()); + $this->setIdentity($user->getId()); + $this->eventsManager->fire('auth:onImpersonate', $this, $current); + } + + public function impersonateClear($imp_id) + { + $this->session->remove(self::IMPERSONATOR_ID); + $this->session->set(self::SESSION_KEY, $imp_id); + } + /** * @param $identity * @return Auth @@ -135,7 +170,12 @@ class Auth extends Injectable */ public function clearIdentity() { - $this->session->remove(self::SESSION_KEY); + $imp_id = $this->session->get(self::IMPERSONATOR_ID); + if ($imp_id !== null) { + $this->impersonateClear($imp_id); + } else { + $this->session->remove(self::SESSION_KEY); + } return $this; } } diff --git a/app/listeners/ActivityLog.php b/app/listeners/ActivityLog.php index 906b3be..4f4787c 100644 --- a/app/listeners/ActivityLog.php +++ b/app/listeners/ActivityLog.php @@ -23,6 +23,19 @@ class ActivityLog extends Injectable $this->_log($auth->getUser(), sprintf("Logged in (%s)", $type)); } + /** + * On Impersonate event. + * + * @param Event $event + * @param Auth $auth + * @param User $user The user Impersonating the user in $auth + */ + public function onImpersonate(Event $event, Auth $auth, User $user) + { + $imp = $auth->getUser(); + $this->_log($user, sprintf("Impersonated user (%s:%s)", $imp->getId(), $imp->getUsername())); + } + /** * @param Event $event * @param User $auth diff --git a/app/views/_common/_components/navigation.volt b/app/views/_common/_components/navigation.volt index eaf3fd7..9b11888 100644 --- a/app/views/_common/_components/navigation.volt +++ b/app/views/_common/_components/navigation.volt @@ -12,6 +12,8 @@ data-bs-toggle="dropdown" role="button" aria-expanded="false"> {{ icon('solid/user') }} {{ auth.getUser().username }} + {% set imp = auth.getImpersonator() %} + {% if imp %}( {{ icon('solid/user-secret') }} {{ imp.username }} ){% endif %}