/* 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/dom/WebIdentityParent.h" #include "mozilla/Components.h" #include "mozilla/IdentityCredentialRequestManager.h" #include "mozilla/dom/IdentityNetworkHelpers.h" #include "mozilla/dom/NavigatorLogin.h" #include "mozilla/dom/WindowGlobalParent.h" #include "nsIEffectiveTLDService.h" #include "nsIIdentityCredentialPromptService.h" #include "nsIIdentityCredentialStorageService.h" #include "nsIXPConnect.h" #include "nsScriptSecurityManager.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(Manager()); if (!manager) { aResolver(NS_ERROR_FAILURE); } identity::GetCredentialInMainProcess( manager->DocumentPrincipal(), this, 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(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(Manager()); if (!manager) { aResolver(NS_ERROR_FAILURE); } nsIPrincipal* principal = manager->DocumentPrincipal(); if (principal) { nsCOMPtr 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(Manager()); if (!manager) { aResolver(NS_ERROR_FAILURE); return IPC_OK(); } 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(); } mozilla::ipc::IPCResult WebIdentityParent::RecvResolveContinuationWindow( nsCString&& aToken, IdentityResolveOptions&& aOptions, const ResolveContinuationWindowResolver& aResolver) { // Pass the resolution on to the ICRM to handle it. // Faithfully convey its error in resolution. IdentityCredentialRequestManager* requestManager = IdentityCredentialRequestManager::GetInstance(); if (!requestManager) { aResolver(NS_ERROR_NOT_AVAILABLE); return IPC_OK(); } nsresult rv = requestManager->MaybeResolvePopup(this, aToken, aOptions); aResolver(rv); return IPC_OK(); } mozilla::ipc::IPCResult WebIdentityParent::RecvIsActiveContinuationWindow( const IsActiveContinuationWindowResolver& aResolver) { IdentityCredentialRequestManager* requestManager = IdentityCredentialRequestManager::GetInstance(); if (!requestManager) { aResolver(false); return IPC_OK(); } aResolver(requestManager->IsActivePopup(this)); 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 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 GetCredentialInMainProcess( nsIPrincipal* aPrincipal, WebIdentityParent* aRelyingParty, IdentityCredentialRequestOptions&& aOptions, const CredentialMediationRequirement& aMediationRequirement, bool aHasUserActivation) { MOZ_ASSERT(aPrincipal); MOZ_ASSERT(aRelyingParty); 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 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 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 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 result = new GetIPCIdentityCredentialPromise::Private(__func__); DiscoverFromExternalSourceInMainProcess(aPrincipal, aRelyingParty, 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 DiscoverFromExternalSourceInMainProcess( nsIPrincipal* aPrincipal, WebIdentityParent* aRelyingParty, const IdentityCredentialRequestOptions& aOptions, const CredentialMediationRequirement& aMediationRequirement) { MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(aPrincipal); MOZ_ASSERT(aRelyingParty); // Make sure we have providers. if (aOptions.mProviders.Length() < 1) { return GetIPCIdentityCredentialPromise::CreateAndReject( NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__); } nsCOMPtr principal(aPrincipal); RefPtr browsingContext = aRelyingParty->MaybeBrowsingContext(); if (!browsingContext) { return GetIPCIdentityCredentialPromise::CreateAndReject( NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__); } RefPtr relyingParty = aRelyingParty; RefPtr result = new GetIPCIdentityCredentialPromise::Private(__func__); RefPtr 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"_ns); 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> manifestPromises; for (const IdentityProviderRequestOptions& provider : aOptions.mProviders) { RefPtr 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 CopyableTArray::ResolveOrRejectValue> results = aResults; const Sequence::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 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, relyingParty](const IdentityProviderRequestOptionsWithManifest& providerAndManifest) { IdentityProviderAPIConfig manifest; IdentityProviderRequestOptions provider; std::tie(provider, manifest) = providerAndManifest; return CreateCredentialDuringDiscovery(principal, relyingParty, 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 SkipAccountChooser( const Sequence& aProviders, const Sequence& 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 FindAccountToReauthenticate( const IdentityProviderRequestOptions& aProvider, nsIPrincipal* aRPPrincipal, const IdentityProviderAccountList& aAccountList) { if (!aAccountList.mAccounts.WasPassed()) { return Nothing(); } nsresult rv; nsCOMPtr icStorageService = mozilla::components::IdentityCredentialStorageService::Service(&rv); if (NS_WARN_IF(!icStorageService)) { return Nothing(); } Maybe 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 configURI; nsresult rv = NS_NewURI(getter_AddRefs(configURI), aProvider.mConfigURL); if (NS_FAILED(rv)) { continue; } nsCOMPtr 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 CreateCredentialDuringDiscovery( nsIPrincipal* aPrincipal, WebIdentityParent* aRelyingParty, const IdentityProviderRequestOptions& aProvider, const IdentityProviderAPIConfig& aManifest, const CredentialMediationRequirement& aMediationRequirement) { MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(aPrincipal); MOZ_ASSERT(aRelyingParty); nsCOMPtr argumentPrincipal = aPrincipal; RefPtr relyingParty(aRelyingParty); RefPtr browsingContext( aRelyingParty->MaybeBrowsingContext()); if (!browsingContext) { return GetIPCIdentityCredentialPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); } return FetchAccountList(argumentPrincipal, aProvider, aManifest) ->Then( GetCurrentSerialEventTarget(), __func__, [argumentPrincipal, browsingContext, aManifest, aMediationRequirement, aProvider]( const std::tuple& 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 reauthenticatingAccount = FindAccountToReauthenticate(aProvider, argumentPrincipal, accountList); if (reauthenticatingAccount.isSome()) { return GetAccountPromise::CreateAndResolve( std::make_tuple(aManifest, reauthenticatingAccount.extract(), true), __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, aProvider, relyingParty](const std::tuple& promiseResult) { IdentityProviderAPIConfig currentManifest; IdentityProviderAccount account; bool isAutoSelected; std::tie(currentManifest, account, isAutoSelected) = promiseResult; return FetchToken(argumentPrincipal, relyingParty, aProvider, currentManifest, account, isAutoSelected); }, [](nsresult error) { return GetTokenPromise::CreateAndReject(error, __func__); }) ->Then( GetCurrentSerialEventTarget(), __func__, [argumentPrincipal, aProvider](const std::tuple& promiseResult) { nsCString token; nsCString accountId; bool isAutoSelected; std::tie(token, accountId, isAutoSelected) = promiseResult; IPCIdentityCredential credential; credential.token() = Some(token); credential.id() = NS_ConvertUTF8toUTF16(accountId); credential.isAutoSelected() = isAutoSelected; credential.configURL() = aProvider.mConfigURL; // We always make sure accounts are linked after we successfully // fetch a token nsresult rv = LinkAccount(argumentPrincipal, accountId, aProvider); if (NS_FAILED(rv)) { return GetIPCIdentityCredentialPromise::CreateAndReject(rv, __func__); } return GetIPCIdentityCredentialPromise::CreateAndResolve(credential, __func__); }, [browsingContext](nsresult error) { CloseUserInterface(browsingContext); return GetIPCIdentityCredentialPromise::CreateAndReject(error, __func__); }); } // static RefPtr 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 configURI; nsresult rv = NS_NewURI(getter_AddRefs(configURI), configLocation); if (NS_WARN_IF(NS_FAILED(rv))) { return GetRootManifestPromise::CreateAndReject(rv, __func__); } RefPtr 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 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 FetchManifest( nsIPrincipal* aPrincipal, const IdentityProviderConfig& aProvider) { MOZ_ASSERT(XRE_IsParentProcess()); nsCOMPtr requestingPrincipal(aPrincipal); return FetchRootManifest(aPrincipal, aProvider) ->Then( GetCurrentSerialEventTarget(), __func__, [aProvider, requestingPrincipal](Maybe rootManifest) { // Build the URL nsCString configLocation = aProvider.mConfigURL; nsCOMPtr manifestURI; nsresult rv = NS_NewURI(getter_AddRefs(manifestURI), configLocation, nullptr); if (NS_FAILED(rv)) { return MozPromise, IdentityProviderAPIConfig>, nsresult, true>::CreateAndReject(NS_ERROR_INVALID_ARG, __func__); } return IdentityNetworkHelpers::FetchConfigHelper( manifestURI, requestingPrincipal, rootManifest); }, [](nsresult error) { return MozPromise, IdentityProviderAPIConfig>, nsresult, true>::CreateAndReject(error, __func__); }) ->Then( GetCurrentSerialEventTarget(), __func__, [aProvider](std::tuple, IdentityProviderAPIConfig> manifests) { IdentityProviderAPIConfig currentManifest; Maybe 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 FetchAccountList( nsIPrincipal* aPrincipal, const IdentityProviderRequestOptions& aProvider, const IdentityProviderAPIConfig& aManifest) { MOZ_ASSERT(XRE_IsParentProcess()); // Build the URL nsCOMPtr baseURI; nsresult rv = NS_NewURI(getter_AddRefs(baseURI), aProvider.mConfigURL); if (NS_WARN_IF(NS_FAILED(rv))) { return GetAccountListPromise::CreateAndReject(rv, __func__); } nsCOMPtr 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 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 FetchToken( nsIPrincipal* aPrincipal, WebIdentityParent* aRelyingParty, const IdentityProviderRequestOptions& aProvider, const IdentityProviderAPIConfig& aManifest, const IdentityProviderAccount& aAccount, const bool isAutoSelected) { MOZ_ASSERT(XRE_IsParentProcess()); // Build the URL nsCOMPtr 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 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); nsCString serializedIsAutoSelected = isAutoSelected ? "true"_ns : "false"_ns; bodyValue.Set("is_auto_selected"_ns, serializedIsAutoSelected); nsAutoCString bodyCString; bodyValue.Serialize(bodyCString, true); RefPtr relyingParty(aRelyingParty); return IdentityNetworkHelpers::FetchTokenHelper(idpURI, bodyCString, aPrincipal) ->Then( GetCurrentSerialEventTarget(), __func__, [aAccount, idpURI, relyingParty, isAutoSelected](const IdentityAssertionResponse& response) { // If we were provided a token, resolve with it. if (response.mToken.WasPassed()) { return GetTokenPromise::CreateAndResolve( std::make_tuple(response.mToken.Value(), NS_ConvertUTF16toUTF8(aAccount.mId), isAutoSelected), __func__); } // If we don't have a continuation window to open at this stage, // reject with the appropriate error. if (!response.mContinue_on.WasPassed()) { return GetTokenPromise::CreateAndReject(NS_ERROR_DOM_NETWORK_ERR, __func__); } // Construct the URI we are going to open nsCOMPtr continueURI; nsCString continueSpec = response.mContinue_on.Value(); nsresult rv = NS_NewURI(getter_AddRefs(continueURI), continueSpec.get()); if (NS_FAILED(rv)) { return GetTokenPromise::CreateAndReject(NS_ERROR_DOM_NETWORK_ERR, __func__); } // It must be same-origin to the URL used to fetch to identity // assertion if (!nsScriptSecurityManager::SecurityCompareURIs(continueURI, idpURI)) { return GetTokenPromise::CreateAndReject(NS_ERROR_DOM_NETWORK_ERR, __func__); } // Open the popup, and return the result of its interaction return AuthorizationPopupForToken(continueURI, relyingParty, aAccount, isAutoSelected); }, [](nsresult error) { return GetTokenPromise::CreateAndReject(error, __func__); }); } RefPtr AuthorizationPopupForToken( nsIURI* aContinueURI, WebIdentityParent* aRelyingParty, const IdentityProviderAccount& aAccount, const bool isAutoSelected) { MOZ_ASSERT(aContinueURI); IdentityCredentialRequestManager* requestManager = IdentityCredentialRequestManager::GetInstance(); if (!requestManager) { return GetTokenPromise::CreateAndReject(NS_ERROR_DOM_NETWORK_ERR, __func__); } // Start the process of getting a token for the popup. // The request manager opens the popup and holds a ref to the promise // returned. This then gets settles depending on the popup page's behavior (or // rejects on close). return requestManager->GetTokenFromPopup(aRelyingParty, aContinueURI) ->Then( GetCurrentSerialEventTarget(), __func__, [aAccount, isAutoSelected]( const std::tuple>& promiseResult) { // We will resolve either way with our token from here, but // We may have an account ID to override the user's selection in the // account chooser. nsCString token; Maybe overridingAccountId; std::tie(token, overridingAccountId) = promiseResult; if (overridingAccountId.isSome()) { return GetTokenPromise::CreateAndResolve( std::make_tuple(token, overridingAccountId.value(), isAutoSelected), __func__); } return GetTokenPromise::CreateAndResolve( std::make_tuple(token, NS_ConvertUTF16toUTF8(aAccount.mId), isAutoSelected), __func__); }, [](nsresult rv) { return GetTokenPromise::CreateAndReject(NS_ERROR_DOM_NETWORK_ERR, __func__); }); } // static RefPtr> DisconnectInMainProcess( nsIPrincipal* aDocumentPrincipal, const IdentityCredentialDisconnectOptions& aOptions) { MOZ_ASSERT(XRE_IsParentProcess()); nsresult rv; nsCOMPtr icStorageService = mozilla::components::IdentityCredentialStorageService::Service(&rv); if (NS_WARN_IF(!icStorageService)) { return MozPromise::CreateAndReject(rv, __func__); } RefPtr::Private> resultPromise = new MozPromise::Private(__func__); RefPtr configURI; rv = NS_NewURI(getter_AddRefs(configURI), aOptions.mConfigURL); if (NS_FAILED(rv)) { resultPromise->Reject(NS_ERROR_DOM_MALFORMED_URI, __func__); return resultPromise; } nsCOMPtr principal(aDocumentPrincipal); nsCOMPtr 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::CreateAndReject(NS_OK, __func__); } RefPtr 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::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::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::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, ®istered, ¬Used); 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 PromptUserToSelectProvider( BrowsingContext* aBrowsingContext, const Sequence& aProviders, const Sequence& aManifests) { MOZ_ASSERT(aBrowsingContext); RefPtr resultPromise = new GetIdentityProviderRequestOptionsWithManifestPromise::Private( __func__); if (NS_WARN_IF(!aBrowsingContext)) { resultPromise->Reject(NS_ERROR_FAILURE, __func__); return resultPromise; } nsresult error; nsCOMPtr icPromptService = mozilla::components::IdentityCredentialPromptService::Service(&error); if (NS_WARN_IF(!icPromptService)) { resultPromise->Reject(error, __func__); return resultPromise; } nsCOMPtr wrapped = do_QueryInterface(icPromptService); AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(wrapped->GetJSObjectGlobal()))) { resultPromise->Reject(NS_ERROR_FAILURE, __func__); return resultPromise; } JS::Rooted 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 Sequence> 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(), fallible))) { resultPromise->Reject(NS_ERROR_FAILURE, __func__); return resultPromise; } } } JS::Rooted manifestsJS(jsapi.cx()); success = ToJSValue(jsapi.cx(), manifests, &manifestsJS); if (NS_WARN_IF(!success)) { resultPromise->Reject(NS_ERROR_FAILURE, __func__); return resultPromise; } RefPtr showPromptPromise; icPromptService->ShowProviderPrompt(aBrowsingContext, providersJS, manifestsJS, getter_AddRefs(showPromptPromise)); showPromptPromise->AddCallbacksWithCycleCollectedArgs( [aProviders, aManifests, resultPromise]( JSContext*, JS::Handle 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 aValue, ErrorResult&) { resultPromise->Reject( Promise::TryExtractNSResultFromRejectionValue(aValue), __func__); }); // Working around https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85883 showPromptPromise->AppendNativeHandler( new MozPromiseRejectOnDestruction{resultPromise, __func__}); return resultPromise; } // static RefPtr PromptUserToSelectAccount( BrowsingContext* aBrowsingContext, const IdentityProviderAccountList& aAccounts, const IdentityProviderRequestOptions& aProvider, const IdentityProviderAPIConfig& aManifest) { MOZ_ASSERT(aBrowsingContext); RefPtr resultPromise = new GetAccountPromise::Private(__func__); if (NS_WARN_IF(!aBrowsingContext)) { resultPromise->Reject(NS_ERROR_FAILURE, __func__); return resultPromise; } nsresult error; nsCOMPtr icPromptService = mozilla::components::IdentityCredentialPromptService::Service(&error); if (NS_WARN_IF(!icPromptService)) { resultPromise->Reject(error, __func__); return resultPromise; } nsCOMPtr wrapped = do_QueryInterface(icPromptService); AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(wrapped->GetJSObjectGlobal()))) { resultPromise->Reject(NS_ERROR_FAILURE, __func__); return resultPromise; } JS::Rooted 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 providerJS(jsapi.cx()); success = ToJSValue(jsapi.cx(), aProvider, &providerJS); if (NS_WARN_IF(!success)) { resultPromise->Reject(NS_ERROR_FAILURE, __func__); return resultPromise; } JS::Rooted manifestJS(jsapi.cx()); success = ToJSValue(jsapi.cx(), aManifest, &manifestJS); if (NS_WARN_IF(!success)) { resultPromise->Reject(NS_ERROR_FAILURE, __func__); return resultPromise; } RefPtr showPromptPromise; icPromptService->ShowAccountListPrompt(aBrowsingContext, accountsJS, providerJS, manifestJS, getter_AddRefs(showPromptPromise)); showPromptPromise->AddCallbacksWithCycleCollectedArgs( [aAccounts, resultPromise, aManifest]( JSContext*, JS::Handle 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, false), __func__); }, [resultPromise](JSContext*, JS::Handle aValue, ErrorResult&) { resultPromise->Reject( Promise::TryExtractNSResultFromRejectionValue(aValue), __func__); }); // Working around https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85883 showPromptPromise->AppendNativeHandler( new MozPromiseRejectOnDestruction{resultPromise, __func__}); return resultPromise; } // static nsresult LinkAccount(nsIPrincipal* aPrincipal, const nsCString& aAccountId, const IdentityProviderRequestOptions& aProvider) { MOZ_ASSERT(aPrincipal); nsresult error; nsCOMPtr icStorageService = mozilla::components::IdentityCredentialStorageService::Service(&error); if (NS_WARN_IF(!icStorageService)) { return NS_ERROR_NOT_AVAILABLE; } // Check the storage bit nsCString configLocation = aProvider.mConfigURL; nsCOMPtr idpURI; error = NS_NewURI(getter_AddRefs(idpURI), configLocation); if (NS_WARN_IF(NS_FAILED(error))) { return error; } nsCOMPtr idpPrincipal = BasePrincipal::CreateContentPrincipal( idpURI, aPrincipal->OriginAttributesRef()); // Mark as logged in and return icStorageService->SetState(aPrincipal, idpPrincipal, aAccountId, true, true); nsCOMPtr permissionManager = components::PermissionManager::Service(); if (NS_WARN_IF(!permissionManager)) { return NS_ERROR_NOT_AVAILABLE; } nsCString idpOrigin; error = idpPrincipal->GetOrigin(idpOrigin); NS_ENSURE_SUCCESS(error, error); permissionManager->AddFromPrincipal( aPrincipal, "credential-allow-silent-access^"_ns + idpOrigin, nsIPermissionManager::ALLOW_ACTION, nsIPermissionManager::EXPIRE_SESSION, 0); permissionManager->AddFromPrincipal(aPrincipal, "credential-allow-silent-access"_ns, nsIPermissionManager::ALLOW_ACTION, nsIPermissionManager::EXPIRE_SESSION, 0); return NS_OK; } // static void CloseUserInterface(BrowsingContext* aBrowsingContext) { nsresult error; nsCOMPtr icPromptService = mozilla::components::IdentityCredentialPromptService::Service(&error); if (NS_WARN_IF(!icPromptService)) { return; } icPromptService->Close(aBrowsingContext); } } // namespace identity } // namespace mozilla::dom