Added ability to impersonate all users

And some UI fixes and improvements
This commit is contained in:
Pinga 2025-04-04 12:02:58 +03:00
parent 08d30c57e9
commit c6e520f1e0
6 changed files with 42 additions and 28 deletions

View file

@ -1543,7 +1543,7 @@ class RegistrarsController extends Controller
FROM registrar r FROM registrar r
JOIN registrar_users ru ON ru.registrar_id = r.id JOIN registrar_users ru ON ru.registrar_id = r.id
JOIN users u ON u.id = ru.user_id JOIN users u ON u.id = ru.user_id
WHERE r.clid = ? AND u.roles_mask = 4 WHERE r.clid = ? AND u.roles_mask = 4 AND u.status = 0
ORDER BY ru.user_id ASC ORDER BY ru.user_id ASC
', [ $args ]); ', [ $args ]);

View file

@ -7,6 +7,7 @@ use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use Respect\Validation\Validator as v; use Respect\Validation\Validator as v;
use App\Auth\Auth;
class UsersController extends Controller class UsersController extends Controller
{ {
@ -416,5 +417,41 @@ class UsersController extends Controller
return $response->withHeader('Location', '/user/update/'.$username)->withStatus(302); return $response->withHeader('Location', '/user/update/'.$username)->withStatus(302);
} }
} }
public function impersonateUser(Request $request, Response $response, $args)
{
if ($_SESSION["auth_roles"] != 0) {
return $response->withHeader('Location', '/dashboard')->withStatus(302);
}
$db = $this->container->get('db');
if ($args) {
$args = trim($args);
if (!preg_match('/^[a-z0-9_-]+$/', $args)) {
$this->container->get('flash')->addMessage('error', 'Invalid user name');
return $response->withHeader('Location', '/users')->withStatus(302);
}
$user_id = $db->selectValue('
SELECT ru.user_id
FROM registrar r
JOIN registrar_users ru ON ru.registrar_id = r.id
JOIN users u ON u.id = ru.user_id
WHERE u.username = ? AND u.status = 0
', [ $args ]);
if (!$user_id) {
$this->container->get('flash')->addMessage('error', 'The specified user does not exist or is no longer active');
return $response->withHeader('Location', '/users')->withStatus(302);
}
Auth::impersonateUser($user_id);
} else {
// Redirect to the users view
return $response->withHeader('Location', '/users')->withStatus(302);
}
}
} }

View file

@ -172,29 +172,6 @@
{% include 'partials/footer.twig' %} {% include 'partials/footer.twig' %}
</div> </div>
<script> <script>
document.addEventListener("DOMContentLoaded", function () {
var el;
window.TomSelect && (new TomSelect(el = document.getElementById('select-roles'), {
copyClassesToDropdown: false,
dropdownParent: 'body',
controlInput: '<input>',
render:{
item: function(data,escape) {
if( data.customProperties ){
return '<div><span class="dropdown-item-indicator">' + data.customProperties + '</span>' + escape(data.text) + '</div>';
}
return '<div>' + escape(data.text) + '</div>';
},
option: function(data,escape){
if( data.customProperties ){
return '<div><span class="dropdown-item-indicator">' + data.customProperties + '</span>' + escape(data.text) + '</div>';
}
return '<div>' + escape(data.text) + '</div>';
},
},
}));
});
document.querySelector('form').addEventListener('submit', function (e) { document.querySelector('form').addEventListener('submit', function (e) {
const select = document.getElementById('select-roles'); const select = document.getElementById('select-roles');
let rolesMask = 0; let rolesMask = 0;
@ -217,5 +194,4 @@ document.querySelector('form').addEventListener('submit', function (e) {
select.disabled = true; select.disabled = true;
}); });
</script> </script>
<script src="/assets/libs/tom-select/dist/js/tom-select.base.min.js" defer></script>
{% endblock %} {% endblock %}

View file

@ -14,7 +14,7 @@
function actionsFormatter(cell, formatterParams, onRendered) { function actionsFormatter(cell, formatterParams, onRendered) {
return ` return `
<a class="btn btn-outline-primary btn-icon update-btn" href="/registrar/update/${cell.getRow().getData().clid}" title="{{ __('Manage Registrar') }}"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M7 7h-1a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-1"></path><path d="M20.385 6.585a2.1 2.1 0 0 0 -2.97 -2.97l-8.415 8.385v3h3l8.385 -8.415z"></path><path d="M16 5l3 3"></path></svg></a> <a class="btn btn-outline-secondary btn-icon update-btn" href="/registrar/pricing/${cell.getRow().getData().clid}" title="{{ __('Manage Custom Pricing') }}"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 21v-16a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v16l-3 -2l-2 2l-2 -2l-2 2l-2 -2l-3 2" /><path d="M14 8h-2.5a1.5 1.5 0 0 0 0 3h1a1.5 1.5 0 0 1 0 3h-2.5m2 0v1.5m0 -9v1.5" /></svg></a> <a class="btn btn-outline-info btn-icon update-btn" href="/registrar/impersonate/${cell.getRow().getData().clid}" title="{{ __('Impersonate') }}"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 8v-2a2 2 0 0 1 2 -2h7a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-7a2 2 0 0 1 -2 -2v-2" /><path d="M3 12h13l-3 -3" /><path d="M13 15l3 -3" /></svg></a> <a class="btn btn-outline-primary btn-icon update-btn" href="/registrar/update/${cell.getRow().getData().clid}" title="{{ __('Manage Registrar') }}"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M7 7h-1a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-1"></path><path d="M20.385 6.585a2.1 2.1 0 0 0 -2.97 -2.97l-8.415 8.385v3h3l8.385 -8.415z"></path><path d="M16 5l3 3"></path></svg></a> <a class="btn btn-outline-teal btn-icon update-btn" href="/registrar/pricing/${cell.getRow().getData().clid}" title="{{ __('Manage Custom Pricing') }}"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 21v-16a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v16l-3 -2l-2 2l-2 -2l-2 2l-2 -2l-3 2" /><path d="M14 8h-2.5a1.5 1.5 0 0 0 0 3h1a1.5 1.5 0 0 1 0 3h-2.5m2 0v1.5m0 -9v1.5" /></svg></a> <a class="btn btn-outline-purple btn-icon update-btn" href="/registrar/impersonate/${cell.getRow().getData().clid}" title="{{ __('Impersonate') }}"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 8v-2a2 2 0 0 1 2 -2h7a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-7a2 2 0 0 1 -2 -2v-2" /><path d="M3 12h13l-3 -3" /><path d="M13 15l3 -3" /></svg></a>
`; `;
} }

View file

@ -14,7 +14,7 @@
function actionsFormatter(cell, formatterParams, onRendered) { function actionsFormatter(cell, formatterParams, onRendered) {
return ` return `
<a class="btn btn-outline-primary btn-icon update-btn" href="/user/update/${cell.getRow().getData().username}" title="{{ __('Manage User') }}"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M7 7h-1a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-1"></path><path d="M20.385 6.585a2.1 2.1 0 0 0 -2.97 -2.97l-8.415 8.385v3h3l8.385 -8.415z"></path><path d="M16 5l3 3"></path></svg></a> <a class="btn btn-outline-primary btn-icon update-btn" href="/user/update/${cell.getRow().getData().username}" title="{{ __('Manage User') }}"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M7 7h-1a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-1"></path><path d="M20.385 6.585a2.1 2.1 0 0 0 -2.97 -2.97l-8.415 8.385v3h3l8.385 -8.415z"></path><path d="M16 5l3 3"></path></svg></a> <a class="btn btn-outline-purple btn-icon update-btn" href="/user/impersonate/${cell.getRow().getData().username}" title="{{ __('Impersonate') }}"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 8v-2a2 2 0 0 1 2 -2h7a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-7a2 2 0 0 1 -2 -2v-2" /><path d="M3 12h13l-3 -3" /><path d="M13 15l3 -3" /></svg></a>
`; `;
} }

View file

@ -110,6 +110,7 @@ $app->group('', function ($route) {
$route->map(['GET', 'POST'], '/user/create', UsersController::class . ':createUser')->setName('createUser'); $route->map(['GET', 'POST'], '/user/create', UsersController::class . ':createUser')->setName('createUser');
$route->get('/user/update/{user}', UsersController::class . ':updateUser')->setName('updateUser'); $route->get('/user/update/{user}', UsersController::class . ':updateUser')->setName('updateUser');
$route->post('/user/update', UsersController::class . ':updateUserProcess')->setName('updateUserProcess'); $route->post('/user/update', UsersController::class . ':updateUserProcess')->setName('updateUserProcess');
$route->get('/user/impersonate/{user}', UsersController::class . ':impersonateUser')->setName('impersonateUser');
$route->get('/epphistory', LogsController::class .':view')->setName('epphistory'); $route->get('/epphistory', LogsController::class .':view')->setName('epphistory');
$route->get('/poll', LogsController::class .':poll')->setName('poll'); $route->get('/poll', LogsController::class .':poll')->setName('poll');