diff options
Diffstat (limited to 'toolkit/components/credentialmanagement/tests')
9 files changed, 1377 insertions, 0 deletions
diff --git a/toolkit/components/credentialmanagement/tests/browser/browser.ini b/toolkit/components/credentialmanagement/tests/browser/browser.ini new file mode 100644 index 0000000000..2d81970265 --- /dev/null +++ b/toolkit/components/credentialmanagement/tests/browser/browser.ini @@ -0,0 +1,14 @@ +[DEFAULT] +head = head.js +prefs = + dom.security.credentialmanagement.identity.enabled=true + dom.security.credentialmanagement.identity.ignore_well_known=true + privacy.antitracking.enableWebcompat=false # disables opener heuristic +scheme = https + +support-files = + custom.svg + +[browser_account_dialog.js] +[browser_policy_dialog.js] +[browser_provider_dialog.js] diff --git a/toolkit/components/credentialmanagement/tests/browser/browser_account_dialog.js b/toolkit/components/credentialmanagement/tests/browser/browser_account_dialog.js new file mode 100644 index 0000000000..44406715b0 --- /dev/null +++ b/toolkit/components/credentialmanagement/tests/browser/browser_account_dialog.js @@ -0,0 +1,455 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +XPCOMUtils.defineLazyServiceGetter( + this, + "IdentityCredentialPromptService", + "@mozilla.org/browser/identitycredentialpromptservice;1", + "nsIIdentityCredentialPromptService" +); + +const TEST_URL = "https://example.com/"; + +// Test that a single account shows up in a dialog and is chosen when "continue" is clicked +add_task(async function test_single_acccount_dialog() { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); + + let popupShown = BrowserTestUtils.waitForEvent( + PopupNotifications.panel, + "popupshown" + ); + + // Show the single account + let prompt = IdentityCredentialPromptService.showAccountListPrompt( + tab.linkedBrowser.browsingContext, + { + accounts: [ + { + id: "00000000-0000-0000-0000-000000000000", + name: "Test Account", + email: "test@idp.example", + }, + ], + }, + { + configURL: "https://idp.example/", + clientId: "123", + }, + { + accounts_endpoint: "", + client_metadata_endpoint: "", + id_assertion_endpoint: "", + } + ); + + // Wait for the popup to appear + await popupShown; + + let popupHiding = BrowserTestUtils.waitForEvent( + PopupNotifications.panel, + "popuphiding" + ); + + let document = tab.linkedBrowser.browsingContext.topChromeWindow.document; + + // Validate the popup contents + let inputs = document + .getElementById("identity-credential-account") + .getElementsByClassName("identity-credential-list-item"); + is(inputs.length, 1, "One account expected"); + let label = inputs[0].getElementsByClassName( + "identity-credential-list-item-label-stack" + )[0]; + ok( + label.textContent.includes("Test Account"), + "Label includes the account name" + ); + ok( + label.textContent.includes("test@idp.example"), + "Label includes the account email" + ); + + let title = document.getElementById("identity-credential-header-text"); + ok( + title.textContent.includes("idp.example"), + "Popup title includes the IDP Site" + ); + + // Click Continue + document + .getElementsByClassName("popup-notification-primary-button")[0] + .click(); + + // Make sure that the prompt resolves with the index of the only argument element + let value = await prompt; + is(value, 0); + + await popupHiding; + + // Close tabs. + await BrowserTestUtils.removeTab(tab); +}); + +// Test that no account is chosen when "cancel" is clicked +add_task(async function test_single_acccount_deny() { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); + + let popupShown = BrowserTestUtils.waitForEvent( + PopupNotifications.panel, + "popupshown" + ); + + // Show a prompt with one account + let prompt = IdentityCredentialPromptService.showAccountListPrompt( + tab.linkedBrowser.browsingContext, + { + accounts: [ + { + id: "00000000-0000-0000-0000-000000000000", + name: "Test Account", + email: "test@idp.example", + }, + ], + }, + { + configURL: "https://idp.example/", + clientId: "123", + }, + { + accounts_endpoint: "", + client_metadata_endpoint: "", + id_assertion_endpoint: "", + } + ); + + // Wait for that popup + await popupShown; + + let popupHiding = BrowserTestUtils.waitForEvent( + PopupNotifications.panel, + "popuphiding" + ); + + let document = tab.linkedBrowser.browsingContext.topChromeWindow.document; + + // Validate the popup contents + let inputs = document + .getElementById("identity-credential-account") + .getElementsByClassName("identity-credential-list-item"); + is(inputs.length, 1, "One account expected"); + let label = inputs[0].getElementsByClassName( + "identity-credential-list-item-label-stack" + )[0]; + ok( + label.textContent.includes("Test Account"), + "Label includes the account name" + ); + ok( + label.textContent.includes("test@idp.example"), + "Label includes the account email" + ); + + // Click cancel + document + .getElementsByClassName("popup-notification-secondary-button")[0] + .click(); + + // Make sure we reject + try { + await prompt; + ok(false, "Prompt should not resolve when denied."); + } catch (e) { + ok(true, "Prompt should reject when denied."); + } + + await popupHiding; + + // Close tabs. + await BrowserTestUtils.removeTab(tab); +}); + +// Show multiple accounts and select the first one +add_task(async function test_multiple_acccount_dialog() { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); + + let popupShown = BrowserTestUtils.waitForEvent( + PopupNotifications.panel, + "popupshown" + ); + + // Show a prompt with multiple accounts + let prompt = IdentityCredentialPromptService.showAccountListPrompt( + tab.linkedBrowser.browsingContext, + { + accounts: [ + { + id: "00000000-0000-0000-0000-000000000000", + name: "Test Account", + email: "test@idp.example", + }, + { + id: "00000000-0000-0000-0000-000000000000", + name: "Test Account 2", + email: "test2@idp.example", + }, + ], + }, + { + configURL: "https://idp.example/", + clientId: "123", + }, + { + accounts_endpoint: "", + client_metadata_endpoint: "", + id_assertion_endpoint: "", + } + ); + + // Wait for that popup to appear + await popupShown; + + let popupHiding = BrowserTestUtils.waitForEvent( + PopupNotifications.panel, + "popuphiding" + ); + + let document = tab.linkedBrowser.browsingContext.topChromeWindow.document; + + // Validate the account list contents and ordering + let inputs = document + .getElementById("identity-credential-account") + .getElementsByClassName("identity-credential-list-item"); + is(inputs.length, 2, "Two accounts expected"); + let label0 = inputs[0].getElementsByClassName( + "identity-credential-list-item-label-stack" + )[0]; + ok( + label0.textContent.includes("Test Account"), + "The first account name should be in the label" + ); + ok( + label0.textContent.includes("test@idp.example"), + "The first account email should be in the label" + ); + let label1 = inputs[1].getElementsByClassName( + "identity-credential-list-item-label-stack" + )[0]; + ok( + label1.textContent.includes("Test Account 2"), + "The second account name should be in the label" + ); + ok( + label1.textContent.includes("test2@idp.example"), + "The second account email should be in the label" + ); + + // Click continue + document + .getElementsByClassName("popup-notification-primary-button")[0] + .click(); + + // Validate that the caller gets a resolving promise with index of the first element + let value = await prompt; + is(value, 0, "The first account is chosen by default"); + + await popupHiding; + + // Close tabs. + await BrowserTestUtils.removeTab(tab); +}); + +// Show multiple accounts and select the second one +add_task(async function test_multiple_acccount_choose_second() { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); + + let popupShown = BrowserTestUtils.waitForEvent( + PopupNotifications.panel, + "popupshown" + ); + + // Show a prompt with multiple accounts + let prompt = IdentityCredentialPromptService.showAccountListPrompt( + tab.linkedBrowser.browsingContext, + { + accounts: [ + { + id: "00000000-0000-0000-0000-000000000000", + name: "Test Account", + email: "test@idp.example", + }, + { + id: "00000000-0000-0000-0000-000000000000", + name: "Test Account 2", + email: "test2@idp.example", + }, + ], + }, + { + configURL: "https://idp.example/", + clientId: "123", + }, + { + accounts_endpoint: "", + client_metadata_endpoint: "", + id_assertion_endpoint: "", + } + ); + + // Wait for that popup + await popupShown; + + let popupHiding = BrowserTestUtils.waitForEvent( + PopupNotifications.panel, + "popuphiding" + ); + + let document = tab.linkedBrowser.browsingContext.topChromeWindow.document; + + // Validate the account list contents and ordering + let inputs = document + .getElementById("identity-credential-account") + .getElementsByClassName("identity-credential-list-item"); + is(inputs.length, 2, "Two accounts expected"); + let label0 = inputs[0].getElementsByClassName( + "identity-credential-list-item-label-stack" + )[0]; + ok( + label0.textContent.includes("Test Account"), + "The first account name should be in the label" + ); + ok( + label0.textContent.includes("test@idp.example"), + "The first account email should be in the label" + ); + let label1 = inputs[1].getElementsByClassName( + "identity-credential-list-item-label-stack" + )[0]; + ok( + label1.textContent.includes("Test Account 2"), + "The second account name should be in the label" + ); + ok( + label1.textContent.includes("test2@idp.example"), + "The second account email should be in the label" + ); + + // Click the second account + inputs[1].click(); + + // Click continue + document + .getElementsByClassName("popup-notification-primary-button")[0] + .click(); + + // Make sure we selected the second account + let value = await prompt; + is(value, 1, "The prompt should resolve to 1, indicating the second account"); + + await popupHiding; + + // Close tabs. + await BrowserTestUtils.removeTab(tab); +}); + +// Test that account pictures are rendered for the user +add_task(async function test_multiple_acccount_show_picture() { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); + + let popupShown = BrowserTestUtils.waitForEvent( + PopupNotifications.panel, + "popupshown" + ); + + // Show a prompt with two accounts, but only the first has a custom picture + let prompt = IdentityCredentialPromptService.showAccountListPrompt( + tab.linkedBrowser.browsingContext, + { + accounts: [ + { + id: "00000000-0000-0000-0000-000000000000", + name: "Test Account", + email: "test@idp.example", + picture: + "https://example.net/browser/toolkit/components/credentialmanagement/tests/browser/custom.svg", + }, + { + id: "00000000-0000-0000-0000-000000000000", + name: "Test Account 2", + email: "test2@idp.example", + }, + ], + }, + { + configURL: "https://idp.example/", + clientId: "123", + }, + { + accounts_endpoint: "", + client_metadata_endpoint: "", + id_assertion_endpoint: "", + privacy_policy_url: "https://idp.example/privacy-policy.html", + terms_of_service_url: "https://idp.example/terms-of-service.html", + branding: { + background_color: "0x6200ee", + color: "0xffffff", + icons: [ + { + size: 256, + url: "https://example.net/browser/toolkit/components/credentialmanagement/tests/browser/custom.svg", + }, + ], + }, + } + ); + + // Wait for that popup + await popupShown; + + let popupHiding = BrowserTestUtils.waitForEvent( + PopupNotifications.panel, + "popuphiding" + ); + + let document = tab.linkedBrowser.browsingContext.topChromeWindow.document; + + let icons = document + .getElementById("identity-credential-account") + .getElementsByClassName("identity-credential-list-item-icon"); + is(icons.length, 2, "Two accounts expected"); + ok(icons[0].src != icons[1].src, "The icons are different"); + ok( + icons[0].src.startsWith( + "data:image/svg+xml;base64,PCEtLSBUaGlzIFNvdXJjZSBDb2RlIEZvcm0gaXMgc3ViamVjdCB0byB0aGUgdGVybXMgb2YgdGhlIE1vemlsbGEgUHVibGljCiAgIC0gTGljZW5zZSwgdi4gMi4wLiBJZiBhIGNvcHkgb2YgdGhlIE1QTCB3YXMgbm90IGRpc3RyaWJ1dGVkIHdpdGggdGhpcwogICAtIGZpbGUsIFlvdSBjYW4gb2J0YWluIG9uZSBhdCBodHRwOi8vbW96aWxsYS5vcmcvTVBMLzIuMC8uIC0tPgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDE2IDE2IiB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIGZpbGw9ImNvbnRleHQtZmlsbCIgZmlsbC1vcGFjaXR5PSJjb250ZXh0LWZpbGwtb3BhY2l0eSI+CiAgPHBhdGggZD0iTS42MjUgMTNhLjYyNS42MjUgMCAwIDEgMC0xLjI1bDMuMjUgMEE0Ljg4IDQuODggMCAwIDAgOC43NSA2Ljg3NWwwLS4yNWEuNjI1LjYyNSAwIDAgMSAxLjI1IDBsMCAuMjVBNi4xMzIgNi4xMzIgMCAwIDEgMy44NzUgMTNsLTMuMjUgMHoiLz" + ), + "The first icon matches the custom.svg" + ); + + const headerIcon = document.getElementsByClassName( + "identity-credential-header-icon" + )[0]; + + ok(BrowserTestUtils.is_visible(headerIcon), "Header Icon is showing"); + ok( + headerIcon.src.startsWith( + "data:image/svg+xml;base64,PCEtLSBUaGlzIFNvdXJjZSBDb2RlIEZvcm0gaXMgc3ViamVjdCB0byB0aGUgdGVybXMgb2YgdGhlIE1vemlsbGEgUHVibGljCiAgIC0gTGljZW5zZSwgdi4gMi4wLiBJZiBhIGNvcHkgb2YgdGhlIE1QTCB3YXMgbm90IGRpc3RyaWJ1dGVkIHdpdGggdGhpcwogICAtIGZpbGUsIFlvdSBjYW4gb2J0YWluIG9uZSBhdCBodHRwOi8vbW96aWxsYS5vcmcvTVBMLzIuMC8uIC0tPgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDE2IDE2IiB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIGZpbGw9ImNvbnRleHQtZmlsbCIgZmlsbC1vcGFjaXR5PSJjb250ZXh0LWZpbGwtb3BhY2l0eSI+CiAgPHBhdGggZD0iTS42MjUgMTNhLjYyNS42MjUgMCAwIDEgMC0xLjI1bDMuMjUgMEE0Ljg4IDQuODggMCAwIDAgOC43NSA2Ljg3NWwwLS4yNWEuNjI1LjYyNSAwIDAgMSAxLjI1IDBsMCAuMjVBNi4xMzIgNi4xMzIgMCAwIDEgMy44NzUgMTNsLTMuMjUgMHoiLz" + ), + "The header icon matches the icon resource from manifest" + ); + + // Click Continue + document + .getElementsByClassName("popup-notification-primary-button")[0] + .click(); + + // Make sure that the prompt resolves with the index of the only argument element + let value = await prompt; + is(value, 0); + + await popupHiding; + + // Close tabs. + await BrowserTestUtils.removeTab(tab); +}); diff --git a/toolkit/components/credentialmanagement/tests/browser/browser_policy_dialog.js b/toolkit/components/credentialmanagement/tests/browser/browser_policy_dialog.js new file mode 100644 index 0000000000..09b420c6fc --- /dev/null +++ b/toolkit/components/credentialmanagement/tests/browser/browser_policy_dialog.js @@ -0,0 +1,197 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +XPCOMUtils.defineLazyServiceGetter( + this, + "IdentityCredentialPromptService", + "@mozilla.org/browser/identitycredentialpromptservice;1", + "nsIIdentityCredentialPromptService" +); + +const TEST_URL = "https://example.com/"; + +// Test that a policy dialog does not appear when no policies are given +add_task(async function test_policy_dialog_empty() { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); + + let prompt = IdentityCredentialPromptService.showPolicyPrompt( + tab.linkedBrowser.browsingContext, + { + configURL: "https://idp.example/", + clientId: "123", + }, + { + accounts_endpoint: "", + client_metadata_endpoint: "", + id_assertion_endpoint: "", + }, + {} // No policies! + ); + + // Make sure we resolve with true without interaction + let value = await prompt; + is(value, true, "Automatically accept the missing policies"); + + // Close tab + await BrowserTestUtils.removeTab(tab); +}); + +// Make sure that a policy dialog shows up when we have policies to show. +// Also test the accept path. +add_task(async function test_policy_dialog() { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); + + let popupShown = BrowserTestUtils.waitForEvent( + PopupNotifications.panel, + "popupshown" + ); + + // Show a prompt- the operative argument is the last one + let prompt = IdentityCredentialPromptService.showPolicyPrompt( + tab.linkedBrowser.browsingContext, + { + configURL: "https://idp.example/", + clientId: "123", + }, + { + accounts_endpoint: "", + client_metadata_endpoint: "", + id_assertion_endpoint: "", + branding: { + background_color: "0x6200ee", + color: "0xffffff", + icons: [ + { + size: 256, + url: "https://example.net/browser/toolkit/components/credentialmanagement/tests/browser/custom.svg", + }, + ], + }, + }, + { + privacy_policy_url: "https://idp.example/privacy-policy.html", + terms_of_service_url: "https://idp.example/terms-of-service.html", + } + ); + + // Make sure the popup shows up + await popupShown; + + let popupHiding = BrowserTestUtils.waitForEvent( + PopupNotifications.panel, + "popuphiding" + ); + + // Validate the contents of the popup + let document = tab.linkedBrowser.browsingContext.topChromeWindow.document; + + let description = document.getElementById( + "identity-credential-policy-explanation" + ); + + ok( + description.textContent.includes("idp.example"), + "IDP domain in the policy prompt text" + ); + ok( + description.textContent.includes("example.com"), + "RP domain in the policy prompt text" + ); + ok( + description.textContent.includes("Privacy Policy"), + "Link to the privacy policy in the policy prompt text" + ); + ok( + description.textContent.includes("Terms of Service"), + "Link to the ToS in the policy prompt text" + ); + + let title = document.getElementById("identity-credential-header-text"); + ok( + title.textContent.includes("idp.example"), + "IDP domain in the policy prompt header" + ); + + const headerIcon = document.getElementsByClassName( + "identity-credential-header-icon" + )[0]; + + ok(BrowserTestUtils.is_visible(headerIcon), "Header Icon is showing"); + ok( + headerIcon.src.startsWith( + "data:image/svg+xml;base64,PCEtLSBUaGlzIFNvdXJjZSBDb2RlIEZvcm0gaXMgc3ViamVjdCB0byB0aGUgdGVybXMgb2YgdGhlIE1vemlsbGEgUHVibGljCiAgIC0gTGljZW5zZSwgdi4gMi4wLiBJZiBhIGNvcHkgb2YgdGhlIE1QTCB3YXMgbm90IGRpc3RyaWJ1dGVkIHdpdGggdGhpcwogICAtIGZpbGUsIFlvdSBjYW4gb2J0YWluIG9uZSBhdCBodHRwOi8vbW96aWxsYS5vcmcvTVBMLzIuMC8uIC0tPgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDE2IDE2IiB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIGZpbGw9ImNvbnRleHQtZmlsbCIgZmlsbC1vcGFjaXR5PSJjb250ZXh0LWZpbGwtb3BhY2l0eSI+CiAgPHBhdGggZD0iTS42MjUgMTNhLjYyNS42MjUgMCAwIDEgMC0xLjI1bDMuMjUgMEE0Ljg4IDQuODggMCAwIDAgOC43NSA2Ljg3NWwwLS4yNWEuNjI1LjYyNSAwIDAgMSAxLjI1IDBsMCAuMjVBNi4xMzIgNi4xMzIgMCAwIDEgMy44NzUgMTNsLTMuMjUgMHoiLz" + ), + "The header icon matches the icon resource from manifest" + ); + + // Accept the policies + document + .getElementsByClassName("popup-notification-primary-button")[0] + .click(); + + // Make sure the call to the propmt resolves with true + let value = await prompt; + is(value, true, "User clicking accept resolves with true"); + + // Wait for the prompt to go away + await popupHiding; + + // Close tab + await BrowserTestUtils.removeTab(tab); +}); + +// Test that rejecting the policies works +add_task(async function test_policy_reject() { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); + + let popupShown = BrowserTestUtils.waitForEvent( + PopupNotifications.panel, + "popupshown" + ); + + // Show the same prompt with policies + let prompt = IdentityCredentialPromptService.showPolicyPrompt( + tab.linkedBrowser.browsingContext, + { + configURL: "https://idp.example/", + clientId: "123", + }, + { + accounts_endpoint: "", + client_metadata_endpoint: "", + id_assertion_endpoint: "", + }, + { + privacy_policy_url: "https://idp.example/privacy-policy.html", + terms_of_service_url: "https://idp.example/terms-of-service.html", + } + ); + + // Wait for the prompt to show up + await popupShown; + + let popupHiding = BrowserTestUtils.waitForEvent( + PopupNotifications.panel, + "popuphiding" + ); + + let document = tab.linkedBrowser.browsingContext.topChromeWindow.document; + + // Click reject. + document + .getElementsByClassName("popup-notification-secondary-button")[0] + .click(); + + // Make sure the prompt call accepts with an indication of the user's reject choice. + let value = await prompt; + is(value, false, "User clicking reject causes the promise to resolve(false)"); + + // Wait for the popup to go away. + await popupHiding; + + // Close tab. + await BrowserTestUtils.removeTab(tab); +}); diff --git a/toolkit/components/credentialmanagement/tests/browser/browser_provider_dialog.js b/toolkit/components/credentialmanagement/tests/browser/browser_provider_dialog.js new file mode 100644 index 0000000000..60d9d03da7 --- /dev/null +++ b/toolkit/components/credentialmanagement/tests/browser/browser_provider_dialog.js @@ -0,0 +1,386 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +XPCOMUtils.defineLazyServiceGetter( + this, + "IdentityCredentialPromptService", + "@mozilla.org/browser/identitycredentialpromptservice;1", + "nsIIdentityCredentialPromptService" +); + +const TEST_URL = "https://example.com/"; + +// Test that a single provider shows up in a dialog and is chosen when "continue" is clicked +add_task(async function test_single_provider_dialog() { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); + + let popupShown = BrowserTestUtils.waitForEvent( + PopupNotifications.panel, + "popupshown" + ); + + // Show one IDP + let prompt = IdentityCredentialPromptService.showProviderPrompt( + tab.linkedBrowser.browsingContext, + [ + { + configURL: "https://idp.example/", + clientId: "123", + }, + ], + [ + { + accounts_endpoint: "", + client_metadata_endpoint: "", + id_assertion_endpoint: "", + }, + ] + ); + + // Make sure a popup shows up. + await popupShown; + + let popupHiding = BrowserTestUtils.waitForEvent( + PopupNotifications.panel, + "popuphiding" + ); + + let document = tab.linkedBrowser.browsingContext.topChromeWindow.document; + + // Make sure there is only one option + let inputs = document + .getElementById("identity-credential-provider") + .getElementsByClassName("identity-credential-list-item"); + is(inputs.length, 1, "One IDP"); + + // Make sure the IDP Site is in the label + let label = inputs[0].getElementsByClassName( + "identity-credential-list-item-label" + )[0]; + ok(label.textContent.includes("idp.example"), "IDP site in label"); + + // Validate the title of the popup + let title = document.querySelector( + 'description[popupid="identity-credential"]' + ); + ok( + title.textContent.includes("Sign in with a login provider"), + "Popup title correct" + ); + + // Click "Continue" + document + .getElementsByClassName("popup-notification-primary-button")[0] + .click(); + + // Make sure the prompt promise resolves + let value = await prompt; + is(value, 0, "Selected the only IDP"); + + await popupHiding; + + // Close tabs. + await BrowserTestUtils.removeTab(tab); +}); + +// Test that a single provider shows up in a dialog and is not chosen when "cancel" is clicked +add_task(async function test_single_provider_deny() { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); + + let popupShown = BrowserTestUtils.waitForEvent( + PopupNotifications.panel, + "popupshown" + ); + + // Show one IDP + let prompt = IdentityCredentialPromptService.showProviderPrompt( + tab.linkedBrowser.browsingContext, + [ + { + configURL: "https://idp.example/", + clientId: "123", + }, + ], + [ + { + accounts_endpoint: "", + client_metadata_endpoint: "", + id_assertion_endpoint: "", + }, + ] + ); + + // Make sure a popup shows up. + await popupShown; + + let popupHiding = BrowserTestUtils.waitForEvent( + PopupNotifications.panel, + "popuphiding" + ); + + // Click cancel + let document = tab.linkedBrowser.browsingContext.topChromeWindow.document; + document + .getElementsByClassName("popup-notification-secondary-button")[0] + .click(); + + try { + await prompt; + ok(false, "Prompt should not resolve when denied."); + } catch (e) { + ok(true, "Prompt should reject when denied."); + } + + await popupHiding; + + // Close tabs. + await BrowserTestUtils.removeTab(tab); +}); + +// Show multiple IDPs and select the first one +add_task(async function test_multiple_provider_dialog() { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); + + let popupShown = BrowserTestUtils.waitForEvent( + PopupNotifications.panel, + "popupshown" + ); + + // Show two providers, in order. (we don't need the metadata because we aren't testing branding) + let prompt = IdentityCredentialPromptService.showProviderPrompt( + tab.linkedBrowser.browsingContext, + [ + { + configURL: "https://idp1.example/", + clientId: "123", + }, + { + configURL: "https://idp2.example/", + clientId: "123", + }, + ], + [ + { + accounts_endpoint: "", + client_metadata_endpoint: "", + id_assertion_endpoint: "", + }, + { + accounts_endpoint: "", + client_metadata_endpoint: "", + id_assertion_endpoint: "", + }, + ] + ); + + // Make sure the popup shows up + await popupShown; + + let popupHiding = BrowserTestUtils.waitForEvent( + PopupNotifications.panel, + "popuphiding" + ); + + let document = tab.linkedBrowser.browsingContext.topChromeWindow.document; + + let inputs = document + .getElementById("identity-credential-provider") + .getElementsByClassName("identity-credential-list-item"); + is(inputs.length, 2, "Two IDPs visible"); + + let label1 = inputs[0].getElementsByClassName( + "identity-credential-list-item-label" + )[0]; + ok( + label1.textContent.includes("idp1.example"), + "First IDP label includes its site" + ); + let label2 = inputs[1].getElementsByClassName( + "identity-credential-list-item-label" + )[0]; + ok( + label2.textContent.includes("idp2.example"), + "Second IDP label includes its site" + ); + + // Click continue + document + .getElementsByClassName("popup-notification-primary-button")[0] + .click(); + + let value = await prompt; + is(value, 0, "The default is the first option in the list"); + + await popupHiding; + + // Close tabs. + await BrowserTestUtils.removeTab(tab); +}); + +// Show multiple IDPs and select the second one +add_task(async function test_multiple_provider_choose_second() { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); + + let popupShown = BrowserTestUtils.waitForEvent( + PopupNotifications.panel, + "popupshown" + ); + + // Show two providers, in order. (we don't need the metadata because we aren't testing branding) + let prompt = IdentityCredentialPromptService.showProviderPrompt( + tab.linkedBrowser.browsingContext, + [ + { + configURL: "https://idp1.example/", + clientId: "123", + }, + { + configURL: "https://idp2.example/", + clientId: "123", + }, + ], + [ + { + accounts_endpoint: "", + client_metadata_endpoint: "", + id_assertion_endpoint: "", + }, + { + accounts_endpoint: "", + client_metadata_endpoint: "", + id_assertion_endpoint: "", + }, + ] + ); + + // Wait for the popup + await popupShown; + + let popupHiding = BrowserTestUtils.waitForEvent( + PopupNotifications.panel, + "popuphiding" + ); + + let document = tab.linkedBrowser.browsingContext.topChromeWindow.document; + + let inputs = document + .getElementById("identity-credential-provider") + .getElementsByClassName("identity-credential-list-item"); + is(inputs.length, 2, "Two IDPs visible"); + + let label1 = inputs[0].getElementsByClassName( + "identity-credential-list-item-label" + )[0]; + ok( + label1.textContent.includes("idp1.example"), + "First IDP label includes its site" + ); + let label2 = inputs[1].getElementsByClassName( + "identity-credential-list-item-label" + )[0]; + ok( + label2.textContent.includes("idp2.example"), + "Second IDP label includes its site" + ); + + // Click the second list item + inputs[1].click(); + + // Click continue + document + .getElementsByClassName("popup-notification-primary-button")[0] + .click(); + + // Make sure the caller gets the second IDP + let value = await prompt; + is(value, 1, "Choosing a different option makes a change"); + + await popupHiding; + + // Close tabs. + await BrowserTestUtils.removeTab(tab); +}); + +// Validate that the branding information is rendered correctly +add_task(async function test_multiple_provider_show_branding() { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); + + let popupShown = BrowserTestUtils.waitForEvent( + PopupNotifications.panel, + "popupshown" + ); + + // Show the prompt, but include an icon for the second IDP + let prompt = IdentityCredentialPromptService.showProviderPrompt( + tab.linkedBrowser.browsingContext, + [ + { + configURL: "https://idp1.example/", + clientId: "123", + }, + { + configURL: "https://idp2.example/", + clientId: "123", + }, + ], + [ + { + accounts_endpoint: "", + client_metadata_endpoint: "", + id_assertion_endpoint: "", + }, + { + accounts_endpoint: "", + client_metadata_endpoint: "", + id_assertion_endpoint: "", + branding: { + icons: [ + { + url: "https://example.net/browser/toolkit/components/credentialmanagement/tests/browser/custom.svg", + }, + ], + }, + }, + ] + ); + + // Wait for that popup + await popupShown; + + let popupHiding = BrowserTestUtils.waitForEvent( + PopupNotifications.panel, + "popuphiding" + ); + + let document = tab.linkedBrowser.browsingContext.topChromeWindow.document; + + // Validate the icons + let icons = document + .getElementById("identity-credential-provider") + .getElementsByClassName("identity-credential-list-item-icon"); + is(icons.length, 2, "Two icons in the popup"); + ok(icons[0].src != icons[1].src, "Icons are different"); + ok( + icons[1].src.startsWith( + "data:image/svg+xml;base64,PCEtLSBUaGlzIFNvdXJjZSBDb2RlIEZvcm0gaXMgc3ViamVjdCB0byB0aGUgdGVybXMgb2YgdGhlIE1vemlsbGEgUHVibGljCiAgIC0gTGljZW5zZSwgdi4gMi4wLiBJZiBhIGNvcHkgb2YgdGhlIE1QTCB3YXMgbm90IGRpc3RyaWJ1dGVkIHdpdGggdGhpcwogICAtIGZpbGUsIFlvdSBjYW4gb2J0YWluIG9uZSBhdCBodHRwOi8vbW96aWxsYS5vcmcvTVBMLzIuMC8uIC0tPgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDE2IDE2IiB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIGZpbGw9ImNvbnRleHQtZmlsbCIgZmlsbC1vcGFjaXR5PSJjb250ZXh0LWZpbGwtb3BhY2l0eSI+CiAgPHBhdGggZD0iTS42MjUgMTNhLjYyNS42MjUgMCAwIDEgMC0xLjI1bDMuMjUgMEE0Ljg4IDQuODggMCAwIDAgOC43NSA2Ljg3NWwwLS4yNWEuNjI1LjYyNSAwIDAgMSAxLjI1IDBsMCAuMjVBNi4xMzIgNi4xMzIgMCAwIDEgMy44NzUgMTNsLTMuMjUgMHoiLz" + ), + "The second icon matches the custom.svg" + ); + + // Click continue + document + .getElementsByClassName("popup-notification-primary-button")[0] + .click(); + + // Make sure the caller gets the first provider still + let value = await prompt; + is(value, 0, "The default is the first option in the list"); + + await popupHiding; + + // Close tabs. + await BrowserTestUtils.removeTab(tab); +}); diff --git a/toolkit/components/credentialmanagement/tests/browser/custom.svg b/toolkit/components/credentialmanagement/tests/browser/custom.svg new file mode 100644 index 0000000000..320d516b07 --- /dev/null +++ b/toolkit/components/credentialmanagement/tests/browser/custom.svg @@ -0,0 +1,8 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity"> + <path d="M.625 13a.625.625 0 0 1 0-1.25l3.25 0A4.88 4.88 0 0 0 8.75 6.875l0-.25a.625.625 0 0 1 1.25 0l0 .25A6.132 6.132 0 0 1 3.875 13l-3.25 0z"/> + <path d="M12.096 15.248a.625.625 0 0 1-.491-1.012c4.281-5.446 3.261-8.813 2.271-10.337-1.346-2.07-4.169-3.106-6.71-2.464-2.428.615-3.951 2.583-4.178 5.397-.027.345-.364.585-.673.574a.626.626 0 0 1-.573-.673C2.011 3.4 3.924.967 6.859.224c3.044-.768 6.437.489 8.064 2.995 1.172 1.803 2.426 5.732-2.337 11.791a.622.622 0 0 1-.49.238z"/> + <path d="M5.734 16a.624.624 0 0 1-.165-1.228c5.912-1.62 6.147-7.768 6.148-7.829a2.113 2.113 0 0 0-.457-1.708 2.713 2.713 0 0 0-1.689-.947c-.683-.109-1.357.021-1.902.371-.521.334-.859.83-.95 1.396l-.217 1.209a3.621 3.621 0 0 1-3.567 2.986l-2.31 0a.625.625 0 0 1 0-1.25l2.31 0a2.373 2.373 0 0 0 2.337-1.957l.215-1.196c.146-.912.683-1.711 1.509-2.24a3.95 3.95 0 0 1 2.774-.553 3.964 3.964 0 0 1 2.461 1.394c.618.762.878 1.688.729 2.611.005 0-.27 7.059-7.061 8.919a.619.619 0 0 1-.165.022z"/> +</svg> diff --git a/toolkit/components/credentialmanagement/tests/browser/head.js b/toolkit/components/credentialmanagement/tests/browser/head.js new file mode 100644 index 0000000000..d646d2b39d --- /dev/null +++ b/toolkit/components/credentialmanagement/tests/browser/head.js @@ -0,0 +1,2 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ diff --git a/toolkit/components/credentialmanagement/tests/xpcshell/head.js b/toolkit/components/credentialmanagement/tests/xpcshell/head.js new file mode 100644 index 0000000000..4614e91961 --- /dev/null +++ b/toolkit/components/credentialmanagement/tests/xpcshell/head.js @@ -0,0 +1,9 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); +const { TestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" +); diff --git a/toolkit/components/credentialmanagement/tests/xpcshell/test_identity_credential_storage_service.js b/toolkit/components/credentialmanagement/tests/xpcshell/test_identity_credential_storage_service.js new file mode 100644 index 0000000000..95ee8042cd --- /dev/null +++ b/toolkit/components/credentialmanagement/tests/xpcshell/test_identity_credential_storage_service.js @@ -0,0 +1,300 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +XPCOMUtils.defineLazyServiceGetter( + this, + "IdentityCredentialStorageService", + "@mozilla.org/browser/identity-credential-storage-service;1", + "nsIIdentityCredentialStorageService" +); + +do_get_profile(); + +add_task(async function test_insert_and_delete() { + let rpPrincipal = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI("https://rp.com/"), + {} + ); + let idpPrincipal = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI("https://idp.com/"), + {} + ); + const credentialID = "ID"; + + // Test initial value + let registered = {}; + let allowLogout = {}; + IdentityCredentialStorageService.getState( + rpPrincipal, + idpPrincipal, + credentialID, + registered, + allowLogout + ); + Assert.ok(!registered.value, "Should not be registered initially."); + Assert.ok(!allowLogout.value, "Should not allow logout initially."); + + // Set and read a value + IdentityCredentialStorageService.setState( + rpPrincipal, + idpPrincipal, + credentialID, + true, + true + ); + IdentityCredentialStorageService.getState( + rpPrincipal, + idpPrincipal, + credentialID, + registered, + allowLogout + ); + Assert.ok(registered.value, "Should be registered by set."); + Assert.ok(allowLogout.value, "Should now allow logout by set."); + + IdentityCredentialStorageService.delete( + rpPrincipal, + idpPrincipal, + credentialID + ); + IdentityCredentialStorageService.getState( + rpPrincipal, + idpPrincipal, + credentialID, + registered, + allowLogout + ); + Assert.ok(!registered.value, "Should not be registered after deletion."); + Assert.ok(!allowLogout.value, "Should not allow logout after deletion."); + IdentityCredentialStorageService.clear(); +}); + +add_task(async function test_basedomain_delete() { + let rpPrincipal = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI("https://rp.com/"), + {} + ); + let rpPrincipal2 = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI("https://www.rp.com/"), + {} + ); + let rpPrincipal3 = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI("https://www.other.com/"), + {} + ); + let idpPrincipal = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI("https://idp.com/"), + {} + ); + const credentialID = "ID"; + let registered = {}; + let allowLogout = {}; + + // Set values + IdentityCredentialStorageService.setState( + rpPrincipal, + idpPrincipal, + credentialID, + true, + true + ); + IdentityCredentialStorageService.setState( + rpPrincipal2, + idpPrincipal, + credentialID, + true, + true + ); + IdentityCredentialStorageService.setState( + rpPrincipal3, + idpPrincipal, + credentialID, + true, + true + ); + + IdentityCredentialStorageService.deleteFromBaseDomain( + rpPrincipal2.baseDomain + ); + IdentityCredentialStorageService.getState( + rpPrincipal, + idpPrincipal, + credentialID, + registered, + allowLogout + ); + Assert.ok(!registered.value, "Should not be registered after deletion."); + Assert.ok(!allowLogout.value, "Should not allow logout after deletion."); + IdentityCredentialStorageService.getState( + rpPrincipal2, + idpPrincipal, + credentialID, + registered, + allowLogout + ); + Assert.ok(!registered.value, "Should not be registered after deletion."); + Assert.ok(!allowLogout.value, "Should not allow logout after deletion."); + IdentityCredentialStorageService.getState( + rpPrincipal3, + idpPrincipal, + credentialID, + registered, + allowLogout + ); + Assert.ok(registered.value, "Should be registered by set."); + Assert.ok(allowLogout.value, "Should now allow logout by set."); + IdentityCredentialStorageService.clear(); +}); + +add_task(async function test_principal_delete() { + let rpPrincipal = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI("https://rp.com/"), + {} + ); + let rpPrincipal2 = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI("https://www.rp.com/"), + {} + ); + let rpPrincipal3 = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI("https://www.other.com/"), + {} + ); + let idpPrincipal = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI("https://idp.com/"), + {} + ); + const credentialID = "ID"; + let registered = {}; + let allowLogout = {}; + + // Set values + IdentityCredentialStorageService.setState( + rpPrincipal, + idpPrincipal, + credentialID, + true, + true + ); + IdentityCredentialStorageService.setState( + rpPrincipal2, + idpPrincipal, + credentialID, + true, + true + ); + IdentityCredentialStorageService.setState( + rpPrincipal3, + idpPrincipal, + credentialID, + true, + true + ); + + IdentityCredentialStorageService.deleteFromPrincipal(rpPrincipal2); + IdentityCredentialStorageService.getState( + rpPrincipal, + idpPrincipal, + credentialID, + registered, + allowLogout + ); + Assert.ok(registered.value, "Should be registered by set."); + Assert.ok(allowLogout.value, "Should now allow logout by set."); + IdentityCredentialStorageService.getState( + rpPrincipal2, + idpPrincipal, + credentialID, + registered, + allowLogout + ); + Assert.ok(!registered.value, "Should not be registered after deletion."); + Assert.ok(!allowLogout.value, "Should not allow logout after deletion."); + IdentityCredentialStorageService.getState( + rpPrincipal3, + idpPrincipal, + credentialID, + registered, + allowLogout + ); + Assert.ok(registered.value, "Should be registered by set."); + Assert.ok(allowLogout.value, "Should now allow logout by set."); + IdentityCredentialStorageService.clear(); +}); + +add_task(async function test_principal_delete() { + let rpPrincipal = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI("https://rp.com/"), + {} + ); + let rpPrincipal2 = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI("https://rp.com/"), + { privateBrowsingId: 1 } + ); + let rpPrincipal3 = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI("https://www.other.com/"), + { privateBrowsingId: 1 } + ); + let idpPrincipal = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI("https://idp.com/"), + {} + ); + const credentialID = "ID"; + let registered = {}; + let allowLogout = {}; + + // Set values + IdentityCredentialStorageService.setState( + rpPrincipal, + idpPrincipal, + credentialID, + true, + true + ); + IdentityCredentialStorageService.setState( + rpPrincipal2, + idpPrincipal, + credentialID, + true, + true + ); + IdentityCredentialStorageService.setState( + rpPrincipal3, + idpPrincipal, + credentialID, + true, + true + ); + + IdentityCredentialStorageService.deleteFromOriginAttributesPattern( + '{ "privateBrowsingId": 1 }' + ); + IdentityCredentialStorageService.getState( + rpPrincipal, + idpPrincipal, + credentialID, + registered, + allowLogout + ); + Assert.ok(registered.value, "Should be registered by set."); + Assert.ok(allowLogout.value, "Should now allow logout by set."); + IdentityCredentialStorageService.getState( + rpPrincipal2, + idpPrincipal, + credentialID, + registered, + allowLogout + ); + Assert.ok(!registered.value, "Should not be registered after deletion."); + Assert.ok(!allowLogout.value, "Should not allow logout after deletion."); + IdentityCredentialStorageService.getState( + rpPrincipal3, + idpPrincipal, + credentialID, + registered, + allowLogout + ); + Assert.ok(!registered.value, "Should not be registered after deletion."); + Assert.ok(!allowLogout.value, "Should not allow logout after deletion."); + IdentityCredentialStorageService.clear(); +}); diff --git a/toolkit/components/credentialmanagement/tests/xpcshell/xpcshell.ini b/toolkit/components/credentialmanagement/tests/xpcshell/xpcshell.ini new file mode 100644 index 0000000000..79536069e2 --- /dev/null +++ b/toolkit/components/credentialmanagement/tests/xpcshell/xpcshell.ini @@ -0,0 +1,6 @@ +[DEFAULT] +head = head.js +prefs = + dom.security.credentialmanagement.identity.enabled=true + +[test_identity_credential_storage_service.js] |