diff options
Diffstat (limited to 'toolkit/components/aboutwebauthn/tests')
8 files changed, 1495 insertions, 0 deletions
diff --git a/toolkit/components/aboutwebauthn/tests/browser/browser.toml b/toolkit/components/aboutwebauthn/tests/browser/browser.toml new file mode 100644 index 0000000000..a95e23b956 --- /dev/null +++ b/toolkit/components/aboutwebauthn/tests/browser/browser.toml @@ -0,0 +1,21 @@ +[DEFAULT] +head = "head.js" +prefs = [ + "security.webauth.webauthn=true", + "security.webauth.webauthn_enable_softtoken=false", + "security.webauth.webauthn_enable_android_fido2=false", + "security.webauth.webauthn_enable_usbtoken=false", + "security.webauthn.ctap2=true", +] + +["browser_aboutwebauthn_aria_keycontrols.js"] + +["browser_aboutwebauthn_bio.js"] + +["browser_aboutwebauthn_credentials.js"] + +["browser_aboutwebauthn_info.js"] + +["browser_aboutwebauthn_no_token.js"] + +["browser_aboutwebauthn_pin.js"] diff --git a/toolkit/components/aboutwebauthn/tests/browser/browser_aboutwebauthn_aria_keycontrols.js b/toolkit/components/aboutwebauthn/tests/browser/browser_aboutwebauthn_aria_keycontrols.js new file mode 100644 index 0000000000..44e7f3dcd3 --- /dev/null +++ b/toolkit/components/aboutwebauthn/tests/browser/browser_aboutwebauthn_aria_keycontrols.js @@ -0,0 +1,195 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +var doc, tab; + +add_setup(async function () { + info("Starting about:webauthn"); + tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: "about:webauthn", + waitForLoad: true, + }); + + doc = tab.linkedBrowser.contentDocument; + let ops = { + credMgmt: true, + clientPin: true, + bioEnroll: true, + }; + send_auth_info_and_check_categories(doc, ops); +}); + +registerCleanupFunction(async function () { + // Close tab. + await BrowserTestUtils.removeTab(tab); +}); + +add_task(async function moving_up_and_down() { + doc.getElementById("info-tab-button").focus(); + [ + ["UP", "DOWN"], + ["LEFT", "RIGHT"], + ].forEach(dirs => { + let up = dirs[0]; + let down = dirs[1]; + EventUtils.sendKey(down); + is( + doc.activeElement, + doc.getElementById("pin-tab-button"), + "Wrong element active" + ); + EventUtils.sendKey(down); + is( + doc.activeElement, + doc.getElementById("credentials-tab-button"), + "Wrong element active" + ); + EventUtils.sendKey(down); + is( + doc.activeElement, + doc.getElementById("bio-enrollments-tab-button"), + "Wrong element active" + ); + // Trying to go down further should do nothing + EventUtils.sendKey(down); + is( + doc.activeElement, + doc.getElementById("bio-enrollments-tab-button"), + "Wrong element active" + ); + EventUtils.sendKey(down); + is( + doc.activeElement, + doc.getElementById("bio-enrollments-tab-button"), + "Wrong element active" + ); + + // Going back up again + EventUtils.sendKey(up); + is( + doc.activeElement, + doc.getElementById("credentials-tab-button"), + "Wrong element active" + ); + EventUtils.sendKey(up); + is( + doc.activeElement, + doc.getElementById("pin-tab-button"), + "Wrong element active" + ); + EventUtils.sendKey(up); + is( + doc.activeElement, + doc.getElementById("info-tab-button"), + "Wrong element active" + ); + // Trying to go up further should do nothing + EventUtils.sendKey(up); + is( + doc.activeElement, + doc.getElementById("info-tab-button"), + "Wrong element active" + ); + EventUtils.sendKey(up); + is( + doc.activeElement, + doc.getElementById("info-tab-button"), + "Wrong element active" + ); + }); +}); + +add_task(async function switching_sections() { + doc.getElementById("info-tab-button").focus(); + EventUtils.sendKey("DOWN"); // Pin section + EventUtils.sendKey("DOWN"); // Credentials section + EventUtils.sendKey("RETURN"); + + let credentials_section = doc.getElementById("credential-management-section"); + isnot( + credentials_section.style.display, + "none", + "credentials section not visible" + ); + + EventUtils.sendKey("UP"); // PIN section + EventUtils.sendKey("RETURN"); + + let pin_section = doc.getElementById("set-change-pin-section"); + isnot(pin_section.style.display, "none", "pin section not visible"); + + EventUtils.sendKey("DOWN"); // Credentials section + EventUtils.sendChar(" "); // Try using Space instead of Return + + isnot( + credentials_section.style.display, + "none", + "credentials section not visible" + ); +}); + +add_task(async function jumping_sections() { + doc.getElementById("info-tab-button").focus(); + EventUtils.sendKey("DOWN"); // Pin section + EventUtils.sendKey("DOWN"); // Credentials section + is( + doc.activeElement, + doc.getElementById("credentials-tab-button"), + "Wrong element active" + ); + + EventUtils.sendKey("HOME"); + is( + doc.activeElement, + doc.getElementById("info-tab-button"), + "Wrong element active" + ); + + // Another hit of the Home-key should change nothing + EventUtils.sendKey("HOME"); + is( + doc.activeElement, + doc.getElementById("info-tab-button"), + "Wrong element active" + ); + + EventUtils.sendKey("END"); + is( + doc.activeElement, + doc.getElementById("bio-enrollments-tab-button"), + "Wrong element active" + ); + + EventUtils.sendKey("HOME"); + is( + doc.activeElement, + doc.getElementById("info-tab-button"), + "Wrong element active" + ); + + EventUtils.sendKey("DOWN"); // Pin section + EventUtils.sendKey("DOWN"); // Credentials section + is( + doc.activeElement, + doc.getElementById("credentials-tab-button"), + "Wrong element active" + ); + + EventUtils.sendKey("END"); + is( + doc.activeElement, + doc.getElementById("bio-enrollments-tab-button"), + "Wrong element active" + ); + + // Another hit of the "End"-key should change nothing + EventUtils.sendKey("END"); + is( + doc.activeElement, + doc.getElementById("bio-enrollments-tab-button"), + "Wrong element active" + ); +}); diff --git a/toolkit/components/aboutwebauthn/tests/browser/browser_aboutwebauthn_bio.js b/toolkit/components/aboutwebauthn/tests/browser/browser_aboutwebauthn_bio.js new file mode 100644 index 0000000000..79ce5723d1 --- /dev/null +++ b/toolkit/components/aboutwebauthn/tests/browser/browser_aboutwebauthn_bio.js @@ -0,0 +1,351 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +var doc, tab; + +add_setup(async function () { + info("Starting about:webauthn"); + tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: "about:webauthn", + waitForLoad: true, + }); + + doc = tab.linkedBrowser.contentDocument; +}); + +registerCleanupFunction(async function () { + // Close tab. + await BrowserTestUtils.removeTab(tab); +}); + +function send_bio_enrollment_command(state) { + let msg = JSON.stringify({ + type: "bio-enrollment-update", + result: state, + }); + Services.obs.notifyObservers(null, "about-webauthn-prompt", msg); +} + +function check_bio_buttons_disabled(disabled) { + let buttons = Array.from(doc.getElementsByClassName("bio-enrollment-button")); + buttons.forEach(button => { + is(button.disabled, disabled); + }); + return buttons; +} + +function check_tab_buttons_aria(button_id) { + let button = doc.getElementById(button_id); + is(button.role, "tab", button_id + " in the sidebar is a tab"); + ok( + button.hasAttribute("aria-controls"), + button_id + " in the sidebar is a tab with an assigned tablist" + ); + ok( + button.hasAttribute("aria-selected"), + button_id + " in the sidebar is a tab that can be or is selected" + ); + ok( + button.hasAttribute("tabindex"), + button_id + " in the sidebar is a tab with keyboard focusability provided" + ); +} + +async function send_authinfo_and_open_bio_section(ops) { + reset_about_page(doc); + send_auth_info_and_check_categories(doc, ops); + + ["pin-tab-button", "credentials-tab-button"].forEach(button_id => { + let button = doc.getElementById(button_id); + is(button.style.display, "none", button_id + " in the sidebar not hidden"); + check_tab_buttons_aria(button_id); + }); + + if (ops.bioEnroll !== null || ops.userVerificationMgmtPreview !== null) { + let bio_enrollments_tab_button = doc.getElementById( + "bio-enrollments-tab-button" + ); + // Check if bio enrollment section is visible + isnot( + bio_enrollments_tab_button.style.display, + "none", + "bio enrollment button in the sidebar not visible" + ); + check_tab_buttons_aria("bio-enrollments-tab-button"); + + // Click the section and wait for it to open + let bio_enrollment_section = doc.getElementById("bio-enrollment-section"); + bio_enrollments_tab_button.click(); + isnot( + bio_enrollment_section.style.display, + "none", + "bio enrollment section not visible" + ); + is( + bio_enrollments_tab_button.getAttribute("selected"), + "true", + "bio enrollment section button not selected" + ); + } +} + +add_task(async function bio_enrollment_not_supported() { + send_authinfo_and_open_bio_section({ + bioEnroll: null, + userVerificationMgmtPreview: null, + }); + let bio_enrollments_tab_button = doc.getElementById( + "bio-enrollments-tab-button" + ); + is( + bio_enrollments_tab_button.style.display, + "none", + "bio enrollments button in the sidebar visible" + ); +}); + +add_task(async function bio_enrollment_supported() { + // Setting bioEnroll should show the button in the sidebar + // The function is checking this for us. + send_authinfo_and_open_bio_section({ + bioEnroll: true, + userVerificationMgmtPreview: null, + }); + // Setting Preview should show the button in the sidebar + send_authinfo_and_open_bio_section({ + bioEnroll: null, + userVerificationMgmtPreview: true, + }); + // Setting both should also work + send_authinfo_and_open_bio_section({ + bioEnroll: true, + userVerificationMgmtPreview: true, + }); +}); + +add_task(async function bio_enrollment_empty_list() { + send_authinfo_and_open_bio_section({ bioEnroll: true }); + + let list_bio_enrollments_button = doc.getElementById( + "list-bio-enrollments-button" + ); + let add_bio_enrollment_button = doc.getElementById( + "add-bio-enrollment-button" + ); + isnot( + list_bio_enrollments_button.style.display, + "none", + "List bio enrollments button in the sidebar not visible" + ); + isnot( + add_bio_enrollment_button.style.display, + "none", + "Add bio enrollment button in the sidebar not visible" + ); + + // Bio list should initially not be visible + let bio_enrollment_list = doc.getElementById( + "bio-enrollment-list-subsection" + ); + is(bio_enrollment_list.hidden, true, "bio enrollment list visible"); + + list_bio_enrollments_button.click(); + is( + list_bio_enrollments_button.disabled, + true, + "List bio enrollments button not disabled while op in progress" + ); + is( + add_bio_enrollment_button.disabled, + true, + "Add bio enrollment button not disabled while op in progress" + ); + + send_bio_enrollment_command({ EnrollmentList: [] }); + is( + list_bio_enrollments_button.disabled, + false, + "List bio enrollments button disabled" + ); + is( + add_bio_enrollment_button.disabled, + false, + "Add bio enrollment button disabled" + ); + + is(bio_enrollment_list.hidden, false, "bio enrollments list visible"); + let bio_enrollment_list_empty_label = doc.getElementById( + "bio-enrollment-list-empty-label" + ); + is( + bio_enrollment_list_empty_label.hidden, + false, + "bio enrollments list empty label not visible" + ); +}); + +add_task(async function bio_enrollment_real_data() { + send_authinfo_and_open_bio_section({ bioEnroll: true }); + let list_bio_enrollments_button = doc.getElementById( + "list-bio-enrollments-button" + ); + let add_bio_enrollment_button = doc.getElementById( + "add-bio-enrollment-button" + ); + list_bio_enrollments_button.click(); + send_bio_enrollment_command({ EnrollmentList: REAL_DATA }); + is( + list_bio_enrollments_button.disabled, + false, + "List bio enrollments button disabled" + ); + is( + add_bio_enrollment_button.disabled, + false, + "Add bio enrollment button disabled" + ); + let bio_enrollment_list = doc.getElementById("bio-enrollment-list"); + is( + bio_enrollment_list.rows.length, + 2, + "bio enrollment list table doesn't contain 2 bio enrollments" + ); + is(bio_enrollment_list.rows[0].cells[0].textContent, "right-middle"); + is(bio_enrollment_list.rows[1].cells[0].textContent, "right-index"); + let buttons = check_bio_buttons_disabled(false); + // 2 for each bio + 1 for the list button + 1 for the add button + hidden "start enroll" + is(buttons.length, 5); + + list_bio_enrollments_button.click(); + buttons = check_bio_buttons_disabled(true); +}); + +add_task(async function bio_enrollment_add_new() { + send_authinfo_and_open_bio_section({ bioEnroll: true }); + let add_bio_enrollment_button = doc.getElementById( + "add-bio-enrollment-button" + ); + add_bio_enrollment_button.click(); + let add_bio_enrollment_section = doc.getElementById( + "add-bio-enrollment-section" + ); + isnot( + add_bio_enrollment_section.style.display, + "none", + "Add bio enrollment section invisible" + ); + let enrollment_name = doc.getElementById("enrollment-name"); + enrollment_name.value = "Test123"; + + let start_enrollment_button = doc.getElementById("start-enrollment-button"); + start_enrollment_button.click(); + + check_bio_buttons_disabled(true); + send_bio_enrollment_command({ + SampleStatus: ["Ctap2EnrollFeedbackFpGood", 3], + }); + + let enrollment_update = doc.getElementById("enrollment-update"); + is(enrollment_update.children.length, 1); + check_bio_buttons_disabled(true); + + send_bio_enrollment_command({ + SampleStatus: ["Ctap2EnrollFeedbackFpGood", 2], + }); + send_bio_enrollment_command({ + SampleStatus: ["Ctap2EnrollFeedbackFpGood", 1], + }); + send_bio_enrollment_command({ + SampleStatus: ["Ctap2EnrollFeedbackFpGood", 0], + }); + is(enrollment_update.children.length, 4); + check_bio_buttons_disabled(true); + // We tell the about-page that we still have enrollments (bioEnroll: true). + // So it will automatically request a refreshed list from the authenticator, disabling + // all the buttons again. + send_bio_enrollment_command({ AddSuccess: { options: { bioEnroll: true } } }); + is(enrollment_update.children.length, 0); + is(enrollment_name.value, "", "Enrollment name field did not get cleared"); + check_bio_buttons_disabled(true); +}); + +add_task(async function bio_enrollment_delete() { + send_authinfo_and_open_bio_section({ bioEnroll: true }); + let list_bio_enrollments_button = doc.getElementById( + "list-bio-enrollments-button" + ); + list_bio_enrollments_button.click(); + send_bio_enrollment_command({ EnrollmentList: REAL_DATA }); + let buttons = check_bio_buttons_disabled(false); + buttons[1].click(); + check_bio_buttons_disabled(true); + // Tell the about page, we still have enrollments + send_bio_enrollment_command({ + DeleteSuccess: { options: { bioEnroll: true } }, + }); + // This will cause it to automatically request the new list, thus disabling all buttons + check_bio_buttons_disabled(true); + send_bio_enrollment_command({ EnrollmentList: REAL_DATA }); + buttons = check_bio_buttons_disabled(false); + buttons[1].click(); + + // Confirmation dialog should pop open + let confirmation_section = doc.getElementById("confirm-deletion-section"); + isnot( + confirmation_section.style.display, + "none", + "Confirmation section did not open." + ); + + // Check if the label displays the correct data + let confirmation_context = doc.getElementById("confirmation-context"); + // Items get listed in reverse order (inserRow(0) in a loop), so we use [0] instead of [1] here + is( + confirmation_context.textContent, + REAL_DATA[0].template_friendly_name, + "Deletion context show wrong credential name" + ); + // Check if the delete-button has the correct context-data + let cmd = { + BioEnrollment: { + DeleteEnrollment: REAL_DATA[0].template_id, + }, + }; + is( + confirmation_context.getAttribute("data-ctap-command"), + JSON.stringify(cmd), + "Confirm-button has the wrong context data" + ); + + let cancel_button = doc.getElementById("cancel-confirmation-button"); + cancel_button.click(); + is( + confirmation_section.style.display, + "none", + "Confirmation section did not close." + ); + check_bio_buttons_disabled(false); + + // Click the delete-button again + buttons[1].click(); + + // Now we tell it that we deleted all enrollments (bioEnroll: false) + send_bio_enrollment_command({ + DeleteSuccess: { options: { bioEnroll: false } }, + }); + // Thus, all buttons should get re-enabled and the subsection should be hidden + check_bio_buttons_disabled(false); + + let bio_enrollment_subsection = doc.getElementById( + "bio-enrollment-list-subsection" + ); + is(bio_enrollment_subsection.hidden, true, "Bio Enrollment List not hidden."); +}); + +const REAL_DATA = [ + { template_id: [248, 82], template_friendly_name: "right-index" }, + { template_id: [14, 163], template_friendly_name: "right-middle" }, +]; diff --git a/toolkit/components/aboutwebauthn/tests/browser/browser_aboutwebauthn_credentials.js b/toolkit/components/aboutwebauthn/tests/browser/browser_aboutwebauthn_credentials.js new file mode 100644 index 0000000000..ff45ea2962 --- /dev/null +++ b/toolkit/components/aboutwebauthn/tests/browser/browser_aboutwebauthn_credentials.js @@ -0,0 +1,395 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +var doc, tab; + +add_setup(async function () { + info("Starting about:webauthn"); + tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: "about:webauthn", + waitForLoad: true, + }); + + doc = tab.linkedBrowser.contentDocument; +}); + +registerCleanupFunction(async function () { + // Close tab. + await BrowserTestUtils.removeTab(tab); +}); + +function send_credential_list(credlist) { + let num_of_creds = 0; + credlist.forEach(domain => { + domain.credentials.forEach(c => { + num_of_creds += 1; + }); + }); + let msg = JSON.stringify({ + type: "credential-management-update", + result: { + CredentialList: { + existing_resident_credentials_count: num_of_creds, + max_possible_remaining_resident_credentials_count: 20, + credential_list: credlist, + }, + }, + }); + Services.obs.notifyObservers(null, "about-webauthn-prompt", msg); +} + +function check_cred_buttons_disabled(disabled) { + let buttons = Array.from(doc.getElementsByClassName("credentials-button")); + buttons.forEach(button => { + is(button.disabled, disabled); + }); + return buttons; +} + +async function send_authinfo_and_open_cred_section(ops) { + reset_about_page(doc); + send_auth_info_and_check_categories(doc, ops); + + ["pin-tab-button", "bio-enrollments-tab-button"].forEach(button_id => { + let button = doc.getElementById(button_id); + is(button.style.display, "none", button_id + " in the sidebar not hidden"); + }); + + if (ops.credMgmt !== null || ops.credentialMgmtPreview !== null) { + let credentials_tab_button = doc.getElementById("credentials-tab-button"); + // Check if credentials section is visible + isnot( + credentials_tab_button.style.display, + "none", + "credentials button in the sidebar not visible" + ); + + // Click the section and wait for it to open + let credentials_section = doc.getElementById( + "credential-management-section" + ); + credentials_tab_button.click(); + isnot( + credentials_section.style.display, + "none", + "credentials section not visible" + ); + is( + credentials_tab_button.getAttribute("selected"), + "true", + "credentials section button not selected" + ); + } +} + +add_task(async function cred_mgmt_not_supported() { + // Not setting credMgmt at all should lead to not showing it in the sidebar + send_authinfo_and_open_cred_section({ + credMgmt: null, + credentialMgmtPreview: null, + }); + let credentials_tab_button = doc.getElementById("credentials-tab-button"); + is( + credentials_tab_button.style.display, + "none", + "credentials button in the sidebar visible" + ); +}); + +add_task(async function cred_mgmt_supported() { + // Setting credMgmt should show the button in the sidebar + // The function is checking this for us. + send_authinfo_and_open_cred_section({ + credMgmt: true, + credentialMgmtPreview: null, + }); + // Setting credMgmtPreview should show the button in the sidebar + send_authinfo_and_open_cred_section({ + credMgmt: null, + credentialMgmtPreview: true, + }); + // Setting both should also work + send_authinfo_and_open_cred_section({ + credMgmt: true, + credentialMgmtPreview: true, + }); +}); + +add_task(async function cred_mgmt_empty_list() { + send_authinfo_and_open_cred_section({ credMgmt: true }); + + let list_credentials_button = doc.getElementById("list-credentials-button"); + isnot( + list_credentials_button.style.display, + "none", + "credentials button in the sidebar not visible" + ); + + // Credential list should initially not be visible + let credential_list = doc.getElementById("credential-list-subsection"); + is(credential_list.hidden, true, "credentials list visible"); + + list_credentials_button.click(); + is( + list_credentials_button.disabled, + true, + "credentials button not disabled while op in progress" + ); + + send_credential_list([]); + is(list_credentials_button.disabled, false, "credentials button disabled"); + + is(credential_list.hidden, false, "credentials list visible"); + let credential_list_empty_label = doc.getElementById( + "credential-list-empty-label" + ); + is( + credential_list_empty_label.hidden, + false, + "credentials list empty label not visible" + ); +}); + +add_task(async function cred_mgmt_real_data() { + send_authinfo_and_open_cred_section({ credMgmt: true }); + let list_credentials_button = doc.getElementById("list-credentials-button"); + list_credentials_button.click(); + send_credential_list(REAL_DATA); + is(list_credentials_button.disabled, false, "credentials button disabled"); + let credential_list = doc.getElementById("credential-list"); + is( + credential_list.rows.length, + 4, + "credential list table doesn't contain 4 credentials" + ); + is(credential_list.rows[0].cells[0].textContent, "webauthn.io"); + is(credential_list.rows[0].cells[1].textContent, "fasdfasd"); + is(credential_list.rows[1].cells[0].textContent, "webauthn.io"); + is(credential_list.rows[1].cells[1].textContent, "twetwetw"); + is(credential_list.rows[2].cells[0].textContent, "webauthn.io"); + is(credential_list.rows[2].cells[1].textContent, "hhhhhg"); + is(credential_list.rows[3].cells[0].textContent, "example.com"); + is(credential_list.rows[3].cells[1].textContent, "A. Nother"); + let buttons = check_cred_buttons_disabled(false); + // 4 for each cred + 1 for the list button + is(buttons.length, 5); + + list_credentials_button.click(); + buttons = check_cred_buttons_disabled(true); + send_credential_list(REAL_DATA); + buttons[2].click(); + check_cred_buttons_disabled(true); + + // Confirmation section should now be open + let credential_section = doc.getElementById("credential-management-section"); + is( + credential_section.style.display, + "none", + "credential section still visible" + ); + let confirmation_section = doc.getElementById("confirm-deletion-section"); + isnot( + confirmation_section.style.display, + "none", + "Confirmation section did not open." + ); + + // Check if the label displays the correct data + let confirmation_context = doc.getElementById("confirmation-context"); + is( + confirmation_context.textContent, + "webauthn.io - hhhhhg", + "Deletion context show wrong credential name" + ); + // Check if the delete-button has the correct context-data + let cmd = { + CredentialManagement: { + DeleteCredential: REAL_DATA[1].credentials[0].credential_id, + }, + }; + is( + confirmation_context.getAttribute("data-ctap-command"), + JSON.stringify(cmd), + "Confirm-button has the wrong context data" + ); + + let cancel_button = doc.getElementById("cancel-confirmation-button"); + cancel_button.click(); + isnot( + credential_section.style.display, + "none", + "credential section still visible" + ); + is( + confirmation_section.style.display, + "none", + "Confirmation section did not open." + ); + check_cred_buttons_disabled(false); +}); + +const REAL_DATA = [ + { + rp: { id: "example.com" }, + rp_id_hash: [ + 163, 121, 166, 246, 238, 175, 185, 165, 94, 55, 140, 17, 128, 52, 226, + 117, 30, 104, 47, 171, 159, 45, 48, 171, 19, 210, 18, 85, 134, 206, 25, + 71, + ], + credentials: [ + { + user: { + id: [65, 46, 32, 78, 111, 116, 104, 101, 114], + name: "A. Nother", + }, + credential_id: { + id: [ + 195, 239, 221, 151, 76, 77, 255, 242, 217, 50, 87, 144, 238, 79, + 199, 120, 234, 148, 142, 69, 163, 133, 189, 254, 74, 138, 119, 140, + 197, 171, 36, 215, 191, 176, 36, 111, 113, 158, 204, 147, 101, 200, + 20, 239, 191, 174, 51, 15, + ], + type: "public-key", + }, + public_key: { + 1: 2, + 3: -7, + "-1": 1, + "-2": [ + 195, 239, 221, 151, 76, 77, 255, 242, 217, 50, 87, 144, 238, 235, + 230, 51, 155, 142, 121, 60, 136, 63, 80, 184, 41, 238, 217, 61, 1, + 206, 253, 141, + ], + "-3": [ + 15, 81, 111, 204, 199, 48, 18, 121, 134, 243, 26, 49, 6, 244, 25, + 156, 188, 71, 245, 122, 93, 47, 218, 235, 25, 222, 191, 116, 20, 14, + 195, 114, + ], + }, + cred_protect: 1, + large_blob_key: [ + 223, 32, 77, 171, 223, 133, 38, 175, 229, 40, 85, 216, 203, 79, 194, + 223, 32, 191, 119, 241, 115, 6, 101, 180, 92, 194, 208, 193, 181, 163, + 164, 64, + ], + }, + ], + }, + { + rp: { id: "webauthn.io" }, + rp_id_hash: [ + 116, 166, 234, 146, 19, 201, 156, 47, 116, 178, 36, 146, 179, 32, 207, 64, + 38, 42, 148, 193, 169, 80, 160, 57, 127, 41, 37, 11, 96, 132, 30, 240, + ], + credentials: [ + { + user: { id: [97, 71, 104, 111, 97, 71, 104, 110], name: "hhhhhg" }, + credential_id: { + id: [ + 64, 132, 129, 5, 185, 62, 86, 253, 199, 113, 219, 14, 207, 113, 145, + 78, 177, 198, 130, 217, 122, 105, 225, 111, 32, 227, 237, 209, 6, + 220, 202, 234, 144, 227, 246, 42, 73, 68, 37, 142, 95, 139, 224, 36, + 156, 168, 118, 181, + ], + type: "public-key", + }, + public_key: { + 1: 2, + 3: -7, + "-1": 1, + "-2": [ + 64, 132, 129, 5, 185, 62, 86, 253, 199, 113, 219, 14, 207, 22, 10, + 241, 230, 152, 5, 204, 35, 94, 22, 191, 213, 2, 247, 220, 227, 62, + 76, 182, + ], + "-3": [ + 13, 30, 30, 149, 170, 118, 78, 115, 101, 218, 245, 52, 154, 242, 67, + 146, 17, 184, 112, 225, 51, 47, 242, 157, 195, 80, 76, 101, 147, + 161, 3, 185, + ], + }, + cred_protect: 1, + large_blob_key: [ + 10, 67, 27, 233, 8, 115, 69, 191, 105, 213, 77, 123, 210, 118, 193, + 234, 3, 12, 234, 228, 215, 106, 24, 228, 102, 247, 255, 156, 99, 196, + 215, 230, + ], + }, + { + user: { + id: [100, 72, 100, 108, 100, 72, 100, 108, 100, 72, 99], + name: "twetwetw", + }, + credential_id: { + id: [ + 29, 8, 25, 57, 66, 234, 22, 27, 227, 141, 77, 93, 233, 234, 251, 61, + 100, 199, 176, 97, 112, 48, 172, 118, 145, 0, 156, 76, 156, 215, 18, + 25, 118, 32, 241, 127, 13, 177, 249, 101, 26, 209, 142, 116, 74, 95, + 117, 29, + ], + type: "public-key", + }, + public_key: { + 1: 2, + 3: -7, + "-1": 1, + "-2": [ + 29, 8, 25, 57, 66, 234, 22, 27, 227, 141, 77, 93, 233, 154, 113, + 177, 251, 161, 54, 76, 150, 15, 6, 143, 117, 214, 232, 215, 118, 41, + 116, 19, + ], + "-3": [ + 201, 6, 43, 178, 3, 249, 175, 123, 149, 81, 127, 20, 116, 152, 238, + 84, 52, 113, 3, 165, 176, 105, 200, 137, 209, 0, 141, 50, 42, 192, + 174, 26, + ], + }, + cred_protect: 1, + large_blob_key: [ + 125, 175, 155, 1, 14, 247, 182, 241, 234, 66, 115, 236, 200, 223, 176, + 88, 88, 88, 202, 173, 147, 217, 9, 193, 114, 7, 29, 169, 224, 179, + 187, 188, + ], + }, + { + user: { + id: [90, 109, 70, 122, 90, 71, 90, 104, 99, 50, 81], + name: "fasdfasd", + }, + credential_id: { + id: [ + 58, 174, 92, 116, 17, 108, 28, 203, 233, 192, 182, 60, 80, 236, 133, + 196, 98, 32, 103, 53, 107, 48, 46, 236, 228, 166, 21, 33, 228, 75, + 85, 191, 71, 18, 214, 177, 56, 254, 89, 28, 187, 220, 241, 7, 21, + 11, 45, 151, + ], + type: "public-key", + }, + public_key: { + 1: 2, + 3: -7, + "-1": 1, + "-2": [ + 58, 174, 92, 116, 17, 108, 28, 203, 233, 192, 182, 60, 80, 111, 150, + 192, 102, 255, 211, 156, 5, 186, 29, 105, 154, 79, 14, 2, 106, 159, + 57, 156, + ], + "-3": [ + 182, 164, 251, 221, 237, 36, 239, 109, 146, 184, 146, 29, 143, 16, + 35, 188, 84, 148, 247, 83, 181, 40, 88, 111, 245, 13, 254, 206, 242, + 164, 234, 159, + ], + }, + cred_protect: 1, + large_blob_key: [ + 113, 154, 217, 69, 45, 108, 115, 20, 104, 43, 214, 120, 253, 93, 223, + 204, 125, 234, 220, 148, 98, 118, 98, 157, 175, 41, 154, 97, 87, 233, + 208, 171, + ], + }, + ], + }, +]; diff --git a/toolkit/components/aboutwebauthn/tests/browser/browser_aboutwebauthn_info.js b/toolkit/components/aboutwebauthn/tests/browser/browser_aboutwebauthn_info.js new file mode 100644 index 0000000000..f66f5929fc --- /dev/null +++ b/toolkit/components/aboutwebauthn/tests/browser/browser_aboutwebauthn_info.js @@ -0,0 +1,218 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +var doc, tab; + +add_setup(async function () { + info("Starting about:webauthn"); + tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: "about:webauthn", + waitForLoad: true, + }); + + doc = tab.linkedBrowser.contentDocument; +}); + +registerCleanupFunction(async function () { + // Close tab. + await BrowserTestUtils.removeTab(tab); +}); + +function send_auth_data_and_check(auth_data) { + Services.obs.notifyObservers( + null, + "about-webauthn-prompt", + JSON.stringify({ type: "selected-device", auth_info: auth_data }) + ); + + let info_text = doc.getElementById("info-text-div"); + is(info_text.hidden, true, "Start prompt not hidden"); + + let info_section = doc.getElementById("token-info-section"); + isnot(info_section.style.display, "none", "Info section hidden"); +} + +add_task(async function multiple_devices() { + Services.obs.notifyObservers( + null, + "about-webauthn-prompt", + JSON.stringify({ type: "select-device" }) + ); + + let info_text = doc.getElementById("info-text-div"); + is(info_text.hidden, false, "Start prompt hidden"); + let field = doc.getElementById("info-text-field"); + is( + field.getAttribute("data-l10n-id"), + "about-webauthn-text-select-device", + "Field does not prompt user to touch device for selection" + ); +}); + +add_task(async function multiple_devices() { + send_auth_data_and_check(REAL_AUTH_INFO_1); + reset_about_page(doc); + send_auth_data_and_check(REAL_AUTH_INFO_2); + reset_about_page(doc); + send_auth_data_and_check(REAL_AUTH_INFO_3); + reset_about_page(doc); +}); + +// Yubikey BIO +const REAL_AUTH_INFO_1 = { + versions: ["U2F_V2", "FIDO_2_0", "FIDO_2_1_PRE", "FIDO_2_1"], + extensions: [ + "credProtect", + "hmac-secret", + "largeBlobKey", + "credBlob", + "minPinLength", + ], + aaguid: [ + 216, 82, 45, 159, 87, 91, 72, 102, 136, 169, 186, 153, 250, 2, 243, 91, + ], + options: { + plat: false, + rk: true, + clientPin: true, + up: true, + uv: true, + pinUvAuthToken: true, + noMcGaPermissionsWithClientPin: null, + largeBlobs: true, + ep: null, + bioEnroll: true, + userVerificationMgmtPreview: true, + uvBioEnroll: null, + authnrCfg: true, + uvAcfg: null, + credMgmt: true, + credentialMgmtPreview: true, + setMinPINLength: true, + makeCredUvNotRqd: true, + alwaysUv: false, + }, + max_msg_size: 1200, + pin_protocols: [2, 1], + max_credential_count_in_list: 8, + max_credential_id_length: 128, + transports: ["usb"], + algorithms: [ + { alg: -7, type: "public-key" }, + { alg: -8, type: "public-key" }, + ], + max_ser_large_blob_array: 1024, + force_pin_change: false, + min_pin_length: 4, + firmware_version: 328966, + max_cred_blob_length: 32, + max_rpids_for_set_min_pin_length: 1, + preferred_platform_uv_attempts: 3, + uv_modality: 2, + certifications: null, + remaining_discoverable_credentials: 20, + vendor_prototype_config_commands: null, +}; + +// Yubikey 5 +const REAL_AUTH_INFO_2 = { + versions: ["U2F_V2", "FIDO_2_0", "FIDO_2_1_PRE"], + extensions: ["credProtect", "hmac-secret"], + aaguid: [ + 47, 192, 87, 159, 129, 19, 71, 234, 177, 22, 187, 90, 141, 185, 32, 42, + ], + options: { + plat: false, + rk: true, + clientPin: true, + up: true, + uv: null, + pinUvAuthToken: null, + noMcGaPermissionsWithClientPin: null, + largeBlobs: null, + ep: null, + bioEnroll: null, + userVerificationMgmtPreview: null, + uvBioEnroll: null, + authnrCfg: null, + uvAcfg: null, + credMgmt: null, + credentialMgmtPreview: true, + setMinPINLength: null, + makeCredUvNotRqd: null, + alwaysUv: null, + }, + max_msg_size: 1200, + pin_protocols: [1], + max_credential_count_in_list: 8, + max_credential_id_length: 128, + transports: ["nfc", "usb"], + algorithms: [ + { alg: -7, type: "public-key" }, + { alg: -8, type: "public-key" }, + ], + max_ser_large_blob_array: null, + force_pin_change: null, + min_pin_length: null, + firmware_version: null, + max_cred_blob_length: null, + max_rpids_for_set_min_pin_length: null, + preferred_platform_uv_attempts: null, + uv_modality: null, + certifications: null, + remaining_discoverable_credentials: null, + vendor_prototype_config_commands: null, +}; + +// Nitrokey 3 +const REAL_AUTH_INFO_3 = { + versions: ["U2F_V2", "FIDO_2_0", "FIDO_2_1_PRE"], + extensions: ["credProtect", "hmac-secret"], + aaguid: [ + 47, 192, 87, 159, 129, 19, 71, 234, 177, 22, 187, 90, 141, 185, 32, 42, + ], + options: { + plat: false, + rk: true, + clientPin: true, + up: true, + uv: null, + pinUvAuthToken: null, + noMcGaPermissionsWithClientPin: null, + largeBlobs: null, + ep: null, + bioEnroll: null, + userVerificationMgmtPreview: null, + uvBioEnroll: null, + authnrCfg: null, + uvAcfg: null, + credMgmt: null, + credentialMgmtPreview: true, + setMinPINLength: null, + makeCredUvNotRqd: null, + alwaysUv: null, + }, + max_msg_size: 1200, + pin_protocols: [1], + max_credential_count_in_list: 8, + max_credential_id_length: 128, + transports: ["nfc", "usb"], + algorithms: [ + { alg: -7, type: "public-key" }, + { alg: -8, type: "public-key" }, + ], + max_ser_large_blob_array: null, + force_pin_change: null, + min_pin_length: null, + firmware_version: null, + max_cred_blob_length: null, + max_rpids_for_set_min_pin_length: null, + preferred_platform_uv_attempts: null, + uv_modality: null, + certifications: null, + remaining_discoverable_credentials: null, + vendor_prototype_config_commands: null, +}; diff --git a/toolkit/components/aboutwebauthn/tests/browser/browser_aboutwebauthn_no_token.js b/toolkit/components/aboutwebauthn/tests/browser/browser_aboutwebauthn_no_token.js new file mode 100644 index 0000000000..f6da6c7146 --- /dev/null +++ b/toolkit/components/aboutwebauthn/tests/browser/browser_aboutwebauthn_no_token.js @@ -0,0 +1,57 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +var doc, tab; + +add_setup(async function () { + info("Starting about:webauthn"); + tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: "about:webauthn", + waitForLoad: true, + }); + + doc = tab.linkedBrowser.contentDocument; +}); + +registerCleanupFunction(async function () { + // Close tab. + await BrowserTestUtils.removeTab(tab); +}); + +add_task(async function verify_page_no_token() { + let info_text = doc.getElementById("info-text-div"); + is(info_text.hidden, false, "info-text-div should be visible"); + let categories = doc.getElementById("categories"); + is(categories.hidden, false, "categories-sidebar should be invisible"); + let dev_info = doc.getElementById("info-tab-button"); + is( + dev_info.getAttribute("selected"), + "true", + "token-info-section not selected by default" + ); + let ctap2_info = doc.getElementById("ctap2-token-info"); + is(ctap2_info.style.display, "none", "ctap2-info-table is visible"); +}); + +add_task(async function verify_no_auth_info() { + let field = doc.getElementById("info-text-field"); + let promise = BrowserTestUtils.waitForMutationCondition( + field, + { attributes: true, attributeFilter: ["data-l10n-id"] }, + () => + field.getAttribute("data-l10n-id") === + "about-webauthn-text-non-ctap2-device" + ); + Services.obs.notifyObservers( + null, + "about-webauthn-prompt", + JSON.stringify({ type: "selected-device", auth_info: null }) + ); + await promise; + + let info_text = doc.getElementById("info-text-div"); + is(info_text.hidden, false); +}); diff --git a/toolkit/components/aboutwebauthn/tests/browser/browser_aboutwebauthn_pin.js b/toolkit/components/aboutwebauthn/tests/browser/browser_aboutwebauthn_pin.js new file mode 100644 index 0000000000..0849b194a4 --- /dev/null +++ b/toolkit/components/aboutwebauthn/tests/browser/browser_aboutwebauthn_pin.js @@ -0,0 +1,218 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const ABOUT_URL = "about:webauthn"; + +var doc, tab; + +async function send_authinfo_and_open_pin_section(ops) { + reset_about_page(doc); + send_auth_info_and_check_categories(doc, ops); + + ["credentials-tab-button", "bio-enrollments-tab-button"].forEach( + button_id => { + let button = doc.getElementById(button_id); + is( + button.style.display, + "none", + button_id + " in the sidebar not hidden" + ); + } + ); + + if (ops.clientPin !== null) { + let pin_tab_button = doc.getElementById("pin-tab-button"); + // Check if PIN section is visible + isnot( + pin_tab_button.style.display, + "none", + "PIN button in the sidebar not visible" + ); + + // Click the section and wait for it to open + let pin_section = doc.getElementById("set-change-pin-section"); + pin_tab_button.click(); + isnot(pin_section.style.display, "none", "PIN section not visible"); + is( + pin_tab_button.getAttribute("selected"), + "true", + "PIN section button not selected" + ); + } +} + +add_setup(async function () { + info("Starting about:webauthn"); + tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: "about:webauthn", + waitForLoad: true, + }); + + doc = tab.linkedBrowser.contentDocument; +}); + +registerCleanupFunction(async function () { + // Close tab. + await BrowserTestUtils.removeTab(tab); +}); + +add_task(async function pin_not_supported() { + // Not setting clientPIN at all should lead to not showing it in the sidebar + send_authinfo_and_open_pin_section({ uv: true, clientPin: null }); + // Check if PIN section is invisible + let pin_tab_button = doc.getElementById("pin-tab-button"); + is(pin_tab_button.style.display, "none", "PIN button in the sidebar visible"); +}); + +add_task(async function pin_already_set() { + send_authinfo_and_open_pin_section({ clientPin: true }); + + let set_pin_button = doc.getElementById("set-pin-button"); + is(set_pin_button.style.display, "none", "Set PIN button visible"); + + let change_pin_button = doc.getElementById("change-pin-button"); + isnot( + change_pin_button.style.display, + "none", + "Change PIN button not visible" + ); + + let current_pin_div = doc.getElementById("current-pin-div"); + is(current_pin_div.hidden, false, "Current PIN field not visible"); + + // Test that the button is only active if both inputs are set the same + let new_pin = doc.getElementById("new-pin"); + let new_pin_repeat = doc.getElementById("new-pin-repeat"); + let current_pin = doc.getElementById("current-pin"); + + set_text(new_pin, "abcdefg"); + is(change_pin_button.disabled, true, "Change PIN button not disabled"); + set_text(new_pin_repeat, "abcde"); + is(change_pin_button.disabled, true, "Change PIN button not disabled"); + set_text(new_pin_repeat, "abcdefg"); + is(change_pin_button.disabled, true, "Change PIN button not disabled"); + set_text(current_pin, "1234567"); + is(change_pin_button.disabled, false, "Change PIN button disabled"); +}); + +add_task(async function pin_not_yet_set() { + send_authinfo_and_open_pin_section({ clientPin: false }); + + let set_pin_button = doc.getElementById("set-pin-button"); + isnot(set_pin_button.style.display, "none", "Set PIN button not visible"); + + let change_pin_button = doc.getElementById("change-pin-button"); + is(change_pin_button.style.display, "none", "Change PIN button visible"); + + let current_pin_div = doc.getElementById("current-pin-div"); + is(current_pin_div.hidden, true, "Current PIN field visible"); + + // Test that the button is only active if both inputs are set the same + let new_pin = doc.getElementById("new-pin"); + let new_pin_repeat = doc.getElementById("new-pin-repeat"); + + set_text(new_pin, "abcdefg"); + is(set_pin_button.disabled, true, "Set PIN button not disabled"); + set_text(new_pin_repeat, "abcde"); + is(set_pin_button.disabled, true, "Set PIN button not disabled"); + set_text(new_pin_repeat, "abcdefg"); + is(set_pin_button.disabled, false, "Set PIN button disabled"); +}); + +add_task(async function pin_switch_back_and_forth() { + // This will click the PIN section button + send_authinfo_and_open_pin_section({ clientPin: false }); + + let pin_tab_button = doc.getElementById("pin-tab-button"); + let pin_section = doc.getElementById("set-change-pin-section"); + let info_tab_button = doc.getElementById("info-tab-button"); + let info_section = doc.getElementById("token-info-section"); + + // a11y-tree is racy here, so we have to wait a tick for it to get up to date + await TestUtils.waitForTick(); + // Now click the "info"-button and verify the correct buttons are highlighted + info_tab_button.click(); + // await info_promise; + isnot(info_section.style.display, "none", "info section not visible"); + is( + info_tab_button.getAttribute("selected"), + "true", + "Info tab button not selected" + ); + isnot( + pin_tab_button.getAttribute("selected"), + "true", + "PIN tab button selected" + ); + + // Click back to the PIN section + pin_tab_button.click(); + isnot(pin_section.style.display, "none", "PIN section not visible"); + is( + pin_tab_button.getAttribute("selected"), + "true", + "PIN tab button not selected" + ); + isnot( + info_tab_button.getAttribute("selected"), + "true", + "Info button selected" + ); +}); + +add_task(async function invalid_pin() { + send_authinfo_and_open_pin_section({ clientPin: true }); + let pin_tab_button = doc.getElementById("pin-tab-button"); + // Click the section and wait for it to open + pin_tab_button.click(); + is( + pin_tab_button.getAttribute("selected"), + "true", + "PIN section button not selected" + ); + + let change_pin_button = doc.getElementById("change-pin-button"); + + // Test that the button is only active if both inputs are set the same + let new_pin = doc.getElementById("new-pin"); + let new_pin_repeat = doc.getElementById("new-pin-repeat"); + let current_pin = doc.getElementById("current-pin"); + + // Needed to activate change_pin_button + set_text(new_pin, "abcdefg"); + set_text(new_pin_repeat, "abcdefg"); + set_text(current_pin, "1234567"); + + // This should silently error out since we have no authenticator + change_pin_button.click(); + + // Now we fake a response from the authenticator, saying the PIN was invalid + let pin_required = doc.getElementById("pin-required-section"); + let msg = JSON.stringify({ type: "pin-required" }); + Services.obs.notifyObservers(null, "about-webauthn-prompt", msg); + isnot(pin_required.style.display, "none", "PIN required dialog not visible"); + + let info_tab_button = doc.getElementById("info-tab-button"); + is( + info_tab_button.classList.contains("disabled-category"), + true, + "Sidebar not disabled" + ); + + let pin_field = doc.getElementById("pin-required"); + let send_pin_button = doc.getElementById("send-pin-button"); + + set_text(pin_field, "654321"); + send_pin_button.click(); + + is( + pin_required.style.display, + "none", + "PIN required dialog did not disappear" + ); + let pin_section = doc.getElementById("set-change-pin-section"); + isnot(pin_section.style.display, "none", "PIN section did not reappear"); +}); diff --git a/toolkit/components/aboutwebauthn/tests/browser/head.js b/toolkit/components/aboutwebauthn/tests/browser/head.js new file mode 100644 index 0000000000..f6ea51ab3a --- /dev/null +++ b/toolkit/components/aboutwebauthn/tests/browser/head.js @@ -0,0 +1,40 @@ +function set_text(field, text) { + field.value = text; + field.dispatchEvent(new Event("input")); +} + +async function reset_about_page(doc) { + let info_text = doc.getElementById("info-text-div"); + let msg = JSON.stringify({ type: "listen-success" }); + let promise = BrowserTestUtils.waitForMutationCondition( + info_text, + { attributes: true, attributeFilter: ["hidden"] }, + () => info_text.hidden !== false + ); + Services.obs.notifyObservers(null, "about-webauthn-prompt", msg); + await promise; +} + +async function send_auth_info_and_check_categories(doc, ops) { + let info_text = doc.getElementById("info-text-div"); + let msg = JSON.stringify({ + type: "selected-device", + auth_info: { options: ops }, + }); + + let promise = BrowserTestUtils.waitForMutationCondition( + info_text, + { attributes: true, attributeFilter: ["hidden"] }, + () => info_text.hidden + ); + Services.obs.notifyObservers(null, "about-webauthn-prompt", msg); + await promise; + + // Info should be shown always, so we use it as a canary + let info_tab_button = doc.getElementById("info-tab-button"); + isnot( + info_tab_button.style.display, + "none", + "Info button in the sidebar not visible" + ); +} |