366 lines
10 KiB
JavaScript
366 lines
10 KiB
JavaScript
|
/** @odoo-module **/
|
||
|
|
||
|
import { queryAll } from "@odoo/hoot-dom";
|
||
|
import { rpc } from "@web/core/network/rpc";
|
||
|
import { registry } from "@web/core/registry";
|
||
|
import { stepUtils } from "@web_tour/tour_service/tour_utils";
|
||
|
|
||
|
function openRoot() {
|
||
|
return [{
|
||
|
content: "return to client root to avoid race condition",
|
||
|
trigger: 'body',
|
||
|
run() {
|
||
|
document.querySelector("body").classList.add("wait");
|
||
|
window.location = '/odoo';
|
||
|
}
|
||
|
}, {
|
||
|
content: "wait for client reload",
|
||
|
trigger: 'body:not(.wait)',
|
||
|
}];
|
||
|
}
|
||
|
function openUserProfileAtSecurityTab() {
|
||
|
return [{
|
||
|
content: 'Open user account menu',
|
||
|
trigger: '.o_user_menu .dropdown-toggle',
|
||
|
run: 'click',
|
||
|
}, {
|
||
|
content: "Open preferences / profile screen",
|
||
|
trigger: '[data-menu=settings]',
|
||
|
run: 'click',
|
||
|
}, {
|
||
|
content: "Switch to security tab",
|
||
|
trigger: 'a[role=tab]:contains("Account Security")',
|
||
|
run: 'click',
|
||
|
}];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Checks that the TOTP button is in the specified state (true = enabled =
|
||
|
* can disable, false = disabled = can enable), then closes the profile dialog
|
||
|
* if it's one (= hr not installed).
|
||
|
*
|
||
|
* If no totp state is provided, just checks that the toggle exists.
|
||
|
*/
|
||
|
function closeProfileDialog({content, totp_state}) {
|
||
|
let trigger;
|
||
|
switch (totp_state) {
|
||
|
case true: trigger = 'button[name=action_totp_disable]'; break;
|
||
|
case false: trigger = 'button[name=action_totp_enable_wizard]'; break;
|
||
|
case undefined: trigger = 'button.o_auth_2fa_btn'; break;
|
||
|
default: throw new Error(`Invalid totp state ${totp_state}`)
|
||
|
}
|
||
|
|
||
|
return [{
|
||
|
content,
|
||
|
trigger,
|
||
|
run(helpers) {
|
||
|
const modal = document.querySelector(".o_dialog");
|
||
|
if (modal) {
|
||
|
modal.querySelector("button[name=preference_cancel]").click();
|
||
|
}
|
||
|
}
|
||
|
}, {
|
||
|
trigger: 'body',
|
||
|
async run() {
|
||
|
while (document.querySelector('.o_dialog')) {
|
||
|
await Promise.resolve();
|
||
|
}
|
||
|
this.anchor.classList.add("dialog-closed");
|
||
|
},
|
||
|
}, {
|
||
|
trigger: 'body.dialog-closed',
|
||
|
}];
|
||
|
}
|
||
|
|
||
|
registry.category("web_tour.tours").add('totp_tour_setup', {
|
||
|
url: '/odoo',
|
||
|
steps: () => [...openUserProfileAtSecurityTab(), {
|
||
|
content: "Open totp wizard",
|
||
|
trigger: 'button[name=action_totp_enable_wizard]',
|
||
|
run: "click",
|
||
|
},
|
||
|
{
|
||
|
trigger: ".modal div:contains(entering your password)",
|
||
|
},
|
||
|
{
|
||
|
content: "Check that we have to enter enhanced security mode and input password",
|
||
|
trigger: '[name=password] input',
|
||
|
run: 'edit test_user',
|
||
|
}, {
|
||
|
content: "Confirm",
|
||
|
trigger: "button:contains(Confirm Password)",
|
||
|
run: "click",
|
||
|
}, {
|
||
|
content: "Check the wizard has opened",
|
||
|
trigger: '.modal li:contains("When requested to do so")',
|
||
|
}, {
|
||
|
content: "Get secret from collapsed div",
|
||
|
trigger: `.modal a:contains("Cannot scan it?")`,
|
||
|
async run(helpers) {
|
||
|
const secret = this.anchor
|
||
|
.closest("div")
|
||
|
.querySelector("[name=secret] span:first-child");
|
||
|
const copyBtn = secret.querySelector("button");
|
||
|
if (copyBtn) {
|
||
|
copyBtn.remove();
|
||
|
}
|
||
|
const token = await rpc('/totphook', {
|
||
|
secret: secret.textContent
|
||
|
});
|
||
|
await helpers.edit(token, '[name=code] input');
|
||
|
document.querySelector("body").classList.add("got-token");
|
||
|
}
|
||
|
},
|
||
|
{
|
||
|
trigger: ".modal button.btn-primary:contains(Activate)",
|
||
|
run: "click",
|
||
|
},
|
||
|
{
|
||
|
trigger: "body:not(:has(.modal))",
|
||
|
},
|
||
|
{
|
||
|
content: 'wait for rpc',
|
||
|
trigger: 'body.got-token',
|
||
|
},
|
||
|
...openRoot(),
|
||
|
...openUserProfileAtSecurityTab(),
|
||
|
...closeProfileDialog({
|
||
|
content: "Check that the button has changed",
|
||
|
totp_state: true,
|
||
|
}),
|
||
|
]});
|
||
|
|
||
|
registry.category("web_tour.tours").add('totp_login_enabled', {
|
||
|
url: '/',
|
||
|
steps: () => [{
|
||
|
content: "check that we're on the login page or go to it",
|
||
|
trigger: 'input#login, a:contains(Sign in)',
|
||
|
run: "click",
|
||
|
}, {
|
||
|
content: "input login",
|
||
|
trigger: 'input#login',
|
||
|
run: "edit test_user",
|
||
|
}, {
|
||
|
content: 'input password',
|
||
|
trigger: 'input#password',
|
||
|
run: "edit test_user",
|
||
|
}, {
|
||
|
content: "click da button",
|
||
|
trigger: 'button:contains("Log in")',
|
||
|
run: "click",
|
||
|
}, {
|
||
|
content: "expect totp screen",
|
||
|
trigger: 'label:contains(Authentication Code)',
|
||
|
run: "click",
|
||
|
}, {
|
||
|
content: "input code",
|
||
|
trigger: 'input[name=totp_token]',
|
||
|
async run(helpers) {
|
||
|
const token = await rpc('/totphook');
|
||
|
helpers.edit(token);
|
||
|
}
|
||
|
},
|
||
|
{
|
||
|
trigger: `button:contains("Log in")`,
|
||
|
run: "click",
|
||
|
},
|
||
|
{
|
||
|
content: "check we're logged in",
|
||
|
trigger: ".o_user_menu .dropdown-toggle",
|
||
|
}]});
|
||
|
|
||
|
registry.category("web_tour.tours").add('totp_login_device', {
|
||
|
url: '/',
|
||
|
steps: () => [{
|
||
|
content: "check that we're on the login page or go to it",
|
||
|
trigger: 'input#login, a:contains(Sign in)',
|
||
|
run: "click",
|
||
|
}, {
|
||
|
content: "input login",
|
||
|
trigger: 'input#login',
|
||
|
run: "edit test_user",
|
||
|
}, {
|
||
|
content: 'input password',
|
||
|
trigger: 'input#password',
|
||
|
run: "edit test_user",
|
||
|
}, {
|
||
|
content: "click da button",
|
||
|
trigger: 'button:contains("Log in")',
|
||
|
run: "click",
|
||
|
}, {
|
||
|
content: "expect totp screen",
|
||
|
trigger: 'label:contains(Authentication Code)',
|
||
|
run: "click",
|
||
|
}, {
|
||
|
content: "check remember device box",
|
||
|
trigger: 'label[for=switch-remember]',
|
||
|
run: "click",
|
||
|
}, {
|
||
|
content: "input code",
|
||
|
trigger: 'input[name=totp_token]',
|
||
|
async run(helpers) {
|
||
|
const token = await rpc('/totphook')
|
||
|
helpers.edit(token);
|
||
|
}
|
||
|
},
|
||
|
{
|
||
|
trigger: "button:contains(Log in)",
|
||
|
run: "click",
|
||
|
},
|
||
|
{
|
||
|
content: "check we're logged in",
|
||
|
trigger: ".o_user_menu .dropdown-toggle",
|
||
|
run: 'click',
|
||
|
}, {
|
||
|
content: "click the Log out button",
|
||
|
trigger: '.dropdown-item[data-menu=logout]',
|
||
|
run: "click",
|
||
|
}, {
|
||
|
content: "check that we're back on the login page or go to it",
|
||
|
trigger: 'input#login, a:contains(Log in)',
|
||
|
run: "edit Test",
|
||
|
}, {
|
||
|
content: "input login again",
|
||
|
trigger: 'input#login',
|
||
|
run: "edit test_user",
|
||
|
}, {
|
||
|
content: 'input password again',
|
||
|
trigger: 'input#password',
|
||
|
run: "edit test_user",
|
||
|
}, {
|
||
|
content: "click da button again",
|
||
|
trigger: 'button:contains("Log in")',
|
||
|
run: "click",
|
||
|
}, {
|
||
|
content: "check we're logged in without 2FA",
|
||
|
trigger: ".o_user_menu .dropdown-toggle",
|
||
|
},
|
||
|
// now go and disable two-factor authentication would be annoying to do in a separate tour
|
||
|
// because we'd need to login & totp again as HttpCase.authenticate can't
|
||
|
// succeed w/ totp enabled
|
||
|
...openUserProfileAtSecurityTab(),
|
||
|
{
|
||
|
content: "Open totp wizard",
|
||
|
trigger: 'button[name=action_totp_disable]',
|
||
|
run: "click",
|
||
|
},
|
||
|
{
|
||
|
trigger: ".modal div:contains(entering your password)",
|
||
|
},
|
||
|
{
|
||
|
content: "Check that we have to enter enhanced security mode and input password",
|
||
|
trigger: '.modal [name=password] input',
|
||
|
run: "edit test_user",
|
||
|
}, {
|
||
|
content: "Confirm",
|
||
|
trigger: ".modal button:contains(Confirm Password)",
|
||
|
run: "click",
|
||
|
},
|
||
|
{
|
||
|
trigger: "body:not(:has(.modal))",
|
||
|
},
|
||
|
...openRoot(),
|
||
|
...openUserProfileAtSecurityTab(),
|
||
|
...closeProfileDialog({
|
||
|
content: "Check that the button has changed",
|
||
|
totp_state: false
|
||
|
}),
|
||
|
]});
|
||
|
|
||
|
registry.category("web_tour.tours").add('totp_login_disabled', {
|
||
|
url: '/',
|
||
|
steps: () => [{
|
||
|
content: "check that we're on the login page or go to it",
|
||
|
trigger: 'input#login, a:contains(Sign in)',
|
||
|
run: "click",
|
||
|
}, {
|
||
|
content: "input login",
|
||
|
trigger: 'input#login',
|
||
|
run: "edit test_user",
|
||
|
}, {
|
||
|
content: 'input password',
|
||
|
trigger: 'input#password',
|
||
|
run: "edit test_user",
|
||
|
}, {
|
||
|
content: "click da button",
|
||
|
trigger: 'button:contains("Log in")',
|
||
|
run: "click",
|
||
|
},
|
||
|
// normally we'd end the tour here as it's all we care about but there are a
|
||
|
// bunch of ongoing queries from the loading of the web client which cause
|
||
|
// issues, so go and open the preferences / profile screen to make sure
|
||
|
// everything settles down
|
||
|
...openUserProfileAtSecurityTab(),
|
||
|
// close the dialog if that makes sense
|
||
|
...closeProfileDialog({})
|
||
|
]});
|
||
|
|
||
|
const columns = {};
|
||
|
registry.category("web_tour.tours").add('totp_admin_disables', {
|
||
|
url: '/odoo',
|
||
|
steps: () => [stepUtils.showAppsMenuItem(), {
|
||
|
content: 'Go to settings',
|
||
|
trigger: '[data-menu-xmlid="base.menu_administration"]',
|
||
|
run: "click",
|
||
|
}, {
|
||
|
content: 'Wait for page',
|
||
|
trigger: '.o_menu_brand:contains("Settings")',
|
||
|
}, {
|
||
|
content: "Open Users menu",
|
||
|
trigger: '[data-menu-xmlid="base.menu_users"]',
|
||
|
run: "click",
|
||
|
}, {
|
||
|
content: "Open Users view",
|
||
|
trigger: '[data-menu-xmlid="base.menu_action_res_users"]',
|
||
|
run: "click",
|
||
|
}, {
|
||
|
content: "Find test_user User",
|
||
|
trigger: 'td.o_data_cell:contains("test_user")',
|
||
|
run(helpers) {
|
||
|
const titles = queryAll("tr:first th", { root: this.anchor.closest("table") });
|
||
|
titles.forEach((el, i) => {
|
||
|
columns[el.getAttribute('data-name')] = i;
|
||
|
})
|
||
|
const row = this.anchor.closest('tr');
|
||
|
const sel = row.querySelector('.o_list_record_selector input[type=checkbox]');
|
||
|
helpers.click(sel);
|
||
|
}
|
||
|
}, {
|
||
|
content: "Open Actions menu",
|
||
|
trigger: 'button.dropdown-toggle:contains("Action")',
|
||
|
run: "click",
|
||
|
}, {
|
||
|
content: "Select totp remover",
|
||
|
trigger: 'span.dropdown-item:contains(Disable two-factor authentication)',
|
||
|
run: "click",
|
||
|
},
|
||
|
{
|
||
|
trigger: ".modal div:contains(entering your password)",
|
||
|
},
|
||
|
{ // enhanced security yo
|
||
|
content: "Check that we have to enter enhanced security mode & input password",
|
||
|
trigger: '.modal [name=password] input',
|
||
|
run: "edit admin",
|
||
|
}, {
|
||
|
content: "Confirm",
|
||
|
trigger: ".modal button:contains(Confirm Password)",
|
||
|
run: "click",
|
||
|
},
|
||
|
{
|
||
|
content: "Wait the modal is closed",
|
||
|
trigger: "body:not(:has(.modal))",
|
||
|
},
|
||
|
{
|
||
|
content: "open the user's form",
|
||
|
trigger: "td.o_data_cell:contains(test_user)",
|
||
|
run: "click",
|
||
|
}, {
|
||
|
content: "go to Account security Tab",
|
||
|
trigger: "a.nav-link:contains(Account Security)",
|
||
|
run: "click",
|
||
|
}, ...closeProfileDialog({
|
||
|
content: "check that test_user user has been de-totp'd",
|
||
|
totp_state: false,
|
||
|
}),
|
||
|
]})
|