diff options
Diffstat (limited to 'toolkit/components/credentialmanagement/IdentityCredentialPromptService.sys.mjs')
-rw-r--r-- | toolkit/components/credentialmanagement/IdentityCredentialPromptService.sys.mjs | 399 |
1 files changed, 399 insertions, 0 deletions
diff --git a/toolkit/components/credentialmanagement/IdentityCredentialPromptService.sys.mjs b/toolkit/components/credentialmanagement/IdentityCredentialPromptService.sys.mjs new file mode 100644 index 0000000000..3557f911e1 --- /dev/null +++ b/toolkit/components/credentialmanagement/IdentityCredentialPromptService.sys.mjs @@ -0,0 +1,399 @@ +/** + * 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/. + */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +const lazy = {}; + +XPCOMUtils.defineLazyServiceGetter( + lazy, + "IDNService", + "@mozilla.org/network/idn-service;1", + "nsIIDNService" +); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "SELECT_FIRST_IN_UI_LISTS", + "dom.security.credentialmanagement.identity.select_first_in_ui_lists", + false +); + +function fulfilledPromiseFromFirstListElement(list) { + if (list.length) { + return Promise.resolve(list[0]); + } + return Promise.reject(); +} + +/** + * Class implementing the nsIIdentityCredentialPromptService + * */ +export class IdentityCredentialPromptService { + classID = Components.ID("{936007db-a957-4f1d-a23d-f7d9403223e6}"); + QueryInterface = ChromeUtils.generateQI([ + "nsIIdentityCredentialPromptService", + ]); + + /** + * Ask the user, using a PopupNotification, to select an Identity Provider from a provided list. + * @param {BrowsingContext} browsingContext - The BrowsingContext of the document requesting an identity credential via navigator.credentials.get() + * @param {IdentityProvider[]} identityProviders - The list of identity providers the user selects from + * @returns {Promise<IdentityProvider>} The user-selected identity provider + */ + showProviderPrompt(browsingContext, identityProviders) { + // For testing only. + if (lazy.SELECT_FIRST_IN_UI_LISTS) { + return fulfilledPromiseFromFirstListElement(identityProviders); + } + return new Promise(function(resolve, reject) { + let browser = browsingContext.top.embedderElement; + if (!browser) { + reject(); + return; + } + + // Localize all strings to be used + // Bug 1797154 - Convert localization calls to use the async formatValues. + let localization = new Localization( + ["preview/identityCredentialNotification.ftl"], + true + ); + let headerMessage = localization.formatValueSync( + "identity-credential-header-providers", + { + host: "<>", + } + ); + let [cancel] = localization.formatMessagesSync([ + { id: "identity-credential-cancel-button" }, + ]); + + let cancelLabel = cancel.attributes.find(x => x.name == "label").value; + let cancelKey = cancel.attributes.find(x => x.name == "accesskey").value; + + // Build the choices into the panel + let listBox = browser.ownerDocument.getElementById( + "identity-credential-provider-selector-container" + ); + while (listBox.firstChild) { + listBox.removeChild(listBox.lastChild); + } + let itemTemplate = browser.ownerDocument.getElementById( + "template-credential-provider-list-item" + ); + for (let providerIndex in identityProviders) { + let provider = identityProviders[providerIndex]; + let providerURI = new URL(provider.configURL); + let displayDomain = lazy.IDNService.convertToDisplayIDN( + providerURI.host, + {} + ); + let newItem = itemTemplate.content.firstElementChild.cloneNode(true); + newItem.firstElementChild.textContent = displayDomain; + newItem.setAttribute("oncommand", `this.callback(event)`); + newItem.callback = function(event) { + let notification = browser.ownerGlobal.PopupNotifications.getNotification( + "identity-credential", + browser + ); + browser.ownerGlobal.PopupNotifications.remove(notification); + resolve(provider); + event.stopPropagation(); + }; + listBox.append(newItem); + } + + // Construct the necessary arguments for notification behavior + let currentOrigin = + browsingContext.currentWindowContext.documentPrincipal.originNoSuffix; + let options = { + name: currentOrigin, + }; + let mainAction = { + label: cancelLabel, + accessKey: cancelKey, + callback(event) { + reject(); + }, + }; + + // Show the popup + browser.ownerDocument.getElementById( + "identity-credential-provider" + ).hidden = false; + browser.ownerDocument.getElementById( + "identity-credential-policy" + ).hidden = true; + browser.ownerDocument.getElementById( + "identity-credential-account" + ).hidden = true; + browser.ownerGlobal.PopupNotifications.show( + browser, + "identity-credential", + headerMessage, + "identity-credential-notification-icon", + mainAction, + null, + options + ); + }); + } + + /** + * Ask the user, using a PopupNotification, to approve or disapprove of the policies of the Identity Provider. + * @param {BrowsingContext} browsingContext - The BrowsingContext of the document requesting an identity credential via navigator.credentials.get() + * @param {IdentityProvider} identityProvider - The Identity Provider that the user has selected to use + * @param {IdentityCredentialMetadata} identityCredentialMetadata - The metadata displayed to the user + * @returns {Promise<bool>} A boolean representing the user's acceptance of the metadata. + */ + showPolicyPrompt( + browsingContext, + identityProvider, + identityCredentialMetadata + ) { + // For testing only. + if (lazy.SELECT_FIRST_IN_UI_LISTS) { + return Promise.resolve(true); + } + if ( + !identityCredentialMetadata || + (!identityCredentialMetadata.privacy_policy_url && + !identityCredentialMetadata.terms_of_service_url) + ) { + return Promise.resolve(true); + } + return new Promise(function(resolve, reject) { + let browser = browsingContext.top.embedderElement; + if (!browser) { + reject(); + return; + } + + let providerURI = new URL(identityProvider.configURL); + let providerDisplayDomain = lazy.IDNService.convertToDisplayIDN( + providerURI.host, + {} + ); + let currentBaseDomain = + browsingContext.currentWindowContext.documentPrincipal.baseDomain; + // Localize the description + // Bug 1797154 - Convert localization calls to use the async formatValues. + let localization = new Localization( + ["preview/identityCredentialNotification.ftl"], + true + ); + let descriptionMessage = localization.formatValueSync( + "identity-credential-policy-description", + { + host: currentBaseDomain, + provider: providerDisplayDomain, + } + ); + let [accept, cancel] = localization.formatMessagesSync([ + { id: "identity-credential-accept-button" }, + { id: "identity-credential-cancel-button" }, + ]); + + let cancelLabel = cancel.attributes.find(x => x.name == "label").value; + let cancelKey = cancel.attributes.find(x => x.name == "accesskey").value; + let acceptLabel = accept.attributes.find(x => x.name == "label").value; + let acceptKey = accept.attributes.find(x => x.name == "accesskey").value; + let title = localization.formatValueSync( + "identity-credential-policy-title" + ); + + // Populate the content of the policy panel + let description = browser.ownerDocument.getElementById( + "identity-credential-policy-explanation" + ); + description.textContent = descriptionMessage; + let privacyPolicyAnchor = browser.ownerDocument.getElementById( + "identity-credential-privacy-policy" + ); + privacyPolicyAnchor.hidden = true; + if (identityCredentialMetadata.privacy_policy_url) { + privacyPolicyAnchor.href = + identityCredentialMetadata.privacy_policy_url; + privacyPolicyAnchor.hidden = false; + } + let termsOfServiceAnchor = browser.ownerDocument.getElementById( + "identity-credential-terms-of-service" + ); + termsOfServiceAnchor.hidden = true; + if (identityCredentialMetadata.terms_of_service_url) { + termsOfServiceAnchor.href = + identityCredentialMetadata.terms_of_service_url; + termsOfServiceAnchor.hidden = false; + } + + // Construct the necessary arguments for notification behavior + let options = {}; + let mainAction = { + label: acceptLabel, + accessKey: acceptKey, + callback(event) { + resolve(true); + }, + }; + let secondaryActions = [ + { + label: cancelLabel, + accessKey: cancelKey, + callback(event) { + resolve(false); + }, + }, + ]; + + // Show the popup + let ownerDocument = browser.ownerDocument; + ownerDocument.getElementById( + "identity-credential-provider" + ).hidden = true; + ownerDocument.getElementById("identity-credential-policy").hidden = false; + ownerDocument.getElementById("identity-credential-account").hidden = true; + browser.ownerGlobal.PopupNotifications.show( + browser, + "identity-credential", + title, + "identity-credential-notification-icon", + mainAction, + secondaryActions, + options + ); + }); + } + + /** + * Ask the user, using a PopupNotification, to select an account from a provided list. + * @param {BrowsingContext} browsingContext - The BrowsingContext of the document requesting an identity credential via navigator.credentials.get() + * @param {IdentityAccountList} accountList - The list of accounts the user selects from + * @returns {Promise<IdentityAccount>} The user-selected account + */ + showAccountListPrompt(browsingContext, accountList) { + // For testing only. + if (lazy.SELECT_FIRST_IN_UI_LISTS) { + return fulfilledPromiseFromFirstListElement(accountList.accounts); + } + return new Promise(function(resolve, reject) { + let browser = browsingContext.top.embedderElement; + if (!browser) { + reject(); + return; + } + let currentOrigin = + browsingContext.currentWindowContext.documentPrincipal.originNoSuffix; + + // Localize all strings to be used + // Bug 1797154 - Convert localization calls to use the async formatValues. + let localization = new Localization( + ["preview/identityCredentialNotification.ftl"], + true + ); + let headerMessage = localization.formatValueSync( + "identity-credential-header-accounts", + { + host: "<>", + } + ); + let descriptionMessage = localization.formatValueSync( + "identity-credential-description-account-explanation", + { + host: currentOrigin, + } + ); + let [cancel] = localization.formatMessagesSync([ + { id: "identity-credential-cancel-button" }, + ]); + + let cancelLabel = cancel.attributes.find(x => x.name == "label").value; + let cancelKey = cancel.attributes.find(x => x.name == "accesskey").value; + + // Add the description text + browser.ownerDocument.getElementById( + "credential-account-explanation" + ).textContent = descriptionMessage; + + // Build the choices into the panel + let listBox = browser.ownerDocument.getElementById( + "identity-credential-account-selector-container" + ); + while (listBox.firstChild) { + listBox.removeChild(listBox.lastChild); + } + let itemTemplate = browser.ownerDocument.getElementById( + "template-credential-account-list-item" + ); + for (let accountIndex in accountList.accounts) { + let account = accountList.accounts[accountIndex]; + let newItem = itemTemplate.content.firstElementChild.cloneNode(true); + newItem.firstElementChild.textContent = account.email; + newItem.setAttribute("oncommand", "this.callback()"); + newItem.callback = function() { + let notification = browser.ownerGlobal.PopupNotifications.getNotification( + "identity-credential", + browser + ); + browser.ownerGlobal.PopupNotifications.remove(notification); + resolve(account); + }; + listBox.append(newItem); + } + + // Construct the necessary arguments for notification behavior + let options = { + name: currentOrigin, + }; + let mainAction = { + label: cancelLabel, + accessKey: cancelKey, + callback(event) { + reject(); + }, + }; + + // Show the popup + browser.ownerDocument.getElementById( + "identity-credential-provider" + ).hidden = true; + browser.ownerDocument.getElementById( + "identity-credential-policy" + ).hidden = true; + browser.ownerDocument.getElementById( + "identity-credential-account" + ).hidden = false; + browser.ownerGlobal.PopupNotifications.show( + browser, + "identity-credential", + headerMessage, + "identity-credential-notification-icon", + mainAction, + null, + options + ); + }); + } + + /** + * Close all UI from the other methods of this module for the provided window. + * @param {BrowsingContext} browsingContext - The BrowsingContext of the document requesting an identity credential via navigator.credentials.get() + * @returns + */ + close(browsingContext) { + let browser = browsingContext.top.embedderElement; + if (!browser) { + return; + } + let notification = browser.ownerGlobal.PopupNotifications.getNotification( + "identity-credential", + browser + ); + if (notification) { + browser.ownerGlobal.PopupNotifications.remove(notification); + } + } +} |