diff --git a/docker-compose.local.yml b/docker-compose.local.yml index 9f3497329d..9f4805adcf 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -53,6 +53,8 @@ x-variables: &variables GK_SESSION_IN_REDIS: "true" + GK_SITE_ADMINISTRATORS: "1" + services: adminer: diff --git a/tests-qa/acceptance/120_admin/10_impersonation.robot b/tests-qa/acceptance/120_admin/10_impersonation.robot new file mode 100644 index 0000000000..00e705e532 --- /dev/null +++ b/tests-qa/acceptance/120_admin/10_impersonation.robot @@ -0,0 +1,98 @@ +*** Settings *** +Resource ../ressources/Authentication.robot +Resource ../ressources/vars/Urls.robot +Resource ../ressources/Moves.robot +Variables ../ressources/vars/users.yml +Variables ../ressources/vars/geokrety.yml +Variables ../ressources/vars/waypoints.yml +Suite Setup Suite Setup + +*** Variables *** + +*** Test Cases *** + +Anonymous users cannot impersonate + Go To Url ${PAGE_HOME_URL_EN} + Page Should Not Contain Element ${NAVBAR_ACTIONS_IMPERSONATE_USER_START_LINK} + Page Should Not Contain Element ${NAVBAR_ACTIONS_IMPERSONATE_USER_STOP_LINK} + + Go To Url ${PAGE_USER_3_PROFILE_URL} + Page Should Not Contain Element ${NAVBAR_ACTIONS_IMPERSONATE_USER_START_LINK} + Page Should Not Contain Element ${NAVBAR_ACTIONS_IMPERSONATE_USER_STOP_LINK} + + Go To Url ${PAGE_ADMIN_IMPERSONATE_USER_START} userid=${USER_3.id} redirect=${PAGE_SIGN_IN_URL} + Go To Url ${PAGE_ADMIN_IMPERSONATE_USER_STOP} userid=${USER_3.id} redirect=${PAGE_SIGN_IN_URL} + + +Other users cannot impersonate + Sign In ${USER_2.name} Fast + + Go To Url ${PAGE_HOME_URL_EN} + Page Should Not Contain Element ${NAVBAR_ACTIONS_IMPERSONATE_USER_START_LINK} + Page Should Not Contain Element ${NAVBAR_ACTIONS_IMPERSONATE_USER_STOP_LINK} + + Go To Url ${PAGE_USER_3_PROFILE_URL} + Page Should Not Contain Element ${NAVBAR_ACTIONS_IMPERSONATE_USER_START_LINK} + Page Should Not Contain Element ${NAVBAR_ACTIONS_IMPERSONATE_USER_STOP_LINK} + + Go To Url ${PAGE_ADMIN_IMPERSONATE_USER_START} userid=${USER_3.id} redirect=${PAGE_HOME_URL_EN} + Page Should Contain HTTP 403 + Go To Url ${PAGE_ADMIN_IMPERSONATE_USER_STOP} userid=${USER_3.id} redirect=${PAGE_HOME_URL_EN} + Page Should Contain HTTP 403 + + +Admin users can impersonate + Sign In ${USER_1.name} Fast + + Go To Url ${PAGE_HOME_URL_EN} + Page Should Not Contain Element ${NAVBAR_ACTIONS_IMPERSONATE_USER_START_LINK} + Page Should Not Contain Element ${NAVBAR_ACTIONS_IMPERSONATE_USER_STOP_LINK} + + Go To Url ${PAGE_USER_1_PROFILE_URL} + Page Should Not Contain Element ${NAVBAR_ACTIONS_IMPERSONATE_USER_START_LINK} + Page Should Not Contain Element ${NAVBAR_ACTIONS_IMPERSONATE_USER_STOP_LINK} + + Go To Url ${PAGE_USER_3_PROFILE_URL} + Page Should Contain Element ${NAVBAR_ACTIONS_IMPERSONATE_USER_START_LINK} + Page Should Not Contain Element ${NAVBAR_ACTIONS_IMPERSONATE_USER_STOP_LINK} + Element Text Should Be ${NAVBAR_PROFILE_LINK} ${USER_1.name} + + Go To Url ${PAGE_ADMIN_IMPERSONATE_USER_START} userid=${USER_3.id} redirect=${PAGE_USER_3_PROFILE_URL} + Page Should Not Contain Element ${NAVBAR_ACTIONS_IMPERSONATE_USER_START_LINK} + Page Should Contain Element ${NAVBAR_ACTIONS_IMPERSONATE_USER_STOP_LINK} + Element Text Should Be ${NAVBAR_PROFILE_LINK} ${USER_3.name} + + Go To Url ${PAGE_HOME_URL_EN} + Page Should Not Contain Element ${NAVBAR_ACTIONS_IMPERSONATE_USER_START_LINK} + Page Should Contain Element ${NAVBAR_ACTIONS_IMPERSONATE_USER_STOP_LINK} + + Go To Url ${PAGE_USER_1_PROFILE_URL} + Page Should Contain Element ${NAVBAR_ACTIONS_IMPERSONATE_USER_START_LINK} + Page Should Contain Element ${NAVBAR_ACTIONS_IMPERSONATE_USER_STOP_LINK} + + Go To Url ${PAGE_USER_2_PROFILE_URL} + Page Should Contain Element ${NAVBAR_ACTIONS_IMPERSONATE_USER_START_LINK} + Page Should Contain Element ${NAVBAR_ACTIONS_IMPERSONATE_USER_STOP_LINK} + + Click Element ${NAVBAR_ACTIONS_IMPERSONATE_USER_START_LINK} + Element Text Should Be ${NAVBAR_PROFILE_LINK} ${USER_2.name} + Page Should Not Contain Element ${NAVBAR_ACTIONS_IMPERSONATE_USER_START_LINK} + Page Should Contain Element ${NAVBAR_ACTIONS_IMPERSONATE_USER_STOP_LINK} + + Go To Url ${PAGE_USER_3_PROFILE_URL} + Element Text Should Be ${NAVBAR_PROFILE_LINK} ${USER_2.name} + Page Should Contain Element ${NAVBAR_ACTIONS_IMPERSONATE_USER_START_LINK} + Page Should Contain Element ${NAVBAR_ACTIONS_IMPERSONATE_USER_STOP_LINK} + + Click Element ${NAVBAR_ACTIONS_IMPERSONATE_USER_STOP_LINK} + Location Should Be ${PAGE_USER_2_PROFILE_URL} + Element Text Should Be ${NAVBAR_PROFILE_LINK} ${USER_1.name} + Page Should Contain Element ${NAVBAR_ACTIONS_IMPERSONATE_USER_START_LINK} + Page Should Not Contain Element ${NAVBAR_ACTIONS_IMPERSONATE_USER_STOP_LINK} + +*** Keywords *** + +Suite Setup + Clear Database And Seed ${3} users + Seed ${1} geokrety owned by ${1} + Sign Out Fast diff --git a/tests-qa/acceptance/ressources/ComponentsLocator.robot b/tests-qa/acceptance/ressources/ComponentsLocator.robot index 66f937199a..fcb52176f3 100644 --- a/tests-qa/acceptance/ressources/ComponentsLocator.robot +++ b/tests-qa/acceptance/ressources/ComponentsLocator.robot @@ -22,6 +22,8 @@ ${NAVBAR_ACTIONS_CREATE_GEOKRET_LINK} //*[@id="navbar-actions-create"] ${NAVBAR_ACTIONS_CLAIM_GEOKRET_LINK} //*[@id="navbar-actions-claim"] ${NAVBAR_ACTIONS_SEARCH_LINK} //*[@id="navbar-actions-search"] ${NAVBAR_ACTIONS_PHOTO_GALLERY_LINK} //*[@id="navbar-actions-gallery"] +${NAVBAR_ACTIONS_IMPERSONATE_USER_START_LINK} //*[@id="navbar-impersonate-start"] +${NAVBAR_ACTIONS_IMPERSONATE_USER_STOP_LINK} //*[@id="navbar-impersonate-stop"] ################ diff --git a/tests-qa/acceptance/ressources/vars/Urls.robot b/tests-qa/acceptance/ressources/vars/Urls.robot index 5a2faf0bd4..024207cbec 100644 --- a/tests-qa/acceptance/ressources/vars/Urls.robot +++ b/tests-qa/acceptance/ressources/vars/Urls.robot @@ -107,6 +107,13 @@ ${PAGE_LEGACY_API_EXPORT_URL} ${GK_URL}/api/v1/export ${PAGE_LEGACY_API_EXPORT2_URL} ${GK_URL}/api/v1/export2 ${PAGE_LEGACY_API_EXPORT_OC_URL} ${GK_URL}/api/v1/export_oc +# ADMIN +${PAGE_ADMIN_HOME} ${PAGE_HOME_URL}/admin + +# IMPERSONATION +${PAGE_ADMIN_IMPERSONATE_USER_START} ${PAGE_ADMIN_HOME}/impersonate/\${params.userid}/start +${PAGE_ADMIN_IMPERSONATE_USER_STOP} ${PAGE_ADMIN_HOME}/impersonate/stop + *** Keywords *** Go To Url diff --git a/website/app-templates/smarty/navbar-profile.tpl b/website/app-templates/smarty/navbar-profile.tpl index ad276d4168..94c4d203f2 100644 --- a/website/app-templates/smarty/navbar-profile.tpl +++ b/website/app-templates/smarty/navbar-profile.tpl @@ -1,6 +1,15 @@ {if $f3->get('SESSION.IS_LOGGED_IN') && !is_null($current_user)} + {if $f3->get('SESSION.IS_ADMIN')} -
  • {fa icon="support"} {t}Admin{/t}
  • +
  • {fa icon="support"} {t}Admin{/t}
  • + {if $f3->get('SESSION.ADMIN_ID') } + {if isset($user) && $user->id !== $f3->get('SESSION.CURRENT_USER')} +
  • {fa icon="user-secret"}
  • + {/if} + {if $f3->get('SESSION.ADMIN_IMPERSONATING') === true} +
  • {fa icon="user-times"}
  • + {/if} + {/if} {/if}
  • diff --git a/website/app/GeoKrety/Controller/Admin/Impersonate.php b/website/app/GeoKrety/Controller/Admin/Impersonate.php new file mode 100644 index 0000000000..bab5a5ecbe --- /dev/null +++ b/website/app/GeoKrety/Controller/Admin/Impersonate.php @@ -0,0 +1,36 @@ +load(['id = ?', $f3->get('PARAMS.userid')]); + if ($user->dry()) { + $f3->error(404, _('This user does not exist.')); + } + + $f3->set('CURRENT_USER', $user->id); + $f3->set('SESSION.CURRENT_USER', $user->id); + $f3->set('SESSION.CURRENT_USERNAME', $user->username); + $f3->set('SESSION.ADMIN_IMPERSONATING', true); + + $f3->reroute(['user_details', ['userid' => $user->id]]); + } + + public function stop(\Base $f3) { + $user_id = $f3->get('CURRENT_USER'); + $f3->set('CURRENT_USER', $this->currentAdmin->id); + $f3->set('SESSION.CURRENT_USER', $this->currentAdmin->id); + $f3->set('SESSION.CURRENT_USERNAME', $this->currentAdmin->username); + $f3->clear('SESSION.ADMIN_IMPERSONATING'); + + $f3->reroute(['user_details', ['userid' => $user_id]]); + } +} diff --git a/website/app/GeoKrety/Controller/Admin/Traits/CurrentAdminLoader.php b/website/app/GeoKrety/Controller/Admin/Traits/CurrentAdminLoader.php new file mode 100644 index 0000000000..b2e49c334b --- /dev/null +++ b/website/app/GeoKrety/Controller/Admin/Traits/CurrentAdminLoader.php @@ -0,0 +1,33 @@ +get('SESSION.IS_LOGGED_IN')) { + $f3->error(401); + } + + $user = new User(); + $user->load(['id = ?', $f3->get('SESSION.ADMIN_ID')]); + if ($user->dry()) { + $f3->error(404, _('This user does not exist.')); + } + $this->currentAdmin = $user; + Smarty::assign('currentAdmin', $this->currentAdmin); + + if (method_exists($this, '_beforeRoute')) { + $this->_beforeRoute($f3); + } + } +} diff --git a/website/app/GeoKrety/Controller/Pages/Base.php b/website/app/GeoKrety/Controller/Pages/Base.php index 5221ed7c1d..63f29b41ac 100644 --- a/website/app/GeoKrety/Controller/Pages/Base.php +++ b/website/app/GeoKrety/Controller/Pages/Base.php @@ -22,6 +22,7 @@ abstract class Base { 'devel_mail', 'devel_mail_delete', 'devel_mail_delete_all', + 'admin_impersonate_user_stop', ]; protected ?User $current_user = null; diff --git a/website/app/GeoKrety/Controller/Pages/Login.php b/website/app/GeoKrety/Controller/Pages/Login.php index 165c9e5b82..c2f848a046 100644 --- a/website/app/GeoKrety/Controller/Pages/Login.php +++ b/website/app/GeoKrety/Controller/Pages/Login.php @@ -78,6 +78,9 @@ public function login(\Base $f3) { * @param bool $redirect Redirect to GOTO url */ public static function connectUser(\Base $f3, User $user, ?string $method = null, bool $redirect = true) { + $user->touch('last_login_datetime'); + $user->save(); + $f3->set('CURRENT_USER', $user->id); $f3->set('SESSION.CURRENT_USER', $user->id); $f3->set('SESSION.CURRENT_USERNAME', $user->username); @@ -85,9 +88,11 @@ public static function connectUser(\Base $f3, User $user, ?string $method = null if (in_array($user->id, GK_SITE_ADMINISTRATORS)) { $f3->set('SESSION.user.group', AuthGroup::AUTH_LEVEL_ADMINISTRATORS); $f3->set('SESSION.IS_ADMIN', true); + $f3->set('SESSION.ADMIN_ID', $user->id); } else { $f3->set('SESSION.user.group', AuthGroup::AUTH_LEVEL_AUTHENTICATED); $f3->set('SESSION.IS_ADMIN', false); + $f3->clear('SESSION.ADMIN_ID'); } Smarty::assign('current_user', $user); Event::instance()->emit("user.login.$method-effective", $user); diff --git a/website/app/admin.ini b/website/app/admin.ini index 6ed3d462a6..4cc2731b4a 100644 --- a/website/app/admin.ini +++ b/website/app/admin.ini @@ -11,6 +11,9 @@ GET @admin_news_list: /admin/news = \GeoKrety\Controller\Admin\NewsList->get GET @admin_news_list_paginate: /admin/news/page/@page = \GeoKrety\Controller\Admin\NewsList->get GET @admin_news_view: /admin/news/@newsid = \GeoKrety\Controller\Admin\NewsView->get +GET @admin_impersonate_user: /admin/impersonate/@userid/start = \GeoKrety\Controller\Admin\Impersonate->get +GET @admin_impersonate_user_stop: /admin/impersonate/stop = \GeoKrety\Controller\Admin\Impersonate->stop + ; statistics GET @statistics_waypoints_restart: /admin/statistics/waypoints/@service_id/restart = \GeoKrety\Controller\Statistics->force_complete_synchronization diff --git a/website/public/app-ui/css/app.scss b/website/public/app-ui/css/app.scss index 78b1259e9e..8d1b3d9ddb 100644 --- a/website/public/app-ui/css/app.scss +++ b/website/public/app-ui/css/app.scss @@ -44,8 +44,8 @@ border: 0; } -#navbar-profile-admin { - color: orangered; +.navbar-admin { + color: orangered !important; } .leaflet-container {