Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 25 additions & 13 deletions templates/pages/setup/sessiontracker/sessiontracker.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -294,24 +294,43 @@
window.reloadTab(new URLSearchParams(getFilterData()).toString());
}

async function revoke(url, options = {}) {
const default_error = '{{ __('An unexpected error occurred')|e('js') }}';
try {
const response = await fetch(url, {
method: 'POST',
...options,
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json',
...(options.headers ?? {}),
},
});
if (!response.ok) {
const data = await response.json().catch(() => ({}));
glpi_toast_error(data.message ?? default_error, data.title);
return;
}
reloadWithFilters();
} catch {
glpi_toast_error(default_error);
}
}

document.querySelector('button.revoke-all-sessions').addEventListener('click', async () => {
const confirmed = await glpi_confirm_danger({
title: '{{ __('Revoke all active sessions')|e('js') }}',
message: '{{ __('Are you sure you want to revoke all active sessions (excluding the current session)?')|e('js') }}',
confirm_label: '{{ __('Revoke')|e('js') }}'
});
if (confirmed) {
fetch(`{{ path('/Security/Session/All/Revoke')|e('js') }}`, {
method: 'POST',
await revoke(`{{ path('/Security/Session/All/Revoke')|e('js') }}`, {
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
users_id: {{ is_admin_view ? 0 : session('glpiID')|e('js') }}
}),
}).then(() => {
reloadWithFilters();
});
}
});
Expand All @@ -329,14 +348,7 @@
const url = session_type === 'api'
? `{{ path('/Security/AccessToken/')|e('js') }}${identifier}/Revoke`
: `{{ path('/Security/Session/')|e('js') }}${identifier}/Revoke`;
fetch(url, {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest',
},
}).then(() => {
reloadWithFilters();
});
await revoke(url);
}
});
});
Expand Down
32 changes: 32 additions & 0 deletions tests/e2e/specs/Security/session_manager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,35 @@ test('Can terminate sessions', async ({
anonymousPage.getByRole('link', { name: "Log in again" })
).toBeVisible();
});

test('Displays the error message when the revocation fails', async ({ page, profile }) => {
await profile.set(Profiles.SuperAdmin);

// Never let the request reach the server: revoking all sessions would
// terminate the sessions used by the other tests.
let accept_header: string | undefined;
await page.route('**/Security/Session/All/Revoke', async (route) => {
accept_header = route.request().headers()['accept'];
await route.fulfill({
status: 403,
contentType: 'application/json',
body: JSON.stringify({
error: true,
title: 'Access denied',
message: 'Sessions cannot be revoked for now.',
}),
});
});

await page.goto("/front/security/securityconfig.form.php?forcetab=Glpi\\Security\\SecurityConfig$2");
await expect(page.getByText("Session list")).toBeVisible();
await page.getByRole("button", { name: "Revoke all active sessions" }).click();

const confirm_dialog = page.getByRole('dialog');
await expect(confirm_dialog).toHaveAttribute('data-glpi-modal-shown', 'true');
await confirm_dialog.getByRole("button", { name: "Revoke" }).click();

// The error message returned by the server is displayed to the user.
await expect(page.getByText('Sessions cannot be revoked for now.')).toBeVisible();
expect(accept_header).toContain('application/json');
});
Loading