Files
tubestation/toolkit/components/credentialmanagement/IdentityCredentialPromptService.sys.mjs
Benjamin VanderSloot eef6c9fd98 Bug 1782087, part 1 - Create registration doorhanger for IdentityCredential, r=pbz,fluent-reviewers,webidl,smaug,flod
We need one more panel in the doorhanger- one that is a user information panel per (rp, idp, account) tuple.

This just gives the privacy policy and ToS links to the user. It is implemented similarly to the other panels,
and would similarly be updated by Bug 1800695.

Similar to the original doorhanger patch (Bug 1782088 - Create account chooser doorhanger for IdentityCredential)
tests and fine polish are out of scope for this prototype.

Differential Revision: https://phabricator.services.mozilla.com/D162126
2022-12-03 18:50:31 +00:00

400 lines
13 KiB
JavaScript

/**
* 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);
}
}
}