Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#include "mozilla/Components.h"
#include "mozilla/dom/NavigatorLogin.h"
#include "mozilla/dom/IdentityNetworkHelpers.h"
#include "mozilla/dom/WebIdentityParent.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "nsIEffectiveTLDService.h"
#include "nsIIdentityCredentialPromptService.h"
#include "nsIIdentityCredentialStorageService.h"
#include "nsIXPConnect.h"
#include "nsURLHelper.h"
namespace mozilla::dom {
void WebIdentityParent::ActorDestroy(ActorDestroyReason aWhy) {
MOZ_ASSERT(NS_IsMainThread());
}
mozilla::ipc::IPCResult WebIdentityParent::RecvRequestCancel() {
MOZ_ASSERT(NS_IsMainThread());
return IPC_OK();
}
mozilla::ipc::IPCResult WebIdentityParent::RecvGetIdentityCredential(
IdentityCredentialRequestOptions&& aOptions,
const CredentialMediationRequirement& aMediationRequirement,
bool aHasUserActivation, const GetIdentityCredentialResolver& aResolver) {
WindowGlobalParent* manager = static_cast<WindowGlobalParent*>(Manager());
if (!manager) {
aResolver(NS_ERROR_FAILURE);
}
identity::GetCredentialInMainProcess(
manager->DocumentPrincipal(), manager->BrowsingContext(),
std::move(aOptions), aMediationRequirement, aHasUserActivation)
->Then(
GetCurrentSerialEventTarget(), __func__,
[aResolver](const IPCIdentityCredential& aResult) {
return aResolver(aResult);
},
[aResolver](nsresult aErr) { aResolver(aErr); });
return IPC_OK();
}
mozilla::ipc::IPCResult WebIdentityParent::RecvDisconnectIdentityCredential(
const IdentityCredentialDisconnectOptions& aOptions,
const DisconnectIdentityCredentialResolver& aResolver) {
WindowGlobalParent* manager = static_cast<WindowGlobalParent*>(Manager());
if (!manager) {
aResolver(NS_ERROR_FAILURE);
}
identity::DisconnectInMainProcess(manager->DocumentPrincipal(), aOptions)
->Then(
GetCurrentSerialEventTarget(), __func__,
[aResolver](const bool& aResult) { aResolver(NS_OK); },
[aResolver](nsresult aErr) { aResolver(aErr); });
return IPC_OK();
}
mozilla::ipc::IPCResult WebIdentityParent::RecvPreventSilentAccess(
const PreventSilentAccessResolver& aResolver) {
WindowGlobalParent* manager = static_cast<WindowGlobalParent*>(Manager());
if (!manager) {
aResolver(NS_ERROR_FAILURE);
}
nsIPrincipal* principal = manager->DocumentPrincipal();
if (principal) {
nsCOMPtr<nsIPermissionManager> permissionManager =
components::PermissionManager::Service();
if (permissionManager) {
permissionManager->RemoveFromPrincipal(
principal, "credential-allow-silent-access"_ns);
aResolver(NS_OK);
return IPC_OK();
}
}
aResolver(NS_ERROR_NOT_AVAILABLE);
return IPC_OK();
}
mozilla::ipc::IPCResult WebIdentityParent::RecvSetLoginStatus(
LoginStatus aStatus, const SetLoginStatusResolver& aResolver) {
WindowGlobalParent* manager = static_cast<WindowGlobalParent*>(Manager());
if (!manager) {
aResolver(NS_ERROR_FAILURE);
}
nsIPrincipal* principal = manager->DocumentPrincipal();
if (!principal) {
aResolver(NS_ERROR_DOM_NOT_ALLOWED_ERR);
return IPC_OK();
}
nsresult rv = NavigatorLogin::SetLoginStatus(principal, aStatus);
aResolver(rv);
return IPC_OK();
}
namespace identity {
nsresult CanSilentlyCollect(nsIPrincipal* aPrincipal,
nsIPrincipal* aIDPPrincipal, bool* aResult) {
NS_ENSURE_ARG_POINTER(aPrincipal);
NS_ENSURE_ARG_POINTER(aIDPPrincipal);
nsCString origin;
nsresult rv = aIDPPrincipal->GetOrigin(origin);
NS_ENSURE_SUCCESS(rv, rv);
uint32_t permit = nsIPermissionManager::UNKNOWN_ACTION;
nsCOMPtr<nsIPermissionManager> permissionManager =
components::PermissionManager::Service();
if (!permissionManager) {
return NS_ERROR_SERVICE_NOT_AVAILABLE;
}
rv = permissionManager->TestPermissionFromPrincipal(
aPrincipal, "credential-allow-silent-access^"_ns + origin, &permit);
NS_ENSURE_SUCCESS(rv, rv);
*aResult = (permit == nsIPermissionManager::ALLOW_ACTION);
if (!*aResult) {
return NS_OK;
}
rv = permissionManager->TestPermissionFromPrincipal(
aPrincipal, "credential-allow-silent-access"_ns, &permit);
NS_ENSURE_SUCCESS(rv, rv);
*aResult = permit == nsIPermissionManager::ALLOW_ACTION;
return NS_OK;
}
// static
RefPtr<GetIPCIdentityCredentialPromise> GetCredentialInMainProcess(
nsIPrincipal* aPrincipal, CanonicalBrowsingContext* aBrowsingContext,
IdentityCredentialRequestOptions&& aOptions,
const CredentialMediationRequirement& aMediationRequirement,
bool aHasUserActivation) {
MOZ_ASSERT(aPrincipal);
MOZ_ASSERT(aBrowsingContext);
WindowContext* wc = aBrowsingContext->GetCurrentWindowContext();
if (!wc) {
return GetIPCIdentityCredentialPromise::CreateAndReject(
NS_ERROR_NOT_AVAILABLE, __func__);
}
if (aOptions.mMode == IdentityCredentialRequestOptionsMode::Active) {
// If the site is operating in "Active Mode" we need user activation to
// proceed.
if (!aHasUserActivation) {
return GetIPCIdentityCredentialPromise::CreateAndReject(
NS_ERROR_DOM_NETWORK_ERR, __func__);
}
} else {
// Otherwise we are in "Passive Mode" and since this doesn't require user
// activation we constrain the credentials that are allowed to be be shown
// to the user so they don't get annoyed.
// Specifically, they need to have this credential registered for use on
// this website.
nsresult rv;
nsCOMPtr<nsIIdentityCredentialStorageService> icStorageService =
mozilla::components::IdentityCredentialStorageService::Service(&rv);
if (NS_WARN_IF(!icStorageService)) {
return GetIPCIdentityCredentialPromise::CreateAndReject(rv, __func__);
}
aOptions.mProviders.RemoveElementsBy(
[icStorageService,
aPrincipal](const IdentityProviderRequestOptions& provider) {
nsCString configLocation = provider.mConfigURL;
nsCOMPtr<nsIURI> configURI;
nsresult rv = NS_NewURI(getter_AddRefs(configURI), configLocation);
if (NS_FAILED(rv)) {
return true;
}
bool thirdParty = true;
rv = aPrincipal->IsThirdPartyURI(configURI, &thirdParty);
if (!thirdParty) {
return false;
}
nsCOMPtr<nsIPrincipal> idpPrincipal =
BasePrincipal::CreateContentPrincipal(
configURI, aPrincipal->OriginAttributesRef());
bool connected = false;
rv =
icStorageService->Connected(aPrincipal, idpPrincipal, &connected);
if (NS_FAILED(rv)) {
return true;
}
return !connected;
});
}
if (aOptions.mProviders.IsEmpty()) {
return GetIPCIdentityCredentialPromise::CreateAndReject(
NS_ERROR_NOT_AVAILABLE, __func__);
}
RefPtr<nsIPrincipal> principal = aPrincipal;
RefPtr<CanonicalBrowsingContext> cbc = aBrowsingContext;
RefPtr<GetIPCIdentityCredentialPromise::Private> result =
new GetIPCIdentityCredentialPromise::Private(__func__);
DiscoverFromExternalSourceInMainProcess(principal, cbc, aOptions,
aMediationRequirement)
->Then(
GetCurrentSerialEventTarget(), __func__,
[result](const IPCIdentityCredential& credential) {
result->Resolve(credential, __func__);
},
[result](nsresult rv) { result->Reject(rv, __func__); });
return result.forget();
}
// static
RefPtr<GetIPCIdentityCredentialPromise> DiscoverFromExternalSourceInMainProcess(
nsIPrincipal* aPrincipal, CanonicalBrowsingContext* aBrowsingContext,
const IdentityCredentialRequestOptions& aOptions,
const CredentialMediationRequirement& aMediationRequirement) {
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(aPrincipal);
MOZ_ASSERT(aBrowsingContext);
// Make sure we have providers.
if (aOptions.mProviders.Length() < 1) {
return GetIPCIdentityCredentialPromise::CreateAndReject(
NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__);
}
RefPtr<GetIPCIdentityCredentialPromise::Private> result =
new GetIPCIdentityCredentialPromise::Private(__func__);
nsCOMPtr<nsIPrincipal> principal(aPrincipal);
RefPtr<CanonicalBrowsingContext> browsingContext(aBrowsingContext);
RefPtr<nsITimer> timeout;
if (StaticPrefs::
dom_security_credentialmanagement_identity_reject_delay_enabled()) {
nsresult rv = NS_NewTimerWithCallback(
getter_AddRefs(timeout),
[=](auto) {
result->Reject(NS_ERROR_DOM_NETWORK_ERR, __func__);
CloseUserInterface(browsingContext);
},
StaticPrefs::
dom_security_credentialmanagement_identity_reject_delay_duration_ms(),
nsITimer::TYPE_ONE_SHOT, "IdentityCredentialTimeoutCallback");
if (NS_WARN_IF(NS_FAILED(rv))) {
result->Reject(NS_ERROR_FAILURE, __func__);
return result.forget();
}
}
// 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 IdentityProviderRequestOptions& provider : aOptions.mProviders) {
RefPtr<GetManifestPromise> manifest = FetchManifest(principal, provider);
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__,
[browsingContext, aOptions](
const GetManifestPromise::AllSettledPromiseType::ResolveValueType&
aResults) {
// Convert the
// GetManifestPromise::AllSettledPromiseType::ResolveValueType to a
// Sequence<MozPromise>
CopyableTArray<MozPromise<IdentityProviderAPIConfig, nsresult,
true>::ResolveOrRejectValue>
results = aResults;
const Sequence<MozPromise<IdentityProviderAPIConfig, nsresult,
true>::ResolveOrRejectValue>
resultsSequence(std::move(results));
// If we can skip the provider check, because there is only one
// option and it is already linked, do so!
Maybe<IdentityProviderRequestOptionsWithManifest>
autoSelectedIdentityProvider =
SkipAccountChooser(aOptions.mProviders, resultsSequence);
if (autoSelectedIdentityProvider.isSome()) {
return GetIdentityProviderRequestOptionsWithManifestPromise::
CreateAndResolve(autoSelectedIdentityProvider.extract(),
__func__);
}
// The user picks from the providers
return PromptUserToSelectProvider(
browsingContext, aOptions.mProviders, resultsSequence);
},
[](bool error) {
return GetIdentityProviderRequestOptionsWithManifestPromise::
CreateAndReject(NS_ERROR_FAILURE, __func__);
})
->Then(
GetCurrentSerialEventTarget(), __func__,
[aMediationRequirement, principal,
browsingContext](const IdentityProviderRequestOptionsWithManifest&
providerAndManifest) {
IdentityProviderAPIConfig manifest;
IdentityProviderRequestOptions provider;
std::tie(provider, manifest) = providerAndManifest;
return CreateCredentialDuringDiscovery(principal, browsingContext,
provider, manifest,
aMediationRequirement);
},
[](nsresult error) {
return GetIPCIdentityCredentialPromise::CreateAndReject(error,
__func__);
})
->Then(
GetCurrentSerialEventTarget(), __func__,
[result, timeout = std::move(timeout)](
const GetIPCIdentityCredentialPromise::ResolveOrRejectValue&&
value) {
// Resolve the result
result->ResolveOrReject(value, __func__);
// Cancel the timer (if it is still pending) and
// release the hold on the variables leaked into the timer.
if (timeout &&
StaticPrefs::
dom_security_credentialmanagement_identity_reject_delay_enabled()) {
timeout->Cancel();
}
});
return result;
}
// static
Maybe<IdentityProviderRequestOptionsWithManifest> SkipAccountChooser(
const Sequence<IdentityProviderRequestOptions>& aProviders,
const Sequence<GetManifestPromise::ResolveOrRejectValue>& aManifests) {
if (aProviders.Length() != 1) {
return Nothing();
}
if (aManifests.Length() != 1) {
return Nothing();
}
if (!aManifests.ElementAt(0).IsResolve()) {
return Nothing();
}
const IdentityProviderRequestOptions& resolvedProvider =
aProviders.ElementAt(0);
const IdentityProviderAPIConfig& resolvedManifest =
aManifests.ElementAt(0).ResolveValue();
return Some(std::make_tuple(resolvedProvider, resolvedManifest));
}
// static
Maybe<IdentityProviderAccount> FindAccountToReauthenticate(
const IdentityProviderRequestOptions& aProvider, nsIPrincipal* aRPPrincipal,
const IdentityProviderAccountList& aAccountList) {
if (!aAccountList.mAccounts.WasPassed()) {
return Nothing();
}
nsresult rv;
nsCOMPtr<nsIIdentityCredentialStorageService> icStorageService =
mozilla::components::IdentityCredentialStorageService::Service(&rv);
if (NS_WARN_IF(!icStorageService)) {
return Nothing();
}
Maybe<IdentityProviderAccount> result = Nothing();
for (const IdentityProviderAccount& account :
aAccountList.mAccounts.Value()) {
// Don't reauthenticate accounts that have an approved clients list but no
// matching clientID from navigator.credentials.get's argument
if (account.mApproved_clients.WasPassed()) {
if (!account.mApproved_clients.Value().Contains(
NS_ConvertUTF8toUTF16(aProvider.mClientId))) {
continue;
}
}
RefPtr<nsIURI> configURI;
nsresult rv = NS_NewURI(getter_AddRefs(configURI), aProvider.mConfigURL);
if (NS_FAILED(rv)) {
continue;
}
nsCOMPtr<nsIPrincipal> idpPrincipal = BasePrincipal::CreateContentPrincipal(
configURI, aRPPrincipal->OriginAttributesRef());
// Don't reauthenticate unconnected accounts
bool connected = false;
rv = icStorageService->Connected(aRPPrincipal, idpPrincipal, &connected);
if (NS_WARN_IF(NS_FAILED(rv)) || !connected) {
continue;
}
// Don't reauthenticate if silent access is disabled
bool silentAllowed = false;
rv = CanSilentlyCollect(aRPPrincipal, idpPrincipal, &silentAllowed);
if (!NS_WARN_IF(NS_FAILED(rv)) && !silentAllowed) {
continue;
}
// We only auto-reauthenticate if we have one candidate.
if (result.isSome()) {
return Nothing();
}
// Remember our first candidate so we can return it after
// this loop, or return nothing if we find another!
result = Some(account);
}
return result;
}
// static
RefPtr<GetIPCIdentityCredentialPromise> CreateCredentialDuringDiscovery(
nsIPrincipal* aPrincipal, BrowsingContext* aBrowsingContext,
const IdentityProviderRequestOptions& aProvider,
const IdentityProviderAPIConfig& aManifest,
const CredentialMediationRequirement& aMediationRequirement) {
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(aPrincipal);
MOZ_ASSERT(aBrowsingContext);
nsCOMPtr<nsIPrincipal> argumentPrincipal = aPrincipal;
RefPtr<BrowsingContext> browsingContext(aBrowsingContext);
return FetchAccountList(argumentPrincipal, aProvider, aManifest)
->Then(
GetCurrentSerialEventTarget(), __func__,
[argumentPrincipal, browsingContext, aManifest, aMediationRequirement,
aProvider](
const std::tuple<IdentityProviderAPIConfig,
IdentityProviderAccountList>& promiseResult) {
IdentityProviderAPIConfig currentManifest;
IdentityProviderAccountList accountList;
std::tie(currentManifest, accountList) = promiseResult;
if (!accountList.mAccounts.WasPassed() ||
accountList.mAccounts.Value().Length() == 0) {
return GetAccountPromise::CreateAndReject(NS_ERROR_FAILURE,
__func__);
}
// Remove accounts without a matching login hint if one was provided
// in the JS call
if (aProvider.mLoginHint.WasPassed()) {
const nsCString& loginHint = aProvider.mLoginHint.Value();
accountList.mAccounts.Value().RemoveElementsBy(
[loginHint](const IdentityProviderAccount& account) {
if (!account.mLogin_hints.WasPassed() ||
account.mLogin_hints.Value().Length() == 0) {
return true;
}
if (account.mLogin_hints.Value().Contains(loginHint)) {
return false;
}
return true;
});
}
// Remove accounts without a matching domain hint if one was
// provided in the JS call
if (aProvider.mDomainHint.WasPassed()) {
const nsCString& domainHint = aProvider.mDomainHint.Value();
accountList.mAccounts.Value().RemoveElementsBy(
[domainHint](const IdentityProviderAccount& account) {
if (!account.mDomain_hints.WasPassed() ||
account.mDomain_hints.Value().Length() == 0) {
return true;
}
// The domain hint "any" matches any hint.
if (domainHint.Equals("any")) {
return false;
}
if (account.mDomain_hints.Value().Contains(domainHint)) {
return false;
}
return true;
});
}
// Remove accounts without a matching account hint if a label was
// provided in the IDP config
if (currentManifest.mAccount_label.WasPassed()) {
const nsCString& accountHint =
currentManifest.mAccount_label.Value();
accountList.mAccounts.Value().RemoveElementsBy(
[accountHint](const IdentityProviderAccount& account) {
if (!account.mLabel_hints.WasPassed() ||
account.mLabel_hints.Value().Length() == 0) {
return true;
}
if (account.mLabel_hints.Value().Contains(accountHint)) {
return false;
}
return true;
});
}
// If we can skip showing the user any UI by just doing a silent
// renewal, do so.
if (aMediationRequirement !=
CredentialMediationRequirement::Required) {
Maybe<IdentityProviderAccount> reauthenticatingAccount =
FindAccountToReauthenticate(aProvider, argumentPrincipal,
accountList);
if (reauthenticatingAccount.isSome()) {
return GetAccountPromise::CreateAndResolve(
std::make_tuple(aManifest,
reauthenticatingAccount.extract()),
__func__);
}
}
if (accountList.mAccounts.Value().Length() < 1) {
return GetAccountPromise::CreateAndReject(
NS_ERROR_DOM_NETWORK_ERR, __func__);
}
return PromptUserToSelectAccount(browsingContext, accountList,
aProvider, currentManifest);
},
[](nsresult error) {
return GetAccountPromise::CreateAndReject(error, __func__);
})
->Then(
GetCurrentSerialEventTarget(), __func__,
[argumentPrincipal, browsingContext, aProvider](
const std::tuple<IdentityProviderAPIConfig,
IdentityProviderAccount>& promiseResult) {
IdentityProviderAPIConfig currentManifest;
IdentityProviderAccount account;
std::tie(currentManifest, account) = promiseResult;
return PromptUserWithPolicy(browsingContext, argumentPrincipal,
account, currentManifest, aProvider);
},
[](nsresult error) {
return GetAccountPromise::CreateAndReject(error, __func__);
})
->Then(
GetCurrentSerialEventTarget(), __func__,
[argumentPrincipal, aProvider](
const std::tuple<IdentityProviderAPIConfig,
IdentityProviderAccount>& promiseResult) {
IdentityProviderAPIConfig currentManifest;
IdentityProviderAccount account;
std::tie(currentManifest, account) = promiseResult;
return FetchToken(argumentPrincipal, aProvider, currentManifest,
account);
},
[](nsresult error) {
return GetTokenPromise::CreateAndReject(error, __func__);
})
->Then(
GetCurrentSerialEventTarget(), __func__,
[aProvider](
const std::tuple<IdentityProviderToken, IdentityProviderAccount>&
promiseResult) {
IdentityProviderToken token;
IdentityProviderAccount account;
std::tie(token, account) = promiseResult;
IPCIdentityCredential credential;
credential.token() = Some(token.mToken);
credential.id() = account.mId;
return GetIPCIdentityCredentialPromise::CreateAndResolve(credential,
__func__);
},
[browsingContext](nsresult error) {
CloseUserInterface(browsingContext);
return GetIPCIdentityCredentialPromise::CreateAndReject(error,
__func__);
});
}
// static
RefPtr<GetRootManifestPromise> FetchRootManifest(
nsIPrincipal* aPrincipal, const IdentityProviderConfig& aProvider) {
MOZ_ASSERT(XRE_IsParentProcess());
if (StaticPrefs::
dom_security_credentialmanagement_identity_test_ignore_well_known()) {
return GetRootManifestPromise::CreateAndResolve(Nothing(), __func__);
}
// Build the URL
nsCString configLocation = aProvider.mConfigURL;
nsCOMPtr<nsIURI> configURI;
nsresult rv = NS_NewURI(getter_AddRefs(configURI), configLocation);
if (NS_WARN_IF(NS_FAILED(rv))) {
return GetRootManifestPromise::CreateAndReject(rv, __func__);
}
RefPtr<nsIEffectiveTLDService> etld =
mozilla::components::EffectiveTLD::Service();
if (!etld) {
return GetRootManifestPromise::CreateAndReject(
NS_ERROR_SERVICE_NOT_AVAILABLE, __func__);
}
nsCString manifestURIString;
rv = etld->GetSite(configURI, manifestURIString);
if (NS_FAILED(rv)) {
return GetRootManifestPromise::CreateAndReject(NS_ERROR_INVALID_ARG,
__func__);
}
nsAutoCString wellKnownPathForTesting;
rv = Preferences::GetCString(
"dom.security.credentialmanagement.identity.test_well_known_path",
wellKnownPathForTesting);
if (NS_SUCCEEDED(rv) && !wellKnownPathForTesting.IsVoid() &&
!wellKnownPathForTesting.IsEmpty()) {
manifestURIString.Append(wellKnownPathForTesting);
} else {
manifestURIString.AppendLiteral("/.well-known/web-identity");
}
nsCOMPtr<nsIURI> manifestURI;
rv = NS_NewURI(getter_AddRefs(manifestURI), manifestURIString, nullptr);
if (NS_FAILED(rv)) {
return GetRootManifestPromise::CreateAndReject(NS_ERROR_INVALID_ARG,
__func__);
}
// We actually don't need to do any of this well-known stuff if the
// requesting principal is same-site to the manifest URI. There is no
// privacy risk in that case, because the requests could be sent with
// their unpartitioned cookies anyway.
if (!aPrincipal->GetIsNullPrincipal()) {
bool thirdParty = true;
rv = aPrincipal->IsThirdPartyURI(manifestURI, &thirdParty);
if (NS_SUCCEEDED(rv) && !thirdParty) {
return GetRootManifestPromise::CreateAndResolve(Nothing(), __func__);
}
}
return IdentityNetworkHelpers::FetchWellKnownHelper(manifestURI, aPrincipal)
->Then(
GetCurrentSerialEventTarget(), __func__,
[aProvider](const IdentityProviderWellKnown& manifest) {
// Resolve whether or not the argument URL is found in
// the well-known
if (manifest.mProvider_urls.Contains(aProvider.mConfigURL)) {
return GetRootManifestPromise::CreateAndResolve(Some(manifest),
__func__);
}
return GetRootManifestPromise::CreateAndReject(NS_ERROR_FAILURE,
__func__);
},
[](nsresult error) {
return GetRootManifestPromise::CreateAndReject(error, __func__);
});
}
// static
RefPtr<GetManifestPromise> FetchManifest(
nsIPrincipal* aPrincipal, const IdentityProviderConfig& aProvider) {
MOZ_ASSERT(XRE_IsParentProcess());
nsCOMPtr<nsIPrincipal> requestingPrincipal(aPrincipal);
return FetchRootManifest(aPrincipal, aProvider)
->Then(
GetCurrentSerialEventTarget(), __func__,
[aProvider,
requestingPrincipal](Maybe<IdentityProviderWellKnown> rootManifest) {
// Build the URL
nsCString configLocation = aProvider.mConfigURL;
nsCOMPtr<nsIURI> manifestURI;
nsresult rv =
NS_NewURI(getter_AddRefs(manifestURI), configLocation, nullptr);
if (NS_FAILED(rv)) {
return MozPromise<std::tuple<Maybe<IdentityProviderWellKnown>,
IdentityProviderAPIConfig>,
nsresult,
true>::CreateAndReject(NS_ERROR_INVALID_ARG,
__func__);
}
return IdentityNetworkHelpers::FetchConfigHelper(
manifestURI, requestingPrincipal, rootManifest);
},
[](nsresult error) {
return MozPromise<std::tuple<Maybe<IdentityProviderWellKnown>,
IdentityProviderAPIConfig>,
nsresult, true>::CreateAndReject(error, __func__);
})
->Then(
GetCurrentSerialEventTarget(), __func__,
[aProvider](std::tuple<Maybe<IdentityProviderWellKnown>,
IdentityProviderAPIConfig>
manifests) {
IdentityProviderAPIConfig currentManifest;
Maybe<IdentityProviderWellKnown> fetchedWellKnown;
std::tie(fetchedWellKnown, currentManifest) = manifests;
// If we have more than one provider URL, we need to make sure that
// the accounts endpoint matches
nsCString configLocation = aProvider.mConfigURL;
if (fetchedWellKnown.isSome()) {
IdentityProviderWellKnown wellKnown(fetchedWellKnown.extract());
if (wellKnown.mProvider_urls.Length() == 1) {
if (!wellKnown.mProvider_urls.Contains(configLocation)) {
return GetManifestPromise::CreateAndReject(NS_ERROR_FAILURE,
__func__);
}
} else if (!wellKnown.mProvider_urls.Contains(configLocation) ||
!wellKnown.mAccounts_endpoint.WasPassed() ||
!wellKnown.mAccounts_endpoint.Value().Equals(
currentManifest.mAccounts_endpoint)) {
return GetManifestPromise::CreateAndReject(NS_ERROR_FAILURE,
__func__);
}
}
return GetManifestPromise::CreateAndResolve<
mozilla::dom::IdentityProviderAPIConfig>(
IdentityProviderAPIConfig(currentManifest), __func__);
},
[](nsresult error) {
return GetManifestPromise::CreateAndReject(error, __func__);
});
}
// static
RefPtr<GetAccountListPromise> FetchAccountList(
nsIPrincipal* aPrincipal, const IdentityProviderRequestOptions& aProvider,
const IdentityProviderAPIConfig& aManifest) {
MOZ_ASSERT(XRE_IsParentProcess());
// Build the URL
nsCOMPtr<nsIURI> baseURI;
nsresult rv = NS_NewURI(getter_AddRefs(baseURI), aProvider.mConfigURL);
if (NS_WARN_IF(NS_FAILED(rv))) {
return GetAccountListPromise::CreateAndReject(rv, __func__);
}
nsCOMPtr<nsIURI> idpURI;
rv = NS_NewURI(getter_AddRefs(idpURI), aManifest.mAccounts_endpoint, nullptr,
baseURI);
if (NS_WARN_IF(NS_FAILED(rv))) {
return GetAccountListPromise::CreateAndReject(rv, __func__);
}
nsCOMPtr<nsIPrincipal> idpPrincipal = BasePrincipal::CreateContentPrincipal(
idpURI, aPrincipal->OriginAttributesRef());
return IdentityNetworkHelpers::FetchAccountsHelper(idpURI, idpPrincipal)
->Then(
GetCurrentSerialEventTarget(), __func__,
[aManifest](const IdentityProviderAccountList& accountList) {
return GetAccountListPromise::CreateAndResolve(
std::make_tuple(aManifest, accountList), __func__);
},
[](nsresult error) {
return GetAccountListPromise::CreateAndReject(error, __func__);
});
}
// static
RefPtr<GetTokenPromise> FetchToken(
nsIPrincipal* aPrincipal, const IdentityProviderRequestOptions& aProvider,
const IdentityProviderAPIConfig& aManifest,
const IdentityProviderAccount& aAccount) {
MOZ_ASSERT(XRE_IsParentProcess());
// Build the URL
nsCOMPtr<nsIURI> baseURI;
nsCString baseURIString = aProvider.mConfigURL;
nsresult rv = NS_NewURI(getter_AddRefs(baseURI), baseURIString);
if (NS_WARN_IF(NS_FAILED(rv))) {
return GetTokenPromise::CreateAndReject(rv, __func__);
}
nsCOMPtr<nsIURI> idpURI;
nsCString tokenSpec = aManifest.mId_assertion_endpoint;
rv = NS_NewURI(getter_AddRefs(idpURI), tokenSpec.get(), baseURI);
if (NS_WARN_IF(NS_FAILED(rv))) {
return GetTokenPromise::CreateAndReject(rv, __func__);
}
nsCString tokenLocation;
rv = idpURI->GetSpec(tokenLocation);
if (NS_WARN_IF(NS_FAILED(rv))) {
return GetTokenPromise::CreateAndReject(rv, __func__);
}
// Create a new request
URLParams bodyValue;
bodyValue.Set("account_id"_ns, NS_ConvertUTF16toUTF8(aAccount.mId));
bodyValue.Set("client_id"_ns, aProvider.mClientId);
if (aProvider.mNonce.WasPassed()) {
bodyValue.Set("nonce"_ns, aProvider.mNonce.Value());
}
bodyValue.Set("disclosure_text_shown"_ns, "false"_ns);
bodyValue.Set("is_auto_selected"_ns, "false"_ns);
nsAutoCString bodyCString;
bodyValue.Serialize(bodyCString, true);
return IdentityNetworkHelpers::FetchTokenHelper(idpURI, bodyCString,
aPrincipal)
->Then(
GetCurrentSerialEventTarget(), __func__,
[aAccount](const IdentityProviderToken& token) {
return GetTokenPromise::CreateAndResolve(
std::make_tuple(token, aAccount), __func__);
},
[](nsresult error) {
return GetTokenPromise::CreateAndReject(error, __func__);
});
}
// static
RefPtr<MozPromise<bool, nsresult, true>> DisconnectInMainProcess(
nsIPrincipal* aDocumentPrincipal,
const IdentityCredentialDisconnectOptions& aOptions) {
MOZ_ASSERT(XRE_IsParentProcess());
nsresult rv;
nsCOMPtr<nsIIdentityCredentialStorageService> icStorageService =
mozilla::components::IdentityCredentialStorageService::Service(&rv);
if (NS_WARN_IF(!icStorageService)) {
return MozPromise<bool, nsresult, true>::CreateAndReject(rv, __func__);
}
RefPtr<MozPromise<bool, nsresult, true>::Private> resultPromise =
new MozPromise<bool, nsresult, true>::Private(__func__);
RefPtr<nsIURI> configURI;
rv = NS_NewURI(getter_AddRefs(configURI), aOptions.mConfigURL);
if (NS_FAILED(rv)) {
resultPromise->Reject(NS_ERROR_DOM_MALFORMED_URI, __func__);
return resultPromise;
}
nsCOMPtr<nsIPrincipal> principal(aDocumentPrincipal);
nsCOMPtr<nsIPrincipal> idpPrincipal = BasePrincipal::CreateContentPrincipal(
configURI, principal->OriginAttributesRef());
FetchManifest(principal, aOptions)
->Then(
GetCurrentSerialEventTarget(), __func__,
[resultPromise, aOptions, icStorageService, configURI, idpPrincipal,
principal](const IdentityProviderAPIConfig& aConfig) {
if (!aConfig.mDisconnect_endpoint.WasPassed()) {
resultPromise->Reject(NS_ERROR_DOM_NETWORK_ERR, __func__);
return MozPromise<DisconnectedAccount, nsresult,
true>::CreateAndReject(NS_OK, __func__);
}
RefPtr<nsIURI> disconnectURI;
nsCString disconnectArgument = aConfig.mDisconnect_endpoint.Value();
nsresult rv = NS_NewURI(getter_AddRefs(disconnectURI),
disconnectArgument, nullptr, configURI);
if (NS_FAILED(rv)) {
resultPromise->Reject(NS_ERROR_DOM_NETWORK_ERR, __func__);
return MozPromise<DisconnectedAccount, nsresult,
true>::CreateAndReject(NS_OK, __func__);
}
bool connected = false;
rv = icStorageService->Connected(principal, idpPrincipal,
&connected);
if (NS_WARN_IF(NS_FAILED(rv)) || !connected) {
resultPromise->Reject(NS_ERROR_DOM_NETWORK_ERR, __func__);
return MozPromise<DisconnectedAccount, nsresult,
true>::CreateAndReject(NS_OK, __func__);
}
// Create a new request
URLParams bodyValue;
bodyValue.Set("client_id"_ns, aOptions.mClientId);
bodyValue.Set("account_hint"_ns, aOptions.mAccountHint);
nsAutoCString bodyCString;
bodyValue.Serialize(bodyCString, true);
return IdentityNetworkHelpers::FetchDisconnectHelper(
disconnectURI, bodyCString, principal);
},
[resultPromise](nsresult aError) {
resultPromise->Reject(aError, __func__);
// We reject with NS_OK, so that we don't disconnect accounts in the
// reject callback here.
return MozPromise<DisconnectedAccount, nsresult,
true>::CreateAndReject(NS_OK, __func__);
})
->Then(
GetCurrentSerialEventTarget(), __func__,
[icStorageService, principal, idpPrincipal,
resultPromise](const DisconnectedAccount& token) {
bool registered = false, notUsed = false;
nsresult rv = icStorageService->GetState(principal, idpPrincipal,
token.mAccount_id,
&registered, &notUsed);
if (NS_WARN_IF(NS_FAILED(rv))) {
resultPromise->Reject(NS_ERROR_UNEXPECTED, __func__);
return;
}
if (registered) {
nsresult rv = icStorageService->Delete(principal, idpPrincipal,
token.mAccount_id);
if (NS_WARN_IF(NS_FAILED(rv))) {
resultPromise->Reject(NS_ERROR_UNEXPECTED, __func__);
return;
}
resultPromise->Resolve(true, __func__);
} else {
nsresult rv =
icStorageService->Disconnect(principal, idpPrincipal);
if (NS_WARN_IF(NS_FAILED(rv))) {
resultPromise->Reject(NS_ERROR_UNEXPECTED, __func__);
return;
}
resultPromise->Resolve(true, __func__);
}
return;
},
[icStorageService, principal, idpPrincipal,
resultPromise](nsresult error) {
// Bail out if we already rejected the result above.
if (error == NS_OK) {
return;
}
// If we issued the request and it failed, fall back
// to clearing all.
nsresult rv = icStorageService->Disconnect(principal, idpPrincipal);
if (NS_WARN_IF(NS_FAILED(rv))) {
resultPromise->Reject(NS_ERROR_UNEXPECTED, __func__);
return;
}
resultPromise->Resolve(true, __func__);
return;
});
return resultPromise;
}
// static
RefPtr<GetIdentityProviderRequestOptionsWithManifestPromise>
PromptUserToSelectProvider(
BrowsingContext* aBrowsingContext,
const Sequence<IdentityProviderRequestOptions>& aProviders,
const Sequence<GetManifestPromise::ResolveOrRejectValue>& aManifests) {
MOZ_ASSERT(aBrowsingContext);
RefPtr<GetIdentityProviderRequestOptionsWithManifestPromise::Private>
resultPromise =
new GetIdentityProviderRequestOptionsWithManifestPromise::Private(
__func__);
if (NS_WARN_IF(!aBrowsingContext)) {
resultPromise->Reject(NS_ERROR_FAILURE, __func__);
return resultPromise;
}
nsresult error;
nsCOMPtr<nsIIdentityCredentialPromptService> icPromptService =
mozilla::components::IdentityCredentialPromptService::Service(&error);
if (NS_WARN_IF(!icPromptService)) {
resultPromise->Reject(error, __func__);
return resultPromise;
}
nsCOMPtr<nsIXPConnectWrappedJS> wrapped = do_QueryInterface(icPromptService);
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(wrapped->GetJSObjectGlobal()))) {
resultPromise->Reject(NS_ERROR_FAILURE, __func__);
return resultPromise;
}
JS::Rooted<JS::Value> providersJS(jsapi.cx());
bool success = ToJSValue(jsapi.cx(), aProviders, &providersJS);
if (NS_WARN_IF(!success)) {
resultPromise->Reject(NS_ERROR_FAILURE, __func__);
return resultPromise;
}
// Convert each settled MozPromise into a Nullable<ResolveValue>
Sequence<Nullable<IdentityProviderAPIConfig>> 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<IdentityProviderAPIConfig>(), 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));
showPromptPromise->AddCallbacksWithCycleCollectedArgs(
[aProviders, aManifests, resultPromise](
JSContext*, JS::Handle<JS::Value> aValue, ErrorResult&) {
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;
}
const IdentityProviderRequestOptions& resolvedProvider =
aProviders.ElementAt(result);
if (!aManifests.ElementAt(result).IsResolve()) {
resultPromise->Reject(NS_ERROR_FAILURE, __func__);
return;
}
const IdentityProviderAPIConfig& resolvedManifest =
aManifests.ElementAt(result).ResolveValue();
resultPromise->Resolve(
std::make_tuple(resolvedProvider, resolvedManifest), __func__);
},
[resultPromise](JSContext*, JS::Handle<JS::Value> aValue, ErrorResult&) {
resultPromise->Reject(
Promise::TryExtractNSResultFromRejectionValue(aValue), __func__);
});
showPromptPromise->AppendNativeHandler(
new MozPromiseRejectOnDestruction{resultPromise, __func__});
return resultPromise;
}
// static
RefPtr<GetAccountPromise> PromptUserToSelectAccount(
BrowsingContext* aBrowsingContext,
const IdentityProviderAccountList& aAccounts,
const IdentityProviderRequestOptions& aProvider,
const IdentityProviderAPIConfig& aManifest) {
MOZ_ASSERT(aBrowsingContext);
RefPtr<GetAccountPromise::Private> resultPromise =
new GetAccountPromise::Private(__func__);
if (NS_WARN_IF(!aBrowsingContext)) {
resultPromise->Reject(NS_ERROR_FAILURE, __func__);
return resultPromise;
}
nsresult error;
nsCOMPtr<nsIIdentityCredentialPromptService> icPromptService =
mozilla::components::IdentityCredentialPromptService::Service(&error);
if (NS_WARN_IF(!icPromptService)) {
resultPromise->Reject(error, __func__);
return resultPromise;
}
nsCOMPtr<nsIXPConnectWrappedJS> wrapped = do_QueryInterface(icPromptService);
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(wrapped->GetJSObjectGlobal()))) {
resultPromise->Reject(NS_ERROR_FAILURE, __func__);
return resultPromise;
}
JS::Rooted<JS::Value> accountsJS(jsapi.cx());
bool success = ToJSValue(jsapi.cx(), aAccounts, &accountsJS);
if (NS_WARN_IF(!success)) {
resultPromise->Reject(NS_ERROR_FAILURE, __func__);
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));
showPromptPromise->AddCallbacksWithCycleCollectedArgs(
[aAccounts, resultPromise, aManifest](
JSContext*, JS::Handle<JS::Value> aValue, ErrorResult&) {
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;
}
const IdentityProviderAccount& resolved =
aAccounts.mAccounts.Value().ElementAt(result);
resultPromise->Resolve(std::make_tuple(aManifest, resolved), __func__);
},
[resultPromise](JSContext*, JS::Handle<JS::Value> aValue, ErrorResult&) {
resultPromise->Reject(
Promise::TryExtractNSResultFromRejectionValue(aValue), __func__);
});
showPromptPromise->AppendNativeHandler(
new MozPromiseRejectOnDestruction{resultPromise, __func__});
return resultPromise;
}
// static
RefPtr<GetAccountPromise> PromptUserWithPolicy(
BrowsingContext* aBrowsingContext, nsIPrincipal* aPrincipal,
const IdentityProviderAccount& aAccount,
const IdentityProviderAPIConfig& aManifest,
const IdentityProviderRequestOptions& aProvider) {
MOZ_ASSERT(aBrowsingContext);
MOZ_ASSERT(aPrincipal);
nsresult error;
nsCOMPtr<nsIIdentityCredentialStorageService> icStorageService =
mozilla::components::IdentityCredentialStorageService::Service(&error);
if (NS_WARN_IF(!icStorageService)) {
return GetAccountPromise::CreateAndReject(error, __func__);
}
// Check the storage bit
nsCString configLocation = aProvider.mConfigURL;
nsCOMPtr<nsIURI> idpURI;
error = NS_NewURI(getter_AddRefs(idpURI), configLocation);
if (NS_WARN_IF(NS_FAILED(error))) {
return GetAccountPromise::CreateAndReject(error, __func__);
}
bool registered = false;
bool allowLogout = false;
nsCOMPtr<nsIPrincipal> idpPrincipal = BasePrincipal::CreateContentPrincipal(
idpURI, aPrincipal->OriginAttributesRef());
error = icStorageService->GetState(aPrincipal, idpPrincipal,
NS_ConvertUTF16toUTF8(aAccount.mId),
&registered, &allowLogout);
if (NS_WARN_IF(NS_FAILED(error))) {
return GetAccountPromise::CreateAndReject(error, __func__);
}
// Mark as logged in and return
icStorageService->SetState(aPrincipal, idpPrincipal,
NS_ConvertUTF16toUTF8(aAccount.mId), true, true);
return GetAccountPromise::CreateAndResolve(
std::make_tuple(aManifest, aAccount), __func__);
}
// static
void CloseUserInterface(BrowsingContext* aBrowsingContext) {
nsresult error;
nsCOMPtr<nsIIdentityCredentialPromptService> icPromptService =
mozilla::components::IdentityCredentialPromptService::Service(&error);
if (NS_WARN_IF(!icPromptService)) {
return;
}
icPromptService->Close(aBrowsingContext);
}
} // namespace identity
} // namespace mozilla::dom