forked from mirrors/gecko-dev
This is required to make our Identity Provider chooser pretty. This has minimal privacy impact because these requests are not credentialed. I had to re-define the service for prompts to include these manifests and return indexes into argument arrays, rather than elements of those arrays. Also updating sourcedocs to reflect this new order of calls. Differential Revision: https://phabricator.services.mozilla.com/D168813
425 lines
14 KiB
JavaScript
425 lines
14 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(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
|
|
* @param {IdentityInternalManifest[]} identityManifests - The manifests corresponding 1-to-1 with identityProviders
|
|
* @returns {Promise<number>} The user-selected identity provider
|
|
*/
|
|
showProviderPrompt(browsingContext, identityProviders, identityManifests) {
|
|
// 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(providerIndex);
|
|
event.stopPropagation();
|
|
};
|
|
listBox.append(newItem);
|
|
}
|
|
|
|
// Construct the necessary arguments for notification behavior
|
|
let currentOrigin =
|
|
browsingContext.currentWindowContext.documentPrincipal.originNoSuffix;
|
|
let options = {
|
|
name: currentOrigin,
|
|
eventCallback(event) {
|
|
if (event === "removed") {
|
|
reject();
|
|
}
|
|
},
|
|
};
|
|
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 {IdentityInternalManifest} identityManifest - The Identity Provider that the user has selected to use's manifest
|
|
* @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,
|
|
identityManifest,
|
|
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 = {
|
|
eventCallback(event) {
|
|
if (event === "removed") {
|
|
reject();
|
|
}
|
|
},
|
|
};
|
|
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
|
|
* @param {IdentityProvider} provider - The selected identity provider
|
|
* @param {IdentityInternalManifest} providerManifest - The manifest of the selected identity provider
|
|
* @returns {Promise<IdentityAccount>} The user-selected account
|
|
*/
|
|
showAccountListPrompt(
|
|
browsingContext,
|
|
accountList,
|
|
provider,
|
|
providerManifest
|
|
) {
|
|
// 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(accountIndex);
|
|
};
|
|
listBox.append(newItem);
|
|
}
|
|
|
|
// Construct the necessary arguments for notification behavior
|
|
let options = {
|
|
name: currentOrigin,
|
|
eventCallback(event) {
|
|
if (event === "removed") {
|
|
reject();
|
|
}
|
|
},
|
|
};
|
|
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);
|
|
}
|
|
}
|
|
}
|