Bug 1804728, part 1 - Fetch all identity provider manifests before showing the identity provider chooser, r=timhuang,emilio

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
This commit is contained in:
Benjamin VanderSloot 2023-02-14 19:25:50 +00:00
Родитель 868ac163b7
Коммит fe5352731c
6 изменённых файлов: 185 добавлений и 71 удалений

Просмотреть файл

@ -22,6 +22,7 @@
#include "nsIXPConnect.h"
#include "nsNetUtil.h"
#include "nsStringStream.h"
#include "nsTArray.h"
#include "nsURLHelper.h"
namespace mozilla::dom {
@ -153,13 +154,64 @@ IdentityCredential::DiscoverFromExternalSourceInMainProcess(
nsCOMPtr<nsIPrincipal> principal(aPrincipal);
RefPtr<CanonicalBrowsingContext> browsingContext(aBrowsingContext);
// Have the user choose a provider then create a credential for that provider.
PromptUserToSelectProvider(aBrowsingContext, aOptions.mProviders.Value())
// Construct an array of requests to fetch manifests for every provider.
// We need this to show their branding information
nsTArray<RefPtr<GetManifestPromise>> manifestPromises;
for (const IdentityProvider& provider : aOptions.mProviders.Value()) {
RefPtr<GetManifestPromise> manifest =
IdentityCredential::CheckRootManifest(aPrincipal, provider)
->Then(
GetCurrentSerialEventTarget(), __func__,
[provider, principal](bool valid) {
if (valid) {
return IdentityCredential::FetchInternalManifest(principal,
provider);
}
return IdentityCredential::GetManifestPromise::
CreateAndReject(NS_ERROR_FAILURE, __func__);
},
[](nsresult error) {
return IdentityCredential::GetManifestPromise::
CreateAndReject(error, __func__);
});
manifestPromises.AppendElement(manifest);
}
// We use AllSettled here so that failures will be included- we use default
// values there.
GetManifestPromise::AllSettled(GetCurrentSerialEventTarget(),
manifestPromises)
->Then(
GetCurrentSerialEventTarget(), __func__,
[principal, browsingContext](const IdentityProvider& provider) {
[browsingContext, aOptions](
const GetManifestPromise::AllSettledPromiseType::ResolveValueType&
aResults) {
// Convert the
// GetManifestPromise::AllSettledPromiseType::ResolveValueType to a
// Sequence<MozPromise>
CopyableTArray<MozPromise<IdentityInternalManifest, nsresult,
true>::ResolveOrRejectValue>
results = aResults;
const Sequence<MozPromise<IdentityInternalManifest, nsresult,
true>::ResolveOrRejectValue>
resultsSequence(std::move(results));
// The user picks from the providers
return PromptUserToSelectProvider(
browsingContext, aOptions.mProviders.Value(), resultsSequence);
},
[](bool error) {
return IdentityCredential::GetIdentityProviderWithManifestPromise::
CreateAndReject(NS_ERROR_FAILURE, __func__);
})
->Then(
GetCurrentSerialEventTarget(), __func__,
[principal, browsingContext](
const IdentityProviderWithManifest& providerAndManifest) {
IdentityInternalManifest manifest;
IdentityProvider provider;
Tie(provider, manifest) = providerAndManifest;
return IdentityCredential::CreateCredential(
principal, browsingContext, provider);
principal, browsingContext, provider, manifest);
},
[](nsresult error) {
return IdentityCredential::GetIPCIdentityCredentialPromise::
@ -200,9 +252,10 @@ IdentityCredential::DiscoverFromExternalSourceInMainProcess(
// static
RefPtr<IdentityCredential::GetIPCIdentityCredentialPromise>
IdentityCredential::CreateCredential(nsIPrincipal* aPrincipal,
BrowsingContext* aBrowsingContext,
const IdentityProvider& aProvider) {
IdentityCredential::CreateCredential(
nsIPrincipal* aPrincipal, BrowsingContext* aBrowsingContext,
const IdentityProvider& aProvider,
const IdentityInternalManifest& aManifest) {
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(aPrincipal);
MOZ_ASSERT(aBrowsingContext);
@ -210,32 +263,8 @@ IdentityCredential::CreateCredential(nsIPrincipal* aPrincipal,
nsCOMPtr<nsIPrincipal> argumentPrincipal = aPrincipal;
RefPtr<BrowsingContext> browsingContext(aBrowsingContext);
return IdentityCredential::CheckRootManifest(aPrincipal, aProvider)
->Then(
GetCurrentSerialEventTarget(), __func__,
[aProvider, argumentPrincipal](bool valid) {
if (valid) {
return IdentityCredential::FetchInternalManifest(
argumentPrincipal, aProvider);
}
return IdentityCredential::GetManifestPromise::CreateAndReject(
NS_ERROR_FAILURE, __func__);
},
[](nsresult error) {
return IdentityCredential::GetManifestPromise::CreateAndReject(
error, __func__);
})
->Then(
GetCurrentSerialEventTarget(), __func__,
[argumentPrincipal,
aProvider](const IdentityInternalManifest& manifest) {
return IdentityCredential::FetchAccountList(argumentPrincipal,
aProvider, manifest);
},
[](nsresult error) {
return IdentityCredential::GetAccountListPromise::CreateAndReject(
error, __func__);
})
return IdentityCredential::FetchAccountList(argumentPrincipal, aProvider,
aManifest)
->Then(
GetCurrentSerialEventTarget(), __func__,
[argumentPrincipal, browsingContext, aProvider](
@ -250,7 +279,7 @@ IdentityCredential::CreateCredential(nsIPrincipal* aPrincipal,
NS_ERROR_FAILURE, __func__);
}
return PromptUserToSelectAccount(browsingContext, accountList,
currentManifest);
aProvider, currentManifest);
},
[](nsresult error) {
return IdentityCredential::GetAccountPromise::CreateAndReject(
@ -698,14 +727,15 @@ IdentityCredential::FetchMetadata(nsIPrincipal* aPrincipal,
}
// static
RefPtr<IdentityCredential::GetIdentityProviderPromise>
RefPtr<IdentityCredential::GetIdentityProviderWithManifestPromise>
IdentityCredential::PromptUserToSelectProvider(
BrowsingContext* aBrowsingContext,
const Sequence<IdentityProvider>& aProviders) {
const Sequence<IdentityProvider>& aProviders,
const Sequence<GetManifestPromise::ResolveOrRejectValue>& aManifests) {
MOZ_ASSERT(aBrowsingContext);
RefPtr<IdentityCredential::GetIdentityProviderPromise::Private>
resultPromise =
new IdentityCredential::GetIdentityProviderPromise::Private(__func__);
RefPtr<IdentityCredential::GetIdentityProviderWithManifestPromise::Private>
resultPromise = new IdentityCredential::
GetIdentityProviderWithManifestPromise::Private(__func__);
if (NS_WARN_IF(!aBrowsingContext)) {
resultPromise->Reject(NS_ERROR_FAILURE, __func__);
@ -734,19 +764,53 @@ IdentityCredential::PromptUserToSelectProvider(
return resultPromise;
}
// Convert each settled MozPromise into a Nullable<ResolveValue>
Sequence<Nullable<IdentityInternalManifest>> manifests;
for (GetManifestPromise::ResolveOrRejectValue manifest : aManifests) {
if (manifest.IsResolve()) {
if (NS_WARN_IF(
!manifests.AppendElement(manifest.ResolveValue(), fallible))) {
resultPromise->Reject(NS_ERROR_FAILURE, __func__);
return resultPromise;
}
} else {
if (NS_WARN_IF(!manifests.AppendElement(
Nullable<IdentityInternalManifest>(), fallible))) {
resultPromise->Reject(NS_ERROR_FAILURE, __func__);
return resultPromise;
}
}
}
JS::Rooted<JS::Value> manifestsJS(jsapi.cx());
success = ToJSValue(jsapi.cx(), manifests, &manifestsJS);
if (NS_WARN_IF(!success)) {
resultPromise->Reject(NS_ERROR_FAILURE, __func__);
return resultPromise;
}
RefPtr<Promise> showPromptPromise;
icPromptService->ShowProviderPrompt(aBrowsingContext, providersJS,
manifestsJS,
getter_AddRefs(showPromptPromise));
RefPtr<DomPromiseListener> listener = new DomPromiseListener(
[resultPromise](JSContext* aCx, JS::Handle<JS::Value> aValue) {
IdentityProvider result;
bool success = result.Init(aCx, aValue);
if (!success) {
[aProviders, aManifests, resultPromise](JSContext* aCx,
JS::Handle<JS::Value> aValue) {
int32_t result = aValue.toInt32();
if (result < 0 || (uint32_t)result > aProviders.Length() ||
(uint32_t)result > aManifests.Length()) {
resultPromise->Reject(NS_ERROR_FAILURE, __func__);
return;
}
resultPromise->Resolve(result, __func__);
const IdentityProvider& resolvedProvider = aProviders.ElementAt(result);
if (!aManifests.ElementAt(result).IsResolve()) {
resultPromise->Reject(NS_ERROR_FAILURE, __func__);
return;
}
const IdentityInternalManifest& resolvedManifest =
aManifests.ElementAt(result).ResolveValue();
resultPromise->Resolve(MakeTuple(resolvedProvider, resolvedManifest),
__func__);
},
[resultPromise](nsresult aRv) { resultPromise->Reject(aRv, __func__); });
showPromptPromise->AppendNativeHandler(listener);
@ -758,6 +822,7 @@ IdentityCredential::PromptUserToSelectProvider(
RefPtr<IdentityCredential::GetAccountPromise>
IdentityCredential::PromptUserToSelectAccount(
BrowsingContext* aBrowsingContext, const IdentityAccountList& aAccounts,
const IdentityProvider& aProvider,
const IdentityInternalManifest& aManifest) {
MOZ_ASSERT(aBrowsingContext);
RefPtr<IdentityCredential::GetAccountPromise::Private> resultPromise =
@ -790,19 +855,37 @@ IdentityCredential::PromptUserToSelectAccount(
return resultPromise;
}
JS::Rooted<JS::Value> providerJS(jsapi.cx());
success = ToJSValue(jsapi.cx(), aProvider, &providerJS);
if (NS_WARN_IF(!success)) {
resultPromise->Reject(NS_ERROR_FAILURE, __func__);
return resultPromise;
}
JS::Rooted<JS::Value> manifestJS(jsapi.cx());
success = ToJSValue(jsapi.cx(), aManifest, &manifestJS);
if (NS_WARN_IF(!success)) {
resultPromise->Reject(NS_ERROR_FAILURE, __func__);
return resultPromise;
}
RefPtr<Promise> showPromptPromise;
icPromptService->ShowAccountListPrompt(aBrowsingContext, accountsJS,
providerJS, manifestJS,
getter_AddRefs(showPromptPromise));
RefPtr<DomPromiseListener> listener = new DomPromiseListener(
[resultPromise, aManifest](JSContext* aCx, JS::Handle<JS::Value> aValue) {
IdentityAccount result;
bool success = result.Init(aCx, aValue);
if (!success) {
[aAccounts, resultPromise, aManifest](JSContext* aCx,
JS::Handle<JS::Value> aValue) {
int32_t result = aValue.toInt32();
if (!aAccounts.mAccounts.WasPassed() || result < 0 ||
(uint32_t)result > aAccounts.mAccounts.Value().Length()) {
resultPromise->Reject(NS_ERROR_FAILURE, __func__);
return;
}
resultPromise->Resolve(MakeTuple(aManifest, result), __func__);
const IdentityAccount& resolved =
aAccounts.mAccounts.Value().ElementAt(result);
resultPromise->Resolve(MakeTuple(aManifest, resolved), __func__);
},
[resultPromise](nsresult aRv) { resultPromise->Reject(aRv, __func__); });
showPromptPromise->AppendNativeHandler(listener);
@ -861,7 +944,7 @@ IdentityCredential::PromptUserWithPolicy(
return FetchMetadata(aPrincipal, aProvider, aManifest)
->Then(
GetCurrentSerialEventTarget(), __func__,
[aAccount, aProvider, argumentPrincipal, browsingContext,
[aAccount, aManifest, aProvider, argumentPrincipal, browsingContext,
icStorageService,
idpPrincipal](const IdentityClientMetadata& metadata)
-> RefPtr<GenericPromise> {
@ -892,10 +975,16 @@ IdentityCredential::PromptUserWithPolicy(
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE,
__func__);
}
JS::Rooted<JS::Value> manifestJS(jsapi.cx());
success = ToJSValue(jsapi.cx(), aManifest, &manifestJS);
if (NS_WARN_IF(!success)) {
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE,
__func__);
}
RefPtr<Promise> showPromptPromise;
icPromptService->ShowPolicyPrompt(
browsingContext, providerJS, metadataJS,
browsingContext, providerJS, manifestJS, metadataJS,
getter_AddRefs(showPromptPromise));
RefPtr<GenericPromise::Private> resultPromise =

Просмотреть файл

@ -36,6 +36,10 @@ class IdentityCredential final : public Credential {
typedef MozPromise<bool, nsresult, true> ValidationPromise;
typedef MozPromise<IdentityInternalManifest, nsresult, true>
GetManifestPromise;
typedef Tuple<IdentityProvider, IdentityInternalManifest>
IdentityProviderWithManifest;
typedef MozPromise<IdentityProviderWithManifest, nsresult, true>
GetIdentityProviderWithManifestPromise;
typedef MozPromise<Tuple<IdentityInternalManifest, IdentityAccountList>,
nsresult, true>
GetAccountListPromise;
@ -124,6 +128,7 @@ class IdentityCredential final : public Credential {
// aPrincipal: the caller of navigator.credentials.get()'s principal
// aBrowsingContext: the BC of the caller of navigator.credentials.get()
// aProvider: the provider to validate the root manifest of
// aManifest: the internal manifest of the identity provider
// Return value:
// a promise resolving to an IPC credential with type "identity", id
// constructed to identify it, and token corresponding to the token
@ -133,7 +138,8 @@ class IdentityCredential final : public Credential {
// other static methods here.
static RefPtr<GetIPCIdentityCredentialPromise> CreateCredential(
nsIPrincipal* aPrincipal, BrowsingContext* aBrowsingContext,
const IdentityProvider& aProvider);
const IdentityProvider& aProvider,
const IdentityInternalManifest& aManifest);
// Performs a Fetch for the root manifest of the provided identity provider
// and validates it as correct. The returned promise resolves with a bool
@ -235,14 +241,17 @@ class IdentityCredential final : public Credential {
// Arguments:
// aBrowsingContext: the BC of the caller of navigator.credentials.get()
// aProviders: the providers to let the user select from
// aManifests: the manifests
// Return value:
// a promise resolving to an identity provider that the user took action
// to select. This promise may reject with nsresult errors.
// Side effects:
// Will show a dialog to the user.
static RefPtr<GetIdentityProviderPromise> PromptUserToSelectProvider(
static RefPtr<GetIdentityProviderWithManifestPromise>
PromptUserToSelectProvider(
BrowsingContext* aBrowsingContext,
const Sequence<IdentityProvider>& aProviders);
const Sequence<IdentityProvider>& aProviders,
const Sequence<GetManifestPromise::ResolveOrRejectValue>& aManifests);
// Show the user a dialog to select what account they would like
// to try to log in with.
@ -250,6 +259,7 @@ class IdentityCredential final : public Credential {
// Arguments:
// aBrowsingContext: the BC of the caller of navigator.credentials.get()
// aAccounts: the accounts to let the user select from
// aProvider: the provider that was chosen
// aManifest: the identity provider that was chosen's manifest
// Return value:
// a promise resolving to an account that the user took action
@ -258,6 +268,7 @@ class IdentityCredential final : public Credential {
// Will show a dialog to the user.
static RefPtr<GetAccountPromise> PromptUserToSelectAccount(
BrowsingContext* aBrowsingContext, const IdentityAccountList& aAccounts,
const IdentityProvider& aProvider,
const IdentityInternalManifest& aManifest);
// Show the user a dialog to select what account they would like

Просмотреть файл

@ -42,12 +42,16 @@ A few notes:
"CredentialsContainer::Get" -> "DiscoverFromExternalSource"
"DiscoverFromExternalSource" -> "DiscoverFromExternalSourceInMainProcess" [label="IPC via WindowGlobal's DiscoverIdentityCredentialFromExternalSource"]
"DiscoverFromExternalSourceInMainProcess" -> "anonymous timeout callback" -> "CloseUserInterface" -> "IdentityCredentialPromptService::Close"
"DiscoverFromExternalSourceInMainProcess" -> "PromptUserToSelectProvider"
"DiscoverFromExternalSourceInMainProcess" -> "CheckRootManifest A"
"CheckRootManifest A" -> "FetchInternalManifest A" [label="via promise chain in DiscoverFromExternalSourceInMainProcess"]
"FetchInternalManifest A" -> "DiscoverFromExternalSourceInMainProcess inline anonymous callback (Promise::All)"
"DiscoverFromExternalSourceInMainProcess" -> "CheckRootManifest N"
"CheckRootManifest N" -> "FetchInternalManifest N" [label="via promise chain in DiscoverFromExternalSourceInMainProcess"]
"FetchInternalManifest N" -> "DiscoverFromExternalSourceInMainProcess inline anonymous callback (Promise::All)"
"DiscoverFromExternalSourceInMainProcess inline anonymous callback (Promise::All)" -> "PromptUserToSelectProvider"
"PromptUserToSelectProvider" -> "IdentityCredentialPromptService::ShowProviderPrompt"
"IdentityCredentialPromptService::ShowProviderPrompt" -> "CreateCredential" [label="via promise chain in DiscoverFromExternalSourceInMainProcess"]
"CreateCredential" -> "CheckRootManifest"
"CheckRootManifest" -> "FetchInternalManifest" [label="via promise chain in CreateCredential"]
"FetchInternalManifest" -> "FetchAccountList" [label="via promise chain in CreateCredential"]
"CreateCredential" -> "FetchAccountList" [label="via promise chain in CreateCredential"]
"FetchAccountList" -> "PromptUserToSelectAccount" [label="via promise chain in CreateCredential"]
"PromptUserToSelectAccount" -> "IdentityCredentialPromptService::ShowAccountListPrompt"
"IdentityCredentialPromptService::ShowAccountListPrompt" -> "PromptUserWithPolicy" [label="via promise chain in CreateCredential"]

Просмотреть файл

@ -47,7 +47,7 @@ dictionary IdentityBranding {
};
// https://fedidcg.github.io/FedCM/#manifest
[GenerateInit]
[GenerateInit, GenerateConversionToJS]
dictionary IdentityInternalManifest {
required USVString accounts_endpoint;
required USVString client_metadata_endpoint;

Просмотреть файл

@ -24,7 +24,7 @@ XPCOMUtils.defineLazyPreferenceGetter(
function fulfilledPromiseFromFirstListElement(list) {
if (list.length) {
return Promise.resolve(list[0]);
return Promise.resolve(0);
}
return Promise.reject();
}
@ -42,9 +42,10 @@ export class IdentityCredentialPromptService {
* 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
* @param {IdentityInternalManifest[]} identityManifests - The manifests corresponding 1-to-1 with identityProviders
* @returns {Promise<number>} The user-selected identity provider
*/
showProviderPrompt(browsingContext, identityProviders) {
showProviderPrompt(browsingContext, identityProviders, identityManifests) {
// For testing only.
if (lazy.SELECT_FIRST_IN_UI_LISTS) {
return fulfilledPromiseFromFirstListElement(identityProviders);
@ -101,7 +102,7 @@ export class IdentityCredentialPromptService {
browser
);
browser.ownerGlobal.PopupNotifications.remove(notification);
resolve(provider);
resolve(providerIndex);
event.stopPropagation();
};
listBox.append(newItem);
@ -152,12 +153,14 @@ export class IdentityCredentialPromptService {
* 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.
@ -283,9 +286,16 @@ export class IdentityCredentialPromptService {
* 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) {
showAccountListPrompt(
browsingContext,
accountList,
provider,
providerManifest
) {
// For testing only.
if (lazy.SELECT_FIRST_IN_UI_LISTS) {
return fulfilledPromiseFromFirstListElement(accountList.accounts);
@ -350,7 +360,7 @@ export class IdentityCredentialPromptService {
browser
);
browser.ownerGlobal.PopupNotifications.remove(notification);
resolve(account);
resolve(accountIndex);
};
listBox.append(newItem);
}

Просмотреть файл

@ -9,14 +9,14 @@ webidl BrowsingContext;
[scriptable, uuid(936007db-a957-4f1d-a23d-f7d9403223e6)]
interface nsIIdentityCredentialPromptService : nsISupports {
// Display to the user an interface to choose from among the identity providers listed
// Resolves with one of the elements of the list.
Promise showProviderPrompt(in BrowsingContext browsingContext, in jsval identityProviders);
// Resolves with an index referring to one pair of the elements of the lists.
Promise showProviderPrompt(in BrowsingContext browsingContext, in jsval identityProviders, in jsval identityManifests);
// Display to the user an interface to approve (or disapprove) of the terms of service for
// the identity provider when used on the current site.
Promise showPolicyPrompt(in BrowsingContext browsingContext, in jsval identityProvider, in jsval identityClientMetadata);
// Display to the user an interface to choose from among the accounts listed.
// Resolves with one of the elements of the list.
Promise showAccountListPrompt(in BrowsingContext browsingContext, in jsval accountList);
Promise showPolicyPrompt(in BrowsingContext browsingContext, in jsval identityProvider, in jsval identityManifest, in jsval identityClientMetadata);
// Display to the user an interface to choose from among the accounts listed with the information of the provider.
// Resolves with an index referring to one of the elements of the list.
Promise showAccountListPrompt(in BrowsingContext browsingContext, in jsval accountList, in jsval identityProvider, in jsval identityManifest);
// Close all UI from the other methods of this module
void close(in BrowsingContext browsingContext);
};