summaryrefslogtreecommitdiffstats
path: root/dom/credentialmanagement/identity
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /dom/credentialmanagement/identity
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--dom/credentialmanagement/identity/IPCIdentityCredential.ipdlh18
-rw-r--r--dom/credentialmanagement/identity/IdentityCredential.cpp1001
-rw-r--r--dom/credentialmanagement/identity/IdentityCredential.h189
-rw-r--r--dom/credentialmanagement/identity/IdentityCredentialSerializationHelpers.h48
-rw-r--r--dom/credentialmanagement/identity/IdentityNetworkHelpers.h95
-rw-r--r--dom/credentialmanagement/identity/moz.build27
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/head.js24
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/helper_set_cookie.html8
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/helper_set_cookie.html^headers^1
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/mochitest.ini48
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/server_accounts_error_accounts.sjs9
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/server_accounts_error_idtoken.sjs15
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/server_accounts_redirect_accounts.sjs10
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/server_accounts_redirect_idtoken.sjs15
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/server_idtoken_error_accounts.sjs24
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/server_idtoken_error_idtoken.sjs9
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/server_idtoken_redirect_accounts.sjs24
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/server_idtoken_redirect_idtoken.sjs10
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/server_manifest.sjs46
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/server_manifest_wrong_provider_in_manifest.sjs19
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/server_metadata.json4
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/server_metadata.json^headers^2
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/server_no_accounts_accounts.sjs38
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/server_no_accounts_idtoken.sjs66
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/server_simple_accounts.sjs47
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/server_simple_idtoken.sjs66
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/server_two_accounts_accounts.sjs55
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/server_two_accounts_idtoken.sjs66
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/server_two_providers_accounts.sjs48
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/server_two_providers_idtoken.sjs59
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/test_accounts_error.html37
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/test_accounts_redirect.html37
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/test_delay_reject.html39
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/test_empty_provider_list.html34
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/test_get_without_providers.html32
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/test_idtoken_error.html37
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/test_idtoken_redirect.html37
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/test_no_accounts.html37
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/test_simple.html46
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/test_two_accounts.html46
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/test_two_providers.html52
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/test_wrong_provider_in_manifest.html37
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/web-identity1
-rw-r--r--dom/credentialmanagement/identity/tests/mochitest/web-identity^headers^2
44 files changed, 2565 insertions, 0 deletions
diff --git a/dom/credentialmanagement/identity/IPCIdentityCredential.ipdlh b/dom/credentialmanagement/identity/IPCIdentityCredential.ipdlh
new file mode 100644
index 0000000000..3502a58b4f
--- /dev/null
+++ b/dom/credentialmanagement/identity/IPCIdentityCredential.ipdlh
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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/. */
+
+namespace mozilla {
+namespace dom {
+
+struct IPCIdentityCredential
+{
+ nsString id;
+ nsString type;
+ nsString token;
+};
+
+}
+}
diff --git a/dom/credentialmanagement/identity/IdentityCredential.cpp b/dom/credentialmanagement/identity/IdentityCredential.cpp
new file mode 100644
index 0000000000..c901e7d4ed
--- /dev/null
+++ b/dom/credentialmanagement/identity/IdentityCredential.cpp
@@ -0,0 +1,1001 @@
+/* -*- 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/dom/ContentChild.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Fetch.h"
+#include "mozilla/dom/IdentityCredential.h"
+#include "mozilla/dom/IdentityNetworkHelpers.h"
+#include "mozilla/dom/Request.h"
+#include "mozilla/dom/WindowGlobalChild.h"
+#include "mozilla/Components.h"
+#include "mozilla/ExpandedPrincipal.h"
+#include "mozilla/NullPrincipal.h"
+#include "nsEffectiveTLDService.h"
+#include "nsIGlobalObject.h"
+#include "nsIIdentityCredentialPromptService.h"
+#include "nsIIdentityCredentialStorageService.h"
+#include "nsITimer.h"
+#include "nsIXPConnect.h"
+#include "nsNetUtil.h"
+#include "nsStringStream.h"
+#include "nsURLHelper.h"
+
+namespace mozilla::dom {
+
+IdentityCredential::~IdentityCredential() = default;
+
+JSObject* IdentityCredential::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return IdentityCredential_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+IdentityCredential::IdentityCredential(nsPIDOMWindowInner* aParent)
+ : Credential(aParent) {}
+
+void IdentityCredential::CopyValuesFrom(const IPCIdentityCredential& aOther) {
+ this->SetToken(aOther.token());
+ this->SetId(aOther.id());
+ this->SetType(aOther.type());
+}
+
+IPCIdentityCredential IdentityCredential::MakeIPCIdentityCredential() {
+ nsString token, id, type;
+ GetToken(token);
+ GetId(id);
+ GetType(type);
+ IPCIdentityCredential result;
+ result.token() = token;
+ result.id() = id;
+ result.type() = type;
+ return result;
+}
+
+void IdentityCredential::GetToken(nsAString& aToken) const {
+ aToken.Assign(mToken);
+}
+void IdentityCredential::SetToken(const nsAString& aToken) {
+ mToken.Assign(aToken);
+}
+
+// static
+RefPtr<IdentityCredential::GetIdentityCredentialPromise>
+IdentityCredential::DiscoverFromExternalSource(
+ nsPIDOMWindowInner* aParent, const CredentialRequestOptions& aOptions,
+ bool aSameOriginWithAncestors) {
+ MOZ_ASSERT(XRE_IsContentProcess());
+ MOZ_ASSERT(aParent);
+ // Prevent origin confusion by requiring no cross domain iframes
+ // in this one's ancestry
+ if (!aSameOriginWithAncestors) {
+ return IdentityCredential::GetIdentityCredentialPromise::CreateAndReject(
+ NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__);
+ }
+
+ Document* parentDocument = aParent->GetExtantDoc();
+ if (!parentDocument) {
+ return IdentityCredential::GetIdentityCredentialPromise::CreateAndReject(
+ NS_ERROR_FAILURE, __func__);
+ }
+
+ RefPtr<IdentityCredential::GetIdentityCredentialPromise::Private> result =
+ new IdentityCredential::GetIdentityCredentialPromise::Private(__func__);
+
+ if (StaticPrefs::
+ dom_security_credentialmanagement_identity_reject_delay_enabled()) {
+ // This is used to give the promise the appropriate lifetime so it is not
+ // freed before the callback below is called. This reference is taken as an
+ // argument to that callback.
+ RefPtr<IdentityCredential::GetIdentityCredentialPromise::Private>
+ forCallbackResult = result;
+
+ RefPtr<nsITimer> timeout;
+ nsresult rv = NS_NewTimerWithFuncCallback(
+ getter_AddRefs(timeout),
+ [](nsITimer* aTimer, void* aClosure) -> void {
+ auto* promise = static_cast<
+ IdentityCredential::GetIdentityCredentialPromise::Private*>(
+ aClosure);
+ if (!promise->IsResolved()) {
+ promise->Reject(NS_ERROR_DOM_NETWORK_ERR, __func__);
+ }
+ // This releases the promise we forgot when we returned from
+ // this function and the timer we forgot after we built this
+ // callback.
+ NS_RELEASE(promise);
+ NS_RELEASE(aTimer);
+ },
+ do_AddRef(forCallbackResult).take(),
+ 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();
+ }
+
+ // Do not clean this timer when we return form this function. This will be
+ // done at the end of the callback above.
+ Unused << timeout.forget();
+ }
+
+ // Kick the request off to the main process and translate the result to the
+ // expected type when we get a result.
+ MOZ_ASSERT(aOptions.mIdentity.WasPassed());
+ RefPtr<WindowGlobalChild> wgc = aParent->GetWindowGlobalChild();
+ MOZ_ASSERT(wgc);
+ RefPtr<IdentityCredential> credential = new IdentityCredential(aParent);
+ wgc->SendDiscoverIdentityCredentialFromExternalSource(
+ aOptions.mIdentity.Value())
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [result,
+ credential](const WindowGlobalChild::
+ DiscoverIdentityCredentialFromExternalSourcePromise::
+ ResolveValueType& aResult) {
+ if (aResult.isSome()) {
+ credential->CopyValuesFrom(aResult.value());
+ result->Resolve(credential, __func__);
+ } else if (
+ !StaticPrefs::
+ dom_security_credentialmanagement_identity_reject_delay_enabled()) {
+ result->Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
+ }
+ },
+ [result](const WindowGlobalChild::
+ DiscoverIdentityCredentialFromExternalSourcePromise::
+ RejectValueType& aResult) {
+ if (!StaticPrefs::
+ dom_security_credentialmanagement_identity_reject_delay_enabled()) {
+ result->Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
+ }
+ });
+ return result.forget();
+}
+
+// static
+RefPtr<IdentityCredential::GetIPCIdentityCredentialPromise>
+IdentityCredential::DiscoverFromExternalSourceInMainProcess(
+ nsIPrincipal* aPrincipal, CanonicalBrowsingContext* aBrowsingContext,
+ const IdentityCredentialRequestOptions& aOptions) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(aBrowsingContext);
+
+ // Make sure we have providers.
+ if (!aOptions.mProviders.WasPassed() ||
+ aOptions.mProviders.Value().Length() < 1) {
+ return IdentityCredential::GetIPCIdentityCredentialPromise::CreateAndReject(
+ NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__);
+ }
+
+ nsCOMPtr<nsIPrincipal> principal(aPrincipal);
+ RefPtr<CanonicalBrowsingContext> browsingContext(aBrowsingContext);
+
+ // Have the user choose a provider.
+ return PromptUserToSelectProvider(aBrowsingContext,
+ aOptions.mProviders.Value())
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [principal, browsingContext](const IdentityProvider& provider) {
+ return IdentityCredential::CreateCredential(
+ principal, browsingContext, provider);
+ },
+ [](nsresult error) {
+ return IdentityCredential::GetIPCIdentityCredentialPromise::
+ CreateAndReject(error, __func__);
+ });
+}
+
+// static
+RefPtr<IdentityCredential::GetIPCIdentityCredentialPromise>
+IdentityCredential::CreateCredential(nsIPrincipal* aPrincipal,
+ BrowsingContext* aBrowsingContext,
+ const IdentityProvider& aProvider) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(aBrowsingContext);
+
+ 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__);
+ })
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [argumentPrincipal, browsingContext, aProvider](
+ const Tuple<IdentityInternalManifest, IdentityAccountList>&
+ promiseResult) {
+ IdentityInternalManifest currentManifest;
+ IdentityAccountList accountList;
+ Tie(currentManifest, accountList) = promiseResult;
+ if (!accountList.mAccounts.WasPassed() ||
+ accountList.mAccounts.Value().Length() == 0) {
+ return IdentityCredential::GetAccountPromise::CreateAndReject(
+ NS_ERROR_FAILURE, __func__);
+ }
+ return PromptUserToSelectAccount(browsingContext, accountList,
+ currentManifest);
+ },
+ [](nsresult error) {
+ return IdentityCredential::GetAccountPromise::CreateAndReject(
+ error, __func__);
+ })
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [argumentPrincipal, browsingContext,
+ aProvider](const Tuple<IdentityInternalManifest, IdentityAccount>&
+ promiseResult) {
+ IdentityInternalManifest currentManifest;
+ IdentityAccount account;
+ Tie(currentManifest, account) = promiseResult;
+ return IdentityCredential::PromptUserWithPolicy(
+ browsingContext, argumentPrincipal, account, currentManifest,
+ aProvider);
+ },
+ [](nsresult error) {
+ return IdentityCredential::GetAccountPromise::CreateAndReject(
+ error, __func__);
+ })
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [argumentPrincipal,
+ aProvider](const Tuple<IdentityInternalManifest, IdentityAccount>&
+ promiseResult) {
+ IdentityInternalManifest currentManifest;
+ IdentityAccount account;
+ Tie(currentManifest, account) = promiseResult;
+ return IdentityCredential::FetchToken(argumentPrincipal, aProvider,
+ currentManifest, account);
+ },
+ [](nsresult error) {
+ return IdentityCredential::GetTokenPromise::CreateAndReject(
+ error, __func__);
+ })
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [aProvider](
+ const Tuple<IdentityToken, IdentityAccount>& promiseResult) {
+ IdentityToken token;
+ IdentityAccount account;
+ Tie(token, account) = promiseResult;
+ IPCIdentityCredential credential;
+ credential.token() = token.mToken;
+ credential.id() = account.mId;
+ credential.type() = u"identity"_ns;
+ return IdentityCredential::GetIPCIdentityCredentialPromise::
+ CreateAndResolve(credential, __func__);
+ },
+ [browsingContext](nsresult error) {
+ CloseUserInterface(browsingContext);
+ return IdentityCredential::GetIPCIdentityCredentialPromise::
+ CreateAndReject(error, __func__);
+ });
+}
+
+// static
+RefPtr<IdentityCredential::ValidationPromise>
+IdentityCredential::CheckRootManifest(nsIPrincipal* aPrincipal,
+ const IdentityProvider& aProvider) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ // Build the URL
+ nsString configLocation = aProvider.mConfigURL;
+ nsCOMPtr<nsIURI> configURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(configURI), configLocation);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return IdentityCredential::ValidationPromise::CreateAndReject(rv, __func__);
+ }
+ RefPtr<nsEffectiveTLDService> etld = nsEffectiveTLDService::GetInstance();
+ if (!etld) {
+ return IdentityCredential::ValidationPromise::CreateAndReject(
+ NS_ERROR_SERVICE_NOT_AVAILABLE, __func__);
+ }
+ nsCString manifestURIString;
+ rv = etld->GetSite(configURI, manifestURIString);
+ if (NS_FAILED(rv)) {
+ return IdentityCredential::ValidationPromise::CreateAndReject(
+ NS_ERROR_INVALID_ARG, __func__);
+ }
+ manifestURIString.AppendLiteral("/.well-known/web-identity");
+
+ // Create the global
+ RefPtr<NullPrincipal> nullPrincipal =
+ NullPrincipal::CreateWithInheritedAttributes(aPrincipal);
+ nsIXPConnect* xpc = nsContentUtils::XPConnect();
+ MOZ_ASSERT(xpc, "This should never be null!");
+ nsCOMPtr<nsIGlobalObject> global;
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> sandbox(cx);
+ rv = xpc->CreateSandbox(cx, nullPrincipal, sandbox.address());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return IdentityCredential::ValidationPromise::CreateAndReject(rv, __func__);
+ }
+ MOZ_ASSERT(JS_IsGlobalObject(sandbox));
+ global = xpc::NativeGlobal(sandbox);
+ if (NS_WARN_IF(!global)) {
+ return IdentityCredential::ValidationPromise::CreateAndReject(
+ NS_ERROR_FAILURE, __func__);
+ }
+
+ // Create a new request
+ constexpr auto fragment = ""_ns;
+ auto internalRequest =
+ MakeSafeRefPtr<InternalRequest>(manifestURIString, fragment);
+ internalRequest->SetCredentialsMode(RequestCredentials::Omit);
+ internalRequest->SetReferrerPolicy(ReferrerPolicy::No_referrer);
+ internalRequest->SetMode(RequestMode::Cors);
+ internalRequest->SetCacheMode(RequestCache::No_cache);
+ internalRequest->SetHeaders(new InternalHeaders(HeadersGuardEnum::Request));
+ internalRequest->OverrideContentPolicyType(
+ nsContentPolicyType::TYPE_WEB_IDENTITY);
+ RefPtr<Request> request =
+ new Request(global, std::move(internalRequest), nullptr);
+
+ return FetchJSONStructure<IdentityRootManifest>(request)->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [aProvider](const IdentityRootManifest& manifest) {
+ // Make sure there is only one provider URL
+ if (manifest.mProvider_urls.Length() != 1) {
+ return IdentityCredential::ValidationPromise::CreateAndResolve(
+ false, __func__);
+ }
+
+ // Resolve whether or not that provider URL is the one we were
+ // passed as an argument.
+ bool correctURL = manifest.mProvider_urls[0] == aProvider.mConfigURL;
+ return IdentityCredential::ValidationPromise::CreateAndResolve(
+ correctURL, __func__);
+ },
+ [](nsresult error) {
+ return IdentityCredential::ValidationPromise::CreateAndReject(error,
+ __func__);
+ });
+}
+
+// static
+RefPtr<IdentityCredential::GetManifestPromise>
+IdentityCredential::FetchInternalManifest(nsIPrincipal* aPrincipal,
+ const IdentityProvider& aProvider) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ // Build the URL
+ nsCString configLocation = NS_ConvertUTF16toUTF8(aProvider.mConfigURL);
+
+ // Create the global
+ RefPtr<NullPrincipal> nullPrincipal =
+ NullPrincipal::CreateWithInheritedAttributes(aPrincipal);
+ nsIXPConnect* xpc = nsContentUtils::XPConnect();
+ MOZ_ASSERT(xpc, "This should never be null!");
+ nsCOMPtr<nsIGlobalObject> global;
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> sandbox(cx);
+ nsresult rv = xpc->CreateSandbox(cx, nullPrincipal, sandbox.address());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return IdentityCredential::GetManifestPromise::CreateAndReject(rv,
+ __func__);
+ }
+ MOZ_ASSERT(JS_IsGlobalObject(sandbox));
+ global = xpc::NativeGlobal(sandbox);
+ if (NS_WARN_IF(!global)) {
+ return IdentityCredential::GetManifestPromise::CreateAndReject(
+ NS_ERROR_FAILURE, __func__);
+ }
+
+ // Create a new request
+ constexpr auto fragment = ""_ns;
+ auto internalRequest =
+ MakeSafeRefPtr<InternalRequest>(configLocation, fragment);
+ internalRequest->SetRedirectMode(RequestRedirect::Error);
+ internalRequest->SetCredentialsMode(RequestCredentials::Omit);
+ internalRequest->SetReferrerPolicy(ReferrerPolicy::No_referrer);
+ internalRequest->SetMode(RequestMode::Cors);
+ internalRequest->SetCacheMode(RequestCache::No_cache);
+ internalRequest->SetHeaders(new InternalHeaders(HeadersGuardEnum::Request));
+ internalRequest->OverrideContentPolicyType(
+ nsContentPolicyType::TYPE_WEB_IDENTITY);
+ RefPtr<Request> request =
+ new Request(global, std::move(internalRequest), nullptr);
+ return FetchJSONStructure<IdentityInternalManifest>(request);
+}
+
+// static
+RefPtr<IdentityCredential::GetAccountListPromise>
+IdentityCredential::FetchAccountList(
+ nsIPrincipal* aPrincipal, const IdentityProvider& aProvider,
+ const IdentityInternalManifest& aManifest) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ // Build the URL
+ nsCOMPtr<nsIURI> baseURI;
+ nsCString baseURIString = NS_ConvertUTF16toUTF8(aProvider.mConfigURL);
+ nsresult rv = NS_NewURI(getter_AddRefs(baseURI), baseURIString);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return IdentityCredential::GetAccountListPromise::CreateAndReject(rv,
+ __func__);
+ }
+ nsCOMPtr<nsIURI> idpURI;
+ nsCString accountSpec = NS_ConvertUTF16toUTF8(aManifest.mAccounts_endpoint);
+ rv = NS_NewURI(getter_AddRefs(idpURI), accountSpec.get(), baseURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return IdentityCredential::GetAccountListPromise::CreateAndReject(rv,
+ __func__);
+ }
+ nsCString configLocation;
+ rv = idpURI->GetSpec(configLocation);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return IdentityCredential::GetAccountListPromise::CreateAndReject(rv,
+ __func__);
+ }
+
+ // Build the principal to use for this connection
+ // This is an expanded principal! It has the cookies of the IDP because it
+ // subsumes the constituent principals. It also has no serializable origin,
+ // so it won't send an Origin header even though this is a CORS mode
+ // request. It accomplishes this without being a SystemPrincipal too.
+ nsCOMPtr<nsIPrincipal> idpPrincipal = BasePrincipal::CreateContentPrincipal(
+ idpURI, aPrincipal->OriginAttributesRef());
+ nsCOMPtr<nsIPrincipal> nullPrincipal =
+ NullPrincipal::CreateWithInheritedAttributes(aPrincipal);
+ AutoTArray<nsCOMPtr<nsIPrincipal>, 2> allowList = {idpPrincipal,
+ nullPrincipal};
+ RefPtr<ExpandedPrincipal> expandedPrincipal =
+ ExpandedPrincipal::Create(allowList, aPrincipal->OriginAttributesRef());
+
+ // Create the global
+ nsIXPConnect* xpc = nsContentUtils::XPConnect();
+ MOZ_ASSERT(xpc, "This should never be null!");
+ nsCOMPtr<nsIGlobalObject> global;
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> sandbox(cx);
+ rv = xpc->CreateSandbox(cx, expandedPrincipal, sandbox.address());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return IdentityCredential::GetAccountListPromise::CreateAndReject(rv,
+ __func__);
+ }
+ MOZ_ASSERT(JS_IsGlobalObject(sandbox));
+ global = xpc::NativeGlobal(sandbox);
+ if (NS_WARN_IF(!global)) {
+ return IdentityCredential::GetAccountListPromise::CreateAndReject(
+ NS_ERROR_FAILURE, __func__);
+ }
+
+ // Create a new request
+ constexpr auto fragment = ""_ns;
+ auto internalRequest =
+ MakeSafeRefPtr<InternalRequest>(configLocation, fragment);
+ internalRequest->SetRedirectMode(RequestRedirect::Error);
+ internalRequest->SetCredentialsMode(RequestCredentials::Include);
+ internalRequest->SetReferrerPolicy(ReferrerPolicy::No_referrer);
+ internalRequest->SetMode(RequestMode::Cors);
+ internalRequest->SetCacheMode(RequestCache::No_cache);
+ internalRequest->SetHeaders(new InternalHeaders(HeadersGuardEnum::Request));
+ internalRequest->OverrideContentPolicyType(
+ nsContentPolicyType::TYPE_WEB_IDENTITY);
+ RefPtr<Request> request =
+ new Request(global, std::move(internalRequest), nullptr);
+
+ return FetchJSONStructure<IdentityAccountList>(request)->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [aManifest](const IdentityAccountList& accountList) {
+ return IdentityCredential::GetAccountListPromise::CreateAndResolve(
+ MakeTuple(aManifest, accountList), __func__);
+ },
+ [](nsresult error) {
+ return IdentityCredential::GetAccountListPromise::CreateAndReject(
+ error, __func__);
+ });
+}
+
+// static
+RefPtr<IdentityCredential::GetTokenPromise> IdentityCredential::FetchToken(
+ nsIPrincipal* aPrincipal, const IdentityProvider& aProvider,
+ const IdentityInternalManifest& aManifest,
+ const IdentityAccount& aAccount) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ // Build the URL
+ nsCOMPtr<nsIURI> baseURI;
+ nsCString baseURIString = NS_ConvertUTF16toUTF8(aProvider.mConfigURL);
+ nsresult rv = NS_NewURI(getter_AddRefs(baseURI), baseURIString);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return IdentityCredential::GetTokenPromise::CreateAndReject(rv, __func__);
+ }
+ nsCOMPtr<nsIURI> idpURI;
+ nsCString tokenSpec = NS_ConvertUTF16toUTF8(aManifest.mId_token_endpoint);
+ rv = NS_NewURI(getter_AddRefs(idpURI), tokenSpec.get(), baseURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return IdentityCredential::GetTokenPromise::CreateAndReject(rv, __func__);
+ }
+ nsCString tokenLocation;
+ rv = idpURI->GetSpec(tokenLocation);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return IdentityCredential::GetTokenPromise::CreateAndReject(rv, __func__);
+ }
+
+ // Create the global
+ nsIXPConnect* xpc = nsContentUtils::XPConnect();
+ MOZ_ASSERT(xpc, "This should never be null!");
+ nsCOMPtr<nsIGlobalObject> global;
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> sandbox(cx);
+ rv = xpc->CreateSandbox(cx, aPrincipal, sandbox.address());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return IdentityCredential::GetTokenPromise::CreateAndReject(rv, __func__);
+ }
+ MOZ_ASSERT(JS_IsGlobalObject(sandbox));
+ global = xpc::NativeGlobal(sandbox);
+ if (NS_WARN_IF(!global)) {
+ return IdentityCredential::GetTokenPromise::CreateAndReject(
+ NS_ERROR_FAILURE, __func__);
+ }
+
+ // Create a new request
+ constexpr auto fragment = ""_ns;
+ auto internalRequest =
+ MakeSafeRefPtr<InternalRequest>(tokenLocation, fragment);
+ internalRequest->SetMethod("POST"_ns);
+ URLParams bodyValue;
+ bodyValue.Set(u"account_id"_ns, aAccount.mId);
+ bodyValue.Set(u"client_id"_ns, aProvider.mClientId);
+ if (aProvider.mNonce.WasPassed()) {
+ bodyValue.Set(u"nonce"_ns, aProvider.mNonce.Value());
+ }
+ bodyValue.Set(u"disclosure_text_shown"_ns, u"false"_ns);
+ nsString bodyString;
+ bodyValue.Serialize(bodyString, true);
+ nsCString bodyCString = NS_ConvertUTF16toUTF8(bodyString);
+ nsCOMPtr<nsIInputStream> streamBody;
+ rv = NS_NewCStringInputStream(getter_AddRefs(streamBody), bodyCString);
+ if (NS_FAILED(rv)) {
+ return IdentityCredential::GetTokenPromise::CreateAndReject(
+ NS_ERROR_FAILURE, __func__);
+ }
+
+ IgnoredErrorResult error;
+ RefPtr<InternalHeaders> internalHeaders =
+ new InternalHeaders(HeadersGuardEnum::Request);
+ internalHeaders->Set("Content-Type"_ns,
+ "application/x-www-form-urlencoded"_ns, error);
+ if (NS_WARN_IF(error.Failed())) {
+ return IdentityCredential::GetTokenPromise::CreateAndReject(
+ NS_ERROR_FAILURE, __func__);
+ }
+ internalRequest->SetHeaders(internalHeaders);
+ internalRequest->SetBody(streamBody, bodyCString.Length());
+ internalRequest->SetRedirectMode(RequestRedirect::Error);
+ internalRequest->SetCredentialsMode(RequestCredentials::Include);
+ internalRequest->SetReferrerPolicy(ReferrerPolicy::Strict_origin);
+ internalRequest->SetMode(RequestMode::Cors);
+ internalRequest->SetCacheMode(RequestCache::No_cache);
+ internalRequest->OverrideContentPolicyType(
+ nsContentPolicyType::TYPE_WEB_IDENTITY);
+ RefPtr<Request> request =
+ new Request(global, std::move(internalRequest), nullptr);
+ return FetchJSONStructure<IdentityToken>(request)->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [aAccount](const IdentityToken& token) {
+ return IdentityCredential::GetTokenPromise::CreateAndResolve(
+ MakeTuple(token, aAccount), __func__);
+ },
+ [](nsresult error) {
+ return IdentityCredential::GetTokenPromise::CreateAndReject(error,
+ __func__);
+ });
+}
+
+// static
+RefPtr<IdentityCredential::GetMetadataPromise>
+IdentityCredential::FetchMetadata(nsIPrincipal* aPrincipal,
+ const IdentityProvider& aProvider,
+ const IdentityInternalManifest& aManifest) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(aPrincipal);
+ // Build the URL
+ nsCOMPtr<nsIURI> baseURI;
+ nsCString baseURIString = NS_ConvertUTF16toUTF8(aProvider.mConfigURL);
+ nsresult rv = NS_NewURI(getter_AddRefs(baseURI), baseURIString);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return IdentityCredential::GetMetadataPromise::CreateAndReject(rv,
+ __func__);
+ }
+ nsCOMPtr<nsIURI> idpURI;
+ nsCString metadataSpec =
+ NS_ConvertUTF16toUTF8(aManifest.mClient_metadata_endpoint);
+ rv = NS_NewURI(getter_AddRefs(idpURI), metadataSpec.get(), baseURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return IdentityCredential::GetMetadataPromise::CreateAndReject(rv,
+ __func__);
+ }
+ nsCString configLocation;
+ rv = idpURI->GetSpec(configLocation);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return IdentityCredential::GetMetadataPromise::CreateAndReject(rv,
+ __func__);
+ }
+
+ // Create the global
+ nsIXPConnect* xpc = nsContentUtils::XPConnect();
+ MOZ_ASSERT(xpc, "This should never be null!");
+ nsCOMPtr<nsIGlobalObject> global;
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> sandbox(cx);
+ rv = xpc->CreateSandbox(cx, aPrincipal, sandbox.address());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return IdentityCredential::GetMetadataPromise::CreateAndReject(rv,
+ __func__);
+ }
+ MOZ_ASSERT(JS_IsGlobalObject(sandbox));
+ global = xpc::NativeGlobal(sandbox);
+ if (NS_WARN_IF(!global)) {
+ return IdentityCredential::GetMetadataPromise::CreateAndReject(
+ NS_ERROR_FAILURE, __func__);
+ }
+
+ // Create a new request
+ constexpr auto fragment = ""_ns;
+ auto internalRequest =
+ MakeSafeRefPtr<InternalRequest>(configLocation, fragment);
+ internalRequest->SetRedirectMode(RequestRedirect::Error);
+ internalRequest->SetCredentialsMode(RequestCredentials::Omit);
+ internalRequest->SetReferrerPolicy(ReferrerPolicy::No_referrer);
+ internalRequest->SetMode(RequestMode::Cors);
+ internalRequest->SetCacheMode(RequestCache::No_cache);
+ internalRequest->SetHeaders(new InternalHeaders(HeadersGuardEnum::Request));
+ internalRequest->OverrideContentPolicyType(
+ nsContentPolicyType::TYPE_WEB_IDENTITY);
+ RefPtr<Request> request =
+ new Request(global, std::move(internalRequest), nullptr);
+ return FetchJSONStructure<IdentityClientMetadata>(request);
+}
+
+// static
+RefPtr<IdentityCredential::GetIdentityProviderPromise>
+IdentityCredential::PromptUserToSelectProvider(
+ BrowsingContext* aBrowsingContext,
+ const Sequence<IdentityProvider>& aProviders) {
+ MOZ_ASSERT(aBrowsingContext);
+ RefPtr<IdentityCredential::GetIdentityProviderPromise::Private>
+ resultPromise =
+ new IdentityCredential::GetIdentityProviderPromise::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;
+ }
+
+ RefPtr<Promise> showPromptPromise;
+ icPromptService->ShowProviderPrompt(aBrowsingContext, providersJS,
+ 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) {
+ resultPromise->Reject(NS_ERROR_FAILURE, __func__);
+ return;
+ }
+ resultPromise->Resolve(result, __func__);
+ },
+ [resultPromise](nsresult aRv) { resultPromise->Reject(aRv, __func__); });
+ showPromptPromise->AppendNativeHandler(listener);
+
+ return resultPromise;
+}
+
+// static
+RefPtr<IdentityCredential::GetAccountPromise>
+IdentityCredential::PromptUserToSelectAccount(
+ BrowsingContext* aBrowsingContext, const IdentityAccountList& aAccounts,
+ const IdentityInternalManifest& aManifest) {
+ MOZ_ASSERT(aBrowsingContext);
+ RefPtr<IdentityCredential::GetAccountPromise::Private> resultPromise =
+ new IdentityCredential::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;
+ }
+
+ RefPtr<Promise> showPromptPromise;
+ icPromptService->ShowAccountListPrompt(aBrowsingContext, accountsJS,
+ 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) {
+ resultPromise->Reject(NS_ERROR_FAILURE, __func__);
+ return;
+ }
+ resultPromise->Resolve(MakeTuple(aManifest, result), __func__);
+ },
+ [resultPromise](nsresult aRv) { resultPromise->Reject(aRv, __func__); });
+ showPromptPromise->AppendNativeHandler(listener);
+
+ return resultPromise;
+}
+
+// static
+RefPtr<IdentityCredential::GetAccountPromise>
+IdentityCredential::PromptUserWithPolicy(
+ BrowsingContext* aBrowsingContext, nsIPrincipal* aPrincipal,
+ const IdentityAccount& aAccount, const IdentityInternalManifest& aManifest,
+ const IdentityProvider& aProvider) {
+ MOZ_ASSERT(aBrowsingContext);
+ MOZ_ASSERT(aPrincipal);
+
+ nsresult error;
+ nsCOMPtr<nsIIdentityCredentialStorageService> icStorageService =
+ mozilla::components::IdentityCredentialStorageService::Service(&error);
+ if (NS_WARN_IF(!icStorageService)) {
+ return IdentityCredential::GetAccountPromise::CreateAndReject(error,
+ __func__);
+ }
+
+ // Check the storage bit
+ nsCString configLocation = NS_ConvertUTF16toUTF8(aProvider.mConfigURL);
+ nsCOMPtr<nsIURI> idpURI;
+ error = NS_NewURI(getter_AddRefs(idpURI), configLocation);
+ if (NS_WARN_IF(NS_FAILED(error))) {
+ return IdentityCredential::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 IdentityCredential::GetAccountPromise::CreateAndReject(error,
+ __func__);
+ }
+
+ // if registered, mark as logged in and return
+ if (registered) {
+ icStorageService->SetState(aPrincipal, idpPrincipal,
+ NS_ConvertUTF16toUTF8(aAccount.mId), true, true);
+ return IdentityCredential::GetAccountPromise::CreateAndResolve(
+ MakeTuple(aManifest, aAccount), __func__);
+ }
+
+ // otherwise, fetch ->Then display ->Then return ->Catch reject
+ RefPtr<BrowsingContext> browsingContext(aBrowsingContext);
+ nsCOMPtr<nsIPrincipal> argumentPrincipal(aPrincipal);
+ return FetchMetadata(aPrincipal, aProvider, aManifest)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [aAccount, aProvider, argumentPrincipal, browsingContext,
+ icStorageService,
+ idpPrincipal](const IdentityClientMetadata& metadata)
+ -> RefPtr<GenericPromise> {
+ nsresult error;
+ nsCOMPtr<nsIIdentityCredentialPromptService> icPromptService =
+ mozilla::components::IdentityCredentialPromptService::Service(
+ &error);
+ if (NS_WARN_IF(!icPromptService)) {
+ return GenericPromise::CreateAndReject(error, __func__);
+ }
+ nsCOMPtr<nsIXPConnectWrappedJS> wrapped =
+ do_QueryInterface(icPromptService);
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(wrapped->GetJSObjectGlobal()))) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE,
+ __func__);
+ }
+
+ JS::Rooted<JS::Value> providerJS(jsapi.cx());
+ bool success = ToJSValue(jsapi.cx(), aProvider, &providerJS);
+ if (NS_WARN_IF(!success)) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE,
+ __func__);
+ }
+ JS::Rooted<JS::Value> metadataJS(jsapi.cx());
+ success = ToJSValue(jsapi.cx(), metadata, &metadataJS);
+ if (NS_WARN_IF(!success)) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE,
+ __func__);
+ }
+
+ RefPtr<Promise> showPromptPromise;
+ icPromptService->ShowPolicyPrompt(
+ browsingContext, providerJS, metadataJS,
+ getter_AddRefs(showPromptPromise));
+
+ RefPtr<GenericPromise::Private> resultPromise =
+ new GenericPromise::Private(__func__);
+ RefPtr<DomPromiseListener> listener = new DomPromiseListener(
+ [aAccount, argumentPrincipal, idpPrincipal, resultPromise,
+ icStorageService](JSContext* aCx,
+ JS::Handle<JS::Value> aValue) {
+ bool isBool = aValue.isBoolean();
+ if (!isBool) {
+ resultPromise->Reject(NS_ERROR_FAILURE, __func__);
+ return;
+ }
+ icStorageService->SetState(
+ argumentPrincipal, idpPrincipal,
+ NS_ConvertUTF16toUTF8(aAccount.mId), true, true);
+ resultPromise->Resolve(aValue.toBoolean(), __func__);
+ },
+ [resultPromise](nsresult aRv) {
+ resultPromise->Reject(aRv, __func__);
+ });
+ showPromptPromise->AppendNativeHandler(listener);
+ return resultPromise;
+ },
+ [](nsresult error) {
+ return GenericPromise::CreateAndReject(error, __func__);
+ })
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [aManifest, aAccount](bool success) {
+ if (success) {
+ return IdentityCredential::GetAccountPromise::CreateAndResolve(
+ MakeTuple(aManifest, aAccount), __func__);
+ }
+ return IdentityCredential::GetAccountPromise::CreateAndReject(
+ NS_ERROR_FAILURE, __func__);
+ },
+ [](nsresult error) {
+ return IdentityCredential::GetAccountPromise::CreateAndReject(
+ error, __func__);
+ });
+}
+
+// static
+void IdentityCredential::CloseUserInterface(BrowsingContext* aBrowsingContext) {
+ nsresult error;
+ nsCOMPtr<nsIIdentityCredentialPromptService> icPromptService =
+ mozilla::components::IdentityCredentialPromptService::Service(&error);
+ if (NS_WARN_IF(!icPromptService)) {
+ return;
+ }
+ icPromptService->Close(aBrowsingContext);
+}
+
+// static
+already_AddRefed<Promise> IdentityCredential::LogoutRPs(
+ GlobalObject& aGlobal,
+ const Sequence<IdentityCredentialLogoutRPsRequest>& aLogoutRequests,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ RefPtr<Promise> promise = Promise::CreateResolvedWithUndefined(global, aRv);
+ NS_ENSURE_FALSE(aRv.Failed(), nullptr);
+ nsresult rv;
+ nsCOMPtr<nsIIdentityCredentialStorageService> icStorageService =
+ components::IdentityCredentialStorageService::Service(&rv);
+ if (NS_WARN_IF(!icStorageService)) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+
+ RefPtr<nsIPrincipal> rpPrincipal = global->PrincipalOrNull();
+ for (const auto& request : aLogoutRequests) {
+ // Get the current state
+ nsCOMPtr<nsIURI> idpURI;
+ rv = NS_NewURI(getter_AddRefs(idpURI), request.mUrl);
+ if (NS_FAILED(rv)) {
+ aRv.ThrowTypeError<MSG_INVALID_URL>(request.mUrl);
+ return nullptr;
+ }
+ nsCOMPtr<nsIPrincipal> idpPrincipal = BasePrincipal::CreateContentPrincipal(
+ idpURI, rpPrincipal->OriginAttributesRef());
+ bool registered, allowLogout;
+ icStorageService->GetState(rpPrincipal, idpPrincipal, request.mAccountId,
+ &registered, &allowLogout);
+
+ // Ignore this request if it isn't permitted
+ if (!(registered && allowLogout)) {
+ continue;
+ }
+
+ // Issue the logout request
+ constexpr auto fragment = ""_ns;
+ auto internalRequest =
+ MakeSafeRefPtr<InternalRequest>(request.mUrl, fragment);
+ internalRequest->SetRedirectMode(RequestRedirect::Error);
+ internalRequest->SetCredentialsMode(RequestCredentials::Include);
+ internalRequest->SetReferrerPolicy(ReferrerPolicy::Strict_origin);
+ internalRequest->SetMode(RequestMode::Cors);
+ internalRequest->SetCacheMode(RequestCache::No_cache);
+ internalRequest->OverrideContentPolicyType(
+ nsContentPolicyType::TYPE_WEB_IDENTITY);
+ RefPtr<Request> domRequest =
+ new Request(global, std::move(internalRequest), nullptr);
+ RequestOrUSVString fetchInput;
+ fetchInput.SetAsRequest() = domRequest;
+ RootedDictionary<RequestInit> requestInit(RootingCx());
+ IgnoredErrorResult error;
+ RefPtr<Promise> fetchPromise = FetchRequest(global, fetchInput, requestInit,
+ CallerType::System, error);
+
+ // Change state to disallow more logout requests
+ icStorageService->SetState(rpPrincipal, idpPrincipal, request.mAccountId,
+ true, false);
+ }
+ return promise.forget();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/credentialmanagement/identity/IdentityCredential.h b/dom/credentialmanagement/identity/IdentityCredential.h
new file mode 100644
index 0000000000..d7b4f6defb
--- /dev/null
+++ b/dom/credentialmanagement/identity/IdentityCredential.h
@@ -0,0 +1,189 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_IdentityCredential_h
+#define mozilla_dom_IdentityCredential_h
+
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/Credential.h"
+#include "mozilla/dom/IPCIdentityCredential.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/Tuple.h"
+
+namespace mozilla::dom {
+
+class IdentityCredential final : public Credential {
+ public:
+ typedef MozPromise<RefPtr<IdentityCredential>, nsresult, true>
+ GetIdentityCredentialPromise;
+ typedef MozPromise<IPCIdentityCredential, nsresult, true>
+ GetIPCIdentityCredentialPromise;
+ typedef MozPromise<IdentityProvider, nsresult, true>
+ GetIdentityProviderPromise;
+ typedef MozPromise<bool, nsresult, true> ValidationPromise;
+ typedef MozPromise<IdentityInternalManifest, nsresult, true>
+ GetManifestPromise;
+ typedef MozPromise<Tuple<IdentityInternalManifest, IdentityAccountList>,
+ nsresult, true>
+ GetAccountListPromise;
+ typedef MozPromise<Tuple<IdentityToken, IdentityAccount>, nsresult, true>
+ GetTokenPromise;
+ typedef MozPromise<Tuple<IdentityInternalManifest, IdentityAccount>, nsresult,
+ true>
+ GetAccountPromise;
+ typedef MozPromise<IdentityClientMetadata, nsresult, true> GetMetadataPromise;
+
+ explicit IdentityCredential(nsPIDOMWindowInner* aParent);
+
+ protected:
+ ~IdentityCredential() override;
+
+ public:
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void CopyValuesFrom(const IPCIdentityCredential& aOther);
+
+ IPCIdentityCredential MakeIPCIdentityCredential();
+
+ void GetToken(nsAString& aToken) const;
+ void SetToken(const nsAString& aToken);
+
+ static already_AddRefed<Promise> LogoutRPs(
+ GlobalObject& aGlobal,
+ const Sequence<IdentityCredentialLogoutRPsRequest>& aLogoutRequests,
+ ErrorResult& aRv);
+
+ static RefPtr<GetIdentityCredentialPromise> DiscoverFromExternalSource(
+ nsPIDOMWindowInner* aParent, const CredentialRequestOptions& aOptions,
+ bool aSameOriginWithAncestors);
+
+ static RefPtr<GetIPCIdentityCredentialPromise>
+ DiscoverFromExternalSourceInMainProcess(
+ nsIPrincipal* aPrincipal, CanonicalBrowsingContext* aBrowsingContext,
+ const IdentityCredentialRequestOptions& aOptions);
+
+ // Create an IPC credential that can be passed back to the content process.
+ // This calls a lot of helpers to do the logic of going from a single provider
+ // to a bearer token for an account at that provider.
+ //
+ // Arguments:
+ // aPrincipal: the caller of navigator.credentials.get()'s principal
+ // aProvider: the provider to validate the root manifest of
+ // Return value:
+ // a promise resolving to an IPC credential with type "identity", id
+ // constructed to identify it, and token corresponding to the token
+ // fetched in FetchToken. This promise may reject with nsresult errors.
+ // Side effects:
+ // Will send network requests to the IDP. The details of which are in the
+ // other static methods here.
+ static RefPtr<GetIPCIdentityCredentialPromise> CreateCredential(
+ nsIPrincipal* aPrincipal, BrowsingContext* aBrowsingContext,
+ const IdentityProvider& aProvider);
+
+ // Performs a Fetch for the root manifest of the provided identity provider
+ // and validates it as correct. The returned promise resolves with a bool
+ // that is true if everything is valid.
+ //
+ // Arguments:
+ // aPrincipal: the caller of navigator.credentials.get()'s principal
+ // aProvider: the provider to validate the root manifest of
+ // Return value:
+ // promise that resolves to a bool that indicates success. Will reject
+ // when there are network or other errors.
+ // Side effects:
+ // Network request to the IDP's well-known from inside a NullPrincipal
+ // sandbox
+ //
+ static RefPtr<ValidationPromise> CheckRootManifest(
+ nsIPrincipal* aPrincipal, const IdentityProvider& aProvider);
+
+ // Performs a Fetch for the internal manifest of the provided identity
+ // provider. The returned promise resolves with the manifest retrieved.
+ //
+ // Arguments:
+ // aPrincipal: the caller of navigator.credentials.get()'s principal
+ // aProvider: the provider to fetch the root manifest
+ // Return value:
+ // promise that resolves to the internal manifest. Will reject
+ // when there are network or other errors.
+ // Side effects:
+ // Network request to the URL in aProvider as the manifest from inside a
+ // NullPrincipal sandbox
+ //
+ static RefPtr<GetManifestPromise> FetchInternalManifest(
+ nsIPrincipal* aPrincipal, const IdentityProvider& aProvider);
+
+ // Performs a Fetch for the account list from the provided identity
+ // provider. The returned promise resolves with the manifest and the fetched
+ // account list in a tuple of objects. We put the argument manifest in the
+ // tuple to facilitate clean promise chaining.
+ //
+ // Arguments:
+ // aPrincipal: the caller of navigator.credentials.get()'s principal
+ // aProvider: the provider to get account lists from
+ // aManifest: the provider's internal manifest
+ // Return value:
+ // promise that resolves to a Tuple of the passed manifest and the fetched
+ // account list. Will reject when there are network or other errors.
+ // Side effects:
+ // Network request to the provider supplied account endpoint with
+ // credentials but without any indication of aPrincipal.
+ //
+ static RefPtr<GetAccountListPromise> FetchAccountList(
+ nsIPrincipal* aPrincipal, const IdentityProvider& aProvider,
+ const IdentityInternalManifest& aManifest);
+
+ // Performs a Fetch for a bearer token to the provided identity
+ // provider for a given account. The returned promise resolves with the
+ // account argument and the fetched token in a tuple of objects.
+ // We put the argument account in the
+ // tuple to facilitate clean promise chaining.
+ //
+ // Arguments:
+ // aPrincipal: the caller of navigator.credentials.get()'s principal
+ // aProvider: the provider to get account lists from
+ // aManifest: the provider's internal manifest
+ // aAccount: the account to request
+ // Return value:
+ // promise that resolves to a Tuple of the passed account and the fetched
+ // token. Will reject when there are network or other errors.
+ // Side effects:
+ // Network request to the provider supplied token endpoint with
+ // credentials and including information about the requesting principal.
+ //
+ static RefPtr<GetTokenPromise> FetchToken(
+ nsIPrincipal* aPrincipal, const IdentityProvider& aProvider,
+ const IdentityInternalManifest& aManifest,
+ const IdentityAccount& aAccount);
+
+ static RefPtr<GetMetadataPromise> FetchMetadata(
+ nsIPrincipal* aPrincipal, const IdentityProvider& aProvider,
+ const IdentityInternalManifest& aManifest);
+
+ static RefPtr<GetIdentityProviderPromise> PromptUserToSelectProvider(
+ BrowsingContext* aBrowsingContext,
+ const Sequence<IdentityProvider>& aProviders);
+
+ static RefPtr<GetAccountPromise> PromptUserToSelectAccount(
+ BrowsingContext* aBrowsingContext, const IdentityAccountList& aAccounts,
+ const IdentityInternalManifest& aManifest);
+
+ static RefPtr<GetAccountPromise> PromptUserWithPolicy(
+ BrowsingContext* aBrowsingContext, nsIPrincipal* aPrincipal,
+ const IdentityAccount& aAccount,
+ const IdentityInternalManifest& aManifest,
+ const IdentityProvider& aProvider);
+
+ static void CloseUserInterface(BrowsingContext* aBrowsingContext);
+
+ private:
+ nsAutoString mToken;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_IdentityCredential_h
diff --git a/dom/credentialmanagement/identity/IdentityCredentialSerializationHelpers.h b/dom/credentialmanagement/identity/IdentityCredentialSerializationHelpers.h
new file mode 100644
index 0000000000..f9fae3a758
--- /dev/null
+++ b/dom/credentialmanagement/identity/IdentityCredentialSerializationHelpers.h
@@ -0,0 +1,48 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_identitycredentialserializationhelpers_h__
+#define mozilla_dom_identitycredentialserializationhelpers_h__
+
+#include "mozilla/dom/IdentityCredential.h"
+#include "mozilla/dom/IdentityCredentialBinding.h"
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::dom::IdentityProvider> {
+ typedef mozilla::dom::IdentityProvider paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mConfigURL);
+ WriteParam(aWriter, aParam.mClientId);
+ WriteParam(aWriter, aParam.mNonce);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &aResult->mConfigURL) &&
+ ReadParam(aReader, &aResult->mClientId) &&
+ ReadParam(aReader, &aResult->mNonce);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::dom::IdentityCredentialRequestOptions> {
+ typedef mozilla::dom::IdentityCredentialRequestOptions paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mProviders);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &aResult->mProviders);
+ ;
+ }
+};
+
+} // namespace IPC
+
+#endif // mozilla_dom_identitycredentialserializationhelpers_h__
diff --git a/dom/credentialmanagement/identity/IdentityNetworkHelpers.h b/dom/credentialmanagement/identity/IdentityNetworkHelpers.h
new file mode 100644
index 0000000000..56cffb284f
--- /dev/null
+++ b/dom/credentialmanagement/identity/IdentityNetworkHelpers.h
@@ -0,0 +1,95 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_IdentityNetworkHelpers_h
+#define mozilla_dom_IdentityNetworkHelpers_h
+
+#include "mozilla/dom/Request.h"
+#include "mozilla/dom/Response.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/MozPromise.h"
+
+namespace mozilla::dom {
+
+template <typename T, typename TPromise = MozPromise<T, nsresult, true>>
+RefPtr<TPromise> FetchJSONStructure(Request* aRequest) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // Create the returned Promise
+ RefPtr<typename TPromise::Private> resultPromise =
+ new typename TPromise::Private(__func__);
+
+ // Fetch the provided request
+ RequestOrUSVString fetchInput;
+ fetchInput.SetAsRequest() = aRequest;
+ RootedDictionary<RequestInit> requestInit(RootingCx());
+ IgnoredErrorResult error;
+ RefPtr<Promise> fetchPromise =
+ FetchRequest(aRequest->GetParentObject(), fetchInput, requestInit,
+ CallerType::System, error);
+ if (NS_WARN_IF(error.Failed())) {
+ resultPromise->Reject(NS_ERROR_FAILURE, __func__);
+ return resultPromise;
+ }
+
+ // Handle the response
+ RefPtr<DomPromiseListener> listener = new DomPromiseListener(
+ [resultPromise](JSContext* aCx, JS::Handle<JS::Value> aValue) {
+ // Get the Response object from the argument to the callback
+ if (NS_WARN_IF(!aValue.isObject())) {
+ resultPromise->Reject(NS_ERROR_FAILURE, __func__);
+ return;
+ }
+ JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+ MOZ_ASSERT(obj);
+ Response* response = nullptr;
+ if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Response, &obj, response)))) {
+ resultPromise->Reject(NS_ERROR_FAILURE, __func__);
+ return;
+ }
+
+ // Make sure the request was a success
+ if (!response->Ok()) {
+ resultPromise->Reject(NS_ERROR_FAILURE, __func__);
+ return;
+ }
+
+ // Parse the body into JSON, which must be done async
+ IgnoredErrorResult error;
+ RefPtr<Promise> jsonPromise = response->ConsumeBody(
+ aCx, BodyConsumer::ConsumeType::CONSUME_JSON, error);
+ if (NS_WARN_IF(error.Failed())) {
+ resultPromise->Reject(NS_ERROR_FAILURE, __func__);
+ return;
+ }
+
+ // Handle the parsed JSON from the Response body
+ RefPtr<DomPromiseListener> jsonListener = new DomPromiseListener(
+ [resultPromise](JSContext* aCx, JS::Handle<JS::Value> aValue) {
+ // Parse the JSON into the correct type, validating fields and
+ // types
+ T result;
+ bool success = result.Init(aCx, aValue);
+ if (!success) {
+ resultPromise->Reject(NS_ERROR_FAILURE, __func__);
+ return;
+ }
+
+ resultPromise->Resolve(result, __func__);
+ },
+ [resultPromise](nsresult aRv) {
+ resultPromise->Reject(aRv, __func__);
+ });
+ jsonPromise->AppendNativeHandler(jsonListener);
+ },
+ [resultPromise](nsresult aRv) { resultPromise->Reject(aRv, __func__); });
+ fetchPromise->AppendNativeHandler(listener);
+ return resultPromise;
+}
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_IdentityNetworkHelpers_h
diff --git a/dom/credentialmanagement/identity/moz.build b/dom/credentialmanagement/identity/moz.build
new file mode 100644
index 0000000000..240a138bb6
--- /dev/null
+++ b/dom/credentialmanagement/identity/moz.build
@@ -0,0 +1,27 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "DOM: Credential Management")
+
+EXPORTS.mozilla.dom += [
+ "IdentityCredential.h",
+ "IdentityCredentialSerializationHelpers.h",
+ "IdentityNetworkHelpers.h",
+]
+
+IPDL_SOURCES += [
+ "IPCIdentityCredential.ipdlh",
+]
+
+UNIFIED_SOURCES += ["IdentityCredential.cpp"]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+MOCHITEST_MANIFESTS += ["tests/mochitest/mochitest.ini"]
diff --git a/dom/credentialmanagement/identity/tests/mochitest/head.js b/dom/credentialmanagement/identity/tests/mochitest/head.js
new file mode 100644
index 0000000000..393ba9fa23
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/head.js
@@ -0,0 +1,24 @@
+/* vim: set ts=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/. */
+
+"use strict";
+
+var idp_host = "https://example.net";
+var test_path = "/tests/dom/credentialmanagement/identity/tests/mochitest";
+var idp_api = idp_host + test_path;
+
+async function setupTest(testName) {
+ ok(
+ window.location.pathname.includes(testName),
+ `Must set the right test name when setting up. Test name "${testName}" must be in URL path "${window.location.pathname}"`
+ );
+ let fetchPromise = fetch(
+ `${idp_api}/server_manifest.sjs?set_test=${testName}`
+ );
+ let focusPromise = SimpleTest.promiseFocus();
+ window.open(`${idp_api}/helper_set_cookie.html`, "_blank");
+ await focusPromise;
+ return fetchPromise;
+}
diff --git a/dom/credentialmanagement/identity/tests/mochitest/helper_set_cookie.html b/dom/credentialmanagement/identity/tests/mochitest/helper_set_cookie.html
new file mode 100644
index 0000000000..9f8e410b90
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/helper_set_cookie.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ window.close();
+</script>
+
+See ya! (This window will close itself)
+The cookie was set via HTTP.
diff --git a/dom/credentialmanagement/identity/tests/mochitest/helper_set_cookie.html^headers^ b/dom/credentialmanagement/identity/tests/mochitest/helper_set_cookie.html^headers^
new file mode 100644
index 0000000000..c221facafc
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/helper_set_cookie.html^headers^
@@ -0,0 +1 @@
+Set-Cookie: credential=authcookieval; SameSite=None; Secure; Path=/
diff --git a/dom/credentialmanagement/identity/tests/mochitest/mochitest.ini b/dom/credentialmanagement/identity/tests/mochitest/mochitest.ini
new file mode 100644
index 0000000000..4106cfb55b
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/mochitest.ini
@@ -0,0 +1,48 @@
+[DEFAULT]
+prefs =
+ dom.security.credentialmanagement.identity.enabled=true
+ dom.security.credentialmanagement.identity.select_first_in_ui_lists=true
+ dom.security.credentialmanagement.identity.reject_delay.enabled=false
+ privacy.antitracking.enableWebcompat=false # disables opener heuristic
+scheme = https
+skip-if = xorigin
+
+support-files =
+ head.js
+ helper_set_cookie.html
+ helper_set_cookie.html^headers^
+ /.well-known/web-identity
+ /.well-known/web-identity^headers^
+ server_manifest.sjs
+ server_manifest_wrong_provider_in_manifest.sjs
+ server_metadata.json
+ server_metadata.json^headers^
+ server_simple_accounts.sjs
+ server_simple_idtoken.sjs
+ server_no_accounts_accounts.sjs
+ server_no_accounts_idtoken.sjs
+ server_two_accounts_accounts.sjs
+ server_two_accounts_idtoken.sjs
+ server_two_providers_accounts.sjs
+ server_two_providers_idtoken.sjs
+ server_accounts_error_accounts.sjs
+ server_accounts_error_idtoken.sjs
+ server_idtoken_error_accounts.sjs
+ server_idtoken_error_idtoken.sjs
+ server_accounts_redirect_accounts.sjs
+ server_accounts_redirect_idtoken.sjs
+ server_idtoken_redirect_accounts.sjs
+ server_idtoken_redirect_idtoken.sjs
+
+[test_simple.html]
+[test_no_accounts.html]
+[test_two_accounts.html]
+[test_accounts_error.html]
+[test_idtoken_error.html]
+[test_accounts_redirect.html]
+[test_idtoken_redirect.html]
+[test_wrong_provider_in_manifest.html]
+[test_get_without_providers.html]
+[test_empty_provider_list.html]
+[test_two_providers.html]
+[test_delay_reject.html]
diff --git a/dom/credentialmanagement/identity/tests/mochitest/server_accounts_error_accounts.sjs b/dom/credentialmanagement/identity/tests/mochitest/server_accounts_error_accounts.sjs
new file mode 100644
index 0000000000..d0a11ce469
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/server_accounts_error_accounts.sjs
@@ -0,0 +1,9 @@
+/* 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/. */
+
+function handleRequest(request, response) {
+ response.setHeader("Access-Control-Allow-Origin", "*");
+ response.setHeader("Access-Control-Allow-Credentials", "true");
+ response.setStatusLine(request.httpVersion, 503, "Service Unavailable");
+}
diff --git a/dom/credentialmanagement/identity/tests/mochitest/server_accounts_error_idtoken.sjs b/dom/credentialmanagement/identity/tests/mochitest/server_accounts_error_idtoken.sjs
new file mode 100644
index 0000000000..a6f6f7c4b1
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/server_accounts_error_idtoken.sjs
@@ -0,0 +1,15 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function handleRequest(request, response) {
+ response.setHeader("Access-Control-Allow-Origin", "https://example.com");
+ response.setHeader("Access-Control-Allow-Credentials", "true");
+ response.setHeader("Content-Type", "application/json");
+ let responseContent = {
+ token: "should not be returned",
+ };
+ let body = JSON.stringify(responseContent);
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.write(body);
+}
diff --git a/dom/credentialmanagement/identity/tests/mochitest/server_accounts_redirect_accounts.sjs b/dom/credentialmanagement/identity/tests/mochitest/server_accounts_redirect_accounts.sjs
new file mode 100644
index 0000000000..f33da643a0
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/server_accounts_redirect_accounts.sjs
@@ -0,0 +1,10 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function handleRequest(request, response) {
+ response.setHeader("Access-Control-Allow-Origin", "*");
+ response.setHeader("Access-Control-Allow-Credentials", "true");
+ response.setHeader("Location", "server_simple_accounts.sjs");
+ response.setStatusLine(request.httpVersion, 302, "Found");
+}
diff --git a/dom/credentialmanagement/identity/tests/mochitest/server_accounts_redirect_idtoken.sjs b/dom/credentialmanagement/identity/tests/mochitest/server_accounts_redirect_idtoken.sjs
new file mode 100644
index 0000000000..a6f6f7c4b1
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/server_accounts_redirect_idtoken.sjs
@@ -0,0 +1,15 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function handleRequest(request, response) {
+ response.setHeader("Access-Control-Allow-Origin", "https://example.com");
+ response.setHeader("Access-Control-Allow-Credentials", "true");
+ response.setHeader("Content-Type", "application/json");
+ let responseContent = {
+ token: "should not be returned",
+ };
+ let body = JSON.stringify(responseContent);
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.write(body);
+}
diff --git a/dom/credentialmanagement/identity/tests/mochitest/server_idtoken_error_accounts.sjs b/dom/credentialmanagement/identity/tests/mochitest/server_idtoken_error_accounts.sjs
new file mode 100644
index 0000000000..9baeb51ba5
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/server_idtoken_error_accounts.sjs
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function handleRequest(request, response) {
+ response.setHeader("Access-Control-Allow-Origin", "*");
+ response.setHeader("Access-Control-Allow-Credentials", "true");
+ response.setHeader("Content-Type", "application/json");
+ let content = {
+ accounts: [
+ {
+ id: "1234",
+ given_name: "John",
+ name: "John Doe",
+ email: "john_doe@idp.example",
+ picture: "https://idp.example/profile/123",
+ approved_clients: ["123", "456", "789"],
+ },
+ ],
+ };
+ let body = JSON.stringify(content);
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.write(body);
+}
diff --git a/dom/credentialmanagement/identity/tests/mochitest/server_idtoken_error_idtoken.sjs b/dom/credentialmanagement/identity/tests/mochitest/server_idtoken_error_idtoken.sjs
new file mode 100644
index 0000000000..653207672b
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/server_idtoken_error_idtoken.sjs
@@ -0,0 +1,9 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function handleRequest(request, response) {
+ response.setHeader("Access-Control-Allow-Origin", "https://example.com");
+ response.setHeader("Access-Control-Allow-Credentials", "true");
+ response.setStatusLine(request.httpVersion, 503, "Service Unavailable");
+}
diff --git a/dom/credentialmanagement/identity/tests/mochitest/server_idtoken_redirect_accounts.sjs b/dom/credentialmanagement/identity/tests/mochitest/server_idtoken_redirect_accounts.sjs
new file mode 100644
index 0000000000..9baeb51ba5
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/server_idtoken_redirect_accounts.sjs
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function handleRequest(request, response) {
+ response.setHeader("Access-Control-Allow-Origin", "*");
+ response.setHeader("Access-Control-Allow-Credentials", "true");
+ response.setHeader("Content-Type", "application/json");
+ let content = {
+ accounts: [
+ {
+ id: "1234",
+ given_name: "John",
+ name: "John Doe",
+ email: "john_doe@idp.example",
+ picture: "https://idp.example/profile/123",
+ approved_clients: ["123", "456", "789"],
+ },
+ ],
+ };
+ let body = JSON.stringify(content);
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.write(body);
+}
diff --git a/dom/credentialmanagement/identity/tests/mochitest/server_idtoken_redirect_idtoken.sjs b/dom/credentialmanagement/identity/tests/mochitest/server_idtoken_redirect_idtoken.sjs
new file mode 100644
index 0000000000..66456eb687
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/server_idtoken_redirect_idtoken.sjs
@@ -0,0 +1,10 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function handleRequest(request, response) {
+ response.setHeader("Access-Control-Allow-Origin", "https://example.com");
+ response.setHeader("Access-Control-Allow-Credentials", "true");
+ response.setHeader("Location", "server_simple_idtoken.sjs");
+ response.setStatusLine(request.httpVersion, 302, "Found");
+}
diff --git a/dom/credentialmanagement/identity/tests/mochitest/server_manifest.sjs b/dom/credentialmanagement/identity/tests/mochitest/server_manifest.sjs
new file mode 100644
index 0000000000..afd575e21a
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/server_manifest.sjs
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+Cu.importGlobalProperties(["URLSearchParams"]);
+
+function handleRequest(request, response) {
+ let params = new URLSearchParams(request.queryString);
+ let test = params.get("set_test");
+ if (test === null) {
+ test = getState("test");
+ } else {
+ setState("test", test);
+ response.setHeader("Access-Control-Allow-Origin", "*");
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ return;
+ }
+
+ if (request.hasHeader("Cookie")) {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+ if (request.hasHeader("Origin") && request.getHeader("Origin") != "null") {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+ if (request.hasHeader("Referer")) {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+
+ response.setHeader("Access-Control-Allow-Origin", "*");
+ response.setHeader("Content-Type", "application/json");
+ let content = {
+ accounts_endpoint:
+ "https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/server_TESTNAME_accounts.sjs",
+ client_metadata_endpoint:
+ "https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/server_metadata.json",
+ id_token_endpoint:
+ "https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/server_TESTNAME_idtoken.sjs",
+ };
+ let bodyFormat = JSON.stringify(content);
+ let body = bodyFormat.replaceAll("TESTNAME", test);
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.write(body);
+}
diff --git a/dom/credentialmanagement/identity/tests/mochitest/server_manifest_wrong_provider_in_manifest.sjs b/dom/credentialmanagement/identity/tests/mochitest/server_manifest_wrong_provider_in_manifest.sjs
new file mode 100644
index 0000000000..ec8730ee13
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/server_manifest_wrong_provider_in_manifest.sjs
@@ -0,0 +1,19 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function handleRequest(request, response) {
+ response.setHeader("Access-Control-Allow-Origin", "*");
+ response.setHeader("Content-Type", "application/json");
+ let content = {
+ accounts_endpoint:
+ "https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/server_simple_accounts.sjs",
+ client_metadata_endpoint:
+ "https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/server_simple_metadata.sjs",
+ id_token_endpoint:
+ "https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/server_simple_idtoken.sjs",
+ };
+ let body = JSON.stringify(content);
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.write(body);
+}
diff --git a/dom/credentialmanagement/identity/tests/mochitest/server_metadata.json b/dom/credentialmanagement/identity/tests/mochitest/server_metadata.json
new file mode 100644
index 0000000000..6616407523
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/server_metadata.json
@@ -0,0 +1,4 @@
+{
+ "privacy_policy_url" : "https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/null.txt",
+ "terms_of_service_url" : "https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/null.txt"
+}
diff --git a/dom/credentialmanagement/identity/tests/mochitest/server_metadata.json^headers^ b/dom/credentialmanagement/identity/tests/mochitest/server_metadata.json^headers^
new file mode 100644
index 0000000000..75875a7cf3
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/server_metadata.json^headers^
@@ -0,0 +1,2 @@
+Content-Type: application/json
+Access-Control-Allow-Origin: *
diff --git a/dom/credentialmanagement/identity/tests/mochitest/server_no_accounts_accounts.sjs b/dom/credentialmanagement/identity/tests/mochitest/server_no_accounts_accounts.sjs
new file mode 100644
index 0000000000..dac20e4466
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/server_no_accounts_accounts.sjs
@@ -0,0 +1,38 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function handleRequest(request, response) {
+ if (
+ !request.hasHeader("Cookie") ||
+ request.getHeader("Cookie") != "credential=authcookieval"
+ ) {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+ if (
+ !request.hasHeader("Sec-Fetch-Dest") ||
+ request.getHeader("Sec-Fetch-Dest") != "webidentity"
+ ) {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+ if (request.hasHeader("Origin") && request.getHeader("Origin") != "null") {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+ if (request.hasHeader("Referer")) {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+
+ response.setHeader("Access-Control-Allow-Origin", "*");
+ response.setHeader("Access-Control-Allow-Credentials", "true");
+ response.setHeader("Content-Type", "application/json");
+ let content = {
+ accounts: [],
+ };
+ let body = JSON.stringify(content);
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.write(body);
+}
diff --git a/dom/credentialmanagement/identity/tests/mochitest/server_no_accounts_idtoken.sjs b/dom/credentialmanagement/identity/tests/mochitest/server_no_accounts_idtoken.sjs
new file mode 100644
index 0000000000..a3ca4ce31d
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/server_no_accounts_idtoken.sjs
@@ -0,0 +1,66 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const BinaryInputStream = Components.Constructor(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+
+function readStream(inputStream) {
+ let available = 0;
+ let result = [];
+ while ((available = inputStream.available()) > 0) {
+ result.push(inputStream.readBytes(available));
+ }
+ return result.join("");
+}
+
+function handleRequest(request, response) {
+ if (request.method != "POST") {
+ response.setStatusLine(request.httpVersion, 405, "Method Not Allowed");
+ return;
+ }
+ if (
+ !request.hasHeader("Cookie") ||
+ request.getHeader("Cookie") != "credential=authcookieval"
+ ) {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+ if (
+ !request.hasHeader("Sec-Fetch-Dest") ||
+ request.getHeader("Sec-Fetch-Dest") != "webidentity"
+ ) {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+ if (
+ !request.hasHeader("Referer") ||
+ request.getHeader("Referer") != "https://example.com/"
+ ) {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+ if (
+ !request.hasHeader("Origin") ||
+ request.getHeader("Origin") != "https://example.com"
+ ) {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+
+ response.setHeader("Access-Control-Allow-Origin", "https://example.com");
+ response.setHeader("Access-Control-Allow-Credentials", "true");
+ response.setHeader("Content-Type", "application/json");
+ let requestContent = readStream(
+ new BinaryInputStream(request.bodyInputStream)
+ );
+ let responseContent = {
+ token: requestContent,
+ };
+ let body = JSON.stringify(responseContent);
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.write(body);
+}
diff --git a/dom/credentialmanagement/identity/tests/mochitest/server_simple_accounts.sjs b/dom/credentialmanagement/identity/tests/mochitest/server_simple_accounts.sjs
new file mode 100644
index 0000000000..6ebce36802
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/server_simple_accounts.sjs
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function handleRequest(request, response) {
+ if (
+ !request.hasHeader("Cookie") ||
+ request.getHeader("Cookie") != "credential=authcookieval"
+ ) {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+ if (
+ !request.hasHeader("Sec-Fetch-Dest") ||
+ request.getHeader("Sec-Fetch-Dest") != "webidentity"
+ ) {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+ if (request.hasHeader("Origin") && request.getHeader("Origin") != "null") {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+ if (request.hasHeader("Referer")) {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+
+ response.setHeader("Access-Control-Allow-Origin", "*");
+ response.setHeader("Access-Control-Allow-Credentials", "true");
+ response.setHeader("Content-Type", "application/json");
+ let content = {
+ accounts: [
+ {
+ id: "1234",
+ given_name: "John",
+ name: "John Doe",
+ email: "john_doe@idp.example",
+ picture: "https://idp.example/profile/123",
+ approved_clients: ["123", "456", "789"],
+ },
+ ],
+ };
+ let body = JSON.stringify(content);
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.write(body);
+}
diff --git a/dom/credentialmanagement/identity/tests/mochitest/server_simple_idtoken.sjs b/dom/credentialmanagement/identity/tests/mochitest/server_simple_idtoken.sjs
new file mode 100644
index 0000000000..a3ca4ce31d
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/server_simple_idtoken.sjs
@@ -0,0 +1,66 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const BinaryInputStream = Components.Constructor(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+
+function readStream(inputStream) {
+ let available = 0;
+ let result = [];
+ while ((available = inputStream.available()) > 0) {
+ result.push(inputStream.readBytes(available));
+ }
+ return result.join("");
+}
+
+function handleRequest(request, response) {
+ if (request.method != "POST") {
+ response.setStatusLine(request.httpVersion, 405, "Method Not Allowed");
+ return;
+ }
+ if (
+ !request.hasHeader("Cookie") ||
+ request.getHeader("Cookie") != "credential=authcookieval"
+ ) {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+ if (
+ !request.hasHeader("Sec-Fetch-Dest") ||
+ request.getHeader("Sec-Fetch-Dest") != "webidentity"
+ ) {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+ if (
+ !request.hasHeader("Referer") ||
+ request.getHeader("Referer") != "https://example.com/"
+ ) {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+ if (
+ !request.hasHeader("Origin") ||
+ request.getHeader("Origin") != "https://example.com"
+ ) {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+
+ response.setHeader("Access-Control-Allow-Origin", "https://example.com");
+ response.setHeader("Access-Control-Allow-Credentials", "true");
+ response.setHeader("Content-Type", "application/json");
+ let requestContent = readStream(
+ new BinaryInputStream(request.bodyInputStream)
+ );
+ let responseContent = {
+ token: requestContent,
+ };
+ let body = JSON.stringify(responseContent);
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.write(body);
+}
diff --git a/dom/credentialmanagement/identity/tests/mochitest/server_two_accounts_accounts.sjs b/dom/credentialmanagement/identity/tests/mochitest/server_two_accounts_accounts.sjs
new file mode 100644
index 0000000000..f9d60183a1
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/server_two_accounts_accounts.sjs
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function handleRequest(request, response) {
+ if (
+ !request.hasHeader("Cookie") ||
+ request.getHeader("Cookie") != "credential=authcookieval"
+ ) {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+ if (
+ !request.hasHeader("Sec-Fetch-Dest") ||
+ request.getHeader("Sec-Fetch-Dest") != "webidentity"
+ ) {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+ if (request.hasHeader("Origin") && request.getHeader("Origin") != "null") {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+ if (request.hasHeader("Referer")) {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+
+ response.setHeader("Access-Control-Allow-Origin", "*");
+ response.setHeader("Access-Control-Allow-Credentials", "true");
+ response.setHeader("Content-Type", "application/json");
+ let content = {
+ accounts: [
+ {
+ id: "1234",
+ given_name: "John",
+ name: "John Doe",
+ email: "john_doe@idp.example",
+ picture: "https://idp.example/profile/123",
+ approved_clients: ["123", "456", "789"],
+ },
+ {
+ id: "5678",
+ given_name: "Johnny",
+ name: "Johnny",
+ email: "johnny@idp.example",
+ picture: "https://idp.example/profile/456",
+ approved_clients: ["abc", "def", "ghi"],
+ },
+ ],
+ };
+ let body = JSON.stringify(content);
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.write(body);
+}
diff --git a/dom/credentialmanagement/identity/tests/mochitest/server_two_accounts_idtoken.sjs b/dom/credentialmanagement/identity/tests/mochitest/server_two_accounts_idtoken.sjs
new file mode 100644
index 0000000000..a3ca4ce31d
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/server_two_accounts_idtoken.sjs
@@ -0,0 +1,66 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const BinaryInputStream = Components.Constructor(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+
+function readStream(inputStream) {
+ let available = 0;
+ let result = [];
+ while ((available = inputStream.available()) > 0) {
+ result.push(inputStream.readBytes(available));
+ }
+ return result.join("");
+}
+
+function handleRequest(request, response) {
+ if (request.method != "POST") {
+ response.setStatusLine(request.httpVersion, 405, "Method Not Allowed");
+ return;
+ }
+ if (
+ !request.hasHeader("Cookie") ||
+ request.getHeader("Cookie") != "credential=authcookieval"
+ ) {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+ if (
+ !request.hasHeader("Sec-Fetch-Dest") ||
+ request.getHeader("Sec-Fetch-Dest") != "webidentity"
+ ) {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+ if (
+ !request.hasHeader("Referer") ||
+ request.getHeader("Referer") != "https://example.com/"
+ ) {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+ if (
+ !request.hasHeader("Origin") ||
+ request.getHeader("Origin") != "https://example.com"
+ ) {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+
+ response.setHeader("Access-Control-Allow-Origin", "https://example.com");
+ response.setHeader("Access-Control-Allow-Credentials", "true");
+ response.setHeader("Content-Type", "application/json");
+ let requestContent = readStream(
+ new BinaryInputStream(request.bodyInputStream)
+ );
+ let responseContent = {
+ token: requestContent,
+ };
+ let body = JSON.stringify(responseContent);
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.write(body);
+}
diff --git a/dom/credentialmanagement/identity/tests/mochitest/server_two_providers_accounts.sjs b/dom/credentialmanagement/identity/tests/mochitest/server_two_providers_accounts.sjs
new file mode 100644
index 0000000000..25060b850e
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/server_two_providers_accounts.sjs
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function handleRequest(request, response) {
+ if (
+ !request.hasHeader("Cookie") ||
+ request.getHeader("Cookie") != "credential=authcookieval"
+ ) {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+ if (request.hasHeader("Origin") && request.getHeader("Origin") != "null") {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+ if (request.hasHeader("Referer")) {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+
+ response.setHeader("Access-Control-Allow-Origin", "*");
+ response.setHeader("Access-Control-Allow-Credentials", "true");
+ response.setHeader("Content-Type", "application/json");
+ let content = {
+ accounts: [
+ {
+ id: "1234",
+ given_name: "John",
+ name: "John Doe",
+ email: "john_doe@idp.example",
+ picture: "https://idp.example/profile/123",
+ approved_clients: ["123", "456", "789"],
+ },
+ {
+ id: "5678",
+ given_name: "Johnny",
+ name: "Johnny",
+ email: "johnny@idp.example",
+ picture: "https://idp.example/profile/456",
+ approved_clients: ["abc", "def", "ghi"],
+ },
+ ],
+ };
+ let body = JSON.stringify(content);
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.write(body);
+}
diff --git a/dom/credentialmanagement/identity/tests/mochitest/server_two_providers_idtoken.sjs b/dom/credentialmanagement/identity/tests/mochitest/server_two_providers_idtoken.sjs
new file mode 100644
index 0000000000..01b61ff33d
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/server_two_providers_idtoken.sjs
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const BinaryInputStream = Components.Constructor(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+
+function readStream(inputStream) {
+ let available = 0;
+ let result = [];
+ while ((available = inputStream.available()) > 0) {
+ result.push(inputStream.readBytes(available));
+ }
+ return result.join("");
+}
+
+function handleRequest(request, response) {
+ if (request.method != "POST") {
+ response.setStatusLine(request.httpVersion, 405, "Method Not Allowed");
+ return;
+ }
+ if (
+ !request.hasHeader("Cookie") ||
+ request.getHeader("Cookie") != "credential=authcookieval"
+ ) {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+ if (
+ !request.hasHeader("Referer") ||
+ request.getHeader("Referer") != "https://example.com/"
+ ) {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+ if (
+ !request.hasHeader("Origin") ||
+ request.getHeader("Origin") != "https://example.com"
+ ) {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ return;
+ }
+
+ response.setHeader("Access-Control-Allow-Origin", "https://example.com");
+ response.setHeader("Access-Control-Allow-Credentials", "true");
+ response.setHeader("Content-Type", "application/json");
+ let requestContent = readStream(
+ new BinaryInputStream(request.bodyInputStream)
+ );
+ let responseContent = {
+ token: requestContent,
+ };
+ let body = JSON.stringify(responseContent);
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.write(body);
+}
diff --git a/dom/credentialmanagement/identity/tests/mochitest/test_accounts_error.html b/dom/credentialmanagement/identity/tests/mochitest/test_accounts_error.html
new file mode 100644
index 0000000000..ca0f85b110
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/test_accounts_error.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Server Error On Accounts Endpoint</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="head.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ setupTest("accounts_error").then(
+ function () {
+ return navigator.credentials.get({
+ identity: {
+ providers: [{
+ configURL: "https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/server_manifest.sjs",
+ clientId: "mochitest",
+ nonce: "nonce"
+ }]
+ }
+ });
+ }
+ ).then((cred) => {
+ ok(false, "incorrectly got a credential");
+ }).catch((err) => {
+ ok(true, "correctly got an error");
+ }).finally(() => {
+ SimpleTest.finish();
+ })
+ </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">This test verifies that we do not get a credential when the accounts endpoint returns an error.</div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/dom/credentialmanagement/identity/tests/mochitest/test_accounts_redirect.html b/dom/credentialmanagement/identity/tests/mochitest/test_accounts_redirect.html
new file mode 100644
index 0000000000..99b897d35e
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/test_accounts_redirect.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Server Redirect On Accounts Endpoint</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="head.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ setupTest("accounts_redirect").then(
+ function () {
+ return navigator.credentials.get({
+ identity: {
+ providers: [{
+ configURL: "https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/server_manifest.sjs",
+ clientId: "mochitest",
+ nonce: "nonce"
+ }]
+ }
+ });
+ }
+ ).then((cred) => {
+ ok(false, "incorrectly got a credential");
+ }).catch((err) => {
+ ok(true, "correctly got an error");
+ }).finally(() => {
+ SimpleTest.finish();
+ })
+ </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">This test verifies that we do not get a credential when the accounts endpoint redirects to another (entirely functional) accounts endpoint.</div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/dom/credentialmanagement/identity/tests/mochitest/test_delay_reject.html b/dom/credentialmanagement/identity/tests/mochitest/test_delay_reject.html
new file mode 100644
index 0000000000..0151f4b6c4
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/test_delay_reject.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Delay Reject</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="head.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv({ set: [
+ ["dom.security.credentialmanagement.identity.reject_delay.enabled", "true" ],
+ ["dom.security.credentialmanagement.identity.reject_delay.duration_ms", "1000" ],
+ ] })
+ .then(() => {setupTest("delay_reject")})
+ .then(
+ function () {
+ return navigator.credentials.get({
+ identity: {
+ providers: []
+ }
+ });
+ }
+ ).then((cred) => {
+ ok(false, "incorrectly got a credential");
+ }).catch((err) => {
+ ok(true, "correctly got an error");
+ }).finally(() => {
+ SimpleTest.finish();
+ })
+ </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">This test verifies that our rejections are delayed, checking for >500ms.</div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/dom/credentialmanagement/identity/tests/mochitest/test_empty_provider_list.html b/dom/credentialmanagement/identity/tests/mochitest/test_empty_provider_list.html
new file mode 100644
index 0000000000..ad5b6ea28c
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/test_empty_provider_list.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Empty Provider List</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="head.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ setupTest("empty_provider_list")
+ .then(
+ function () {
+ return navigator.credentials.get({
+ identity: {
+ providers: []
+ }
+ });
+ }
+ ).then((cred) => {
+ ok(false, "incorrectly got a credential");
+ }).catch((err) => {
+ ok(true, "correctly got an error");
+ }).finally(() => {
+ SimpleTest.finish();
+ })
+ </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">This test verifies that we do not get a credential when we give no providers to support.</div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/dom/credentialmanagement/identity/tests/mochitest/test_get_without_providers.html b/dom/credentialmanagement/identity/tests/mochitest/test_get_without_providers.html
new file mode 100644
index 0000000000..4425abf5aa
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/test_get_without_providers.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>No Providers Specified</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="head.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ setupTest("get_without_providers").then(
+ function () {
+ return navigator.credentials.get({
+ identity: {
+ }
+ });
+ }
+ ).then((cred) => {
+ ok(false, "incorrectly got a credential");
+ }).catch((err) => {
+ ok(true, "correctly got an error");
+ }).finally(() => {
+ SimpleTest.finish();
+ })
+ </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">This test verifies that we do not get a credential when we give no providers field in the JSON. This is mostly to make sure we don't have any nullptr derefs.</div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/dom/credentialmanagement/identity/tests/mochitest/test_idtoken_error.html b/dom/credentialmanagement/identity/tests/mochitest/test_idtoken_error.html
new file mode 100644
index 0000000000..ddc6716081
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/test_idtoken_error.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Server Error On Token Endpoint</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="head.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ setupTest("idtoken_error").then(
+ function () {
+ return navigator.credentials.get({
+ identity: {
+ providers: [{
+ configURL: "https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/server_manifest.sjs",
+ clientId: "mochitest",
+ nonce: "nonce"
+ }]
+ }
+ });
+ }
+ ).then((cred) => {
+ ok(false, "incorrectly got a credential");
+ }).catch((err) => {
+ ok(true, "correctly got an error");
+ }).finally(() => {
+ SimpleTest.finish();
+ })
+ </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">This test verifies that we do not get a credential when the idtoken endpoint returns an error.</div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/dom/credentialmanagement/identity/tests/mochitest/test_idtoken_redirect.html b/dom/credentialmanagement/identity/tests/mochitest/test_idtoken_redirect.html
new file mode 100644
index 0000000000..88512a1d22
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/test_idtoken_redirect.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Server Redirect On Token Endpoint</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="head.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ setupTest("idtoken_redirect").then(
+ function () {
+ return navigator.credentials.get({
+ identity: {
+ providers: [{
+ configURL: "https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/server_manifest.sjs",
+ clientId: "mochitest",
+ nonce: "nonce"
+ }]
+ }
+ });
+ }
+ ).then((cred) => {
+ ok(false, "incorrectly got a credential");
+ }).catch((err) => {
+ ok(true, "correctly got an error");
+ }).finally(() => {
+ SimpleTest.finish();
+ })
+ </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">This test verifies that we do not get a credential when the idtoken endpoint redirects to another (entirely functional) idtoken endpoint.</div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/dom/credentialmanagement/identity/tests/mochitest/test_no_accounts.html b/dom/credentialmanagement/identity/tests/mochitest/test_no_accounts.html
new file mode 100644
index 0000000000..90c3335eda
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/test_no_accounts.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>No Accounts in the List</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="head.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ setupTest("no_accounts").then(
+ function () {
+ return navigator.credentials.get({
+ identity: {
+ providers: [{
+ configURL: "https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/server_manifest.sjs",
+ clientId: "mochitest",
+ nonce: "nonce"
+ }]
+ }
+ });
+ }
+ ).then((cred) => {
+ ok(false, "incorrectly got a credential");
+ }).catch((err) => {
+ ok(true, "correctly got an error");
+ }).finally(() => {
+ SimpleTest.finish();
+ })
+ </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">This test validates that if a provider returns no accounts, we throw an error from our credential request.</div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/dom/credentialmanagement/identity/tests/mochitest/test_simple.html b/dom/credentialmanagement/identity/tests/mochitest/test_simple.html
new file mode 100644
index 0000000000..39d34f3d5f
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/test_simple.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Happypath Test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="head.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ setupTest("simple").then(
+ function () {
+ return navigator.credentials.get({
+ identity: {
+ providers: [{
+ configURL: "https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/server_manifest.sjs",
+ clientId: "mochitest",
+ nonce: "nonce"
+ }]
+ }
+ });
+ }
+ ).then((cred) => {
+ ok(true, "successfully got a credential");
+ is(cred.token,
+ "account_id=1234&client_id=mochitest&nonce=nonce&disclosure_text_shown=false",
+ "Correct token on the credential.");
+ is(cred.id,
+ "1234",
+ "Correct id on the credential");
+ is(cred.type,
+ "identity",
+ "Correct type on the credential");
+ }).catch((err) => {
+ ok(false, "must not have an error");
+ }).finally(() => {
+ SimpleTest.finish();
+ })
+ </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">This is the main happypath test. We get a credential in a way that should work. This includes simplifying some logic like exactly one account and provider.</div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/dom/credentialmanagement/identity/tests/mochitest/test_two_accounts.html b/dom/credentialmanagement/identity/tests/mochitest/test_two_accounts.html
new file mode 100644
index 0000000000..36e99adf75
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/test_two_accounts.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Two Accounts in the List</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="head.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ setupTest("two_accounts").then(
+ function () {
+ return navigator.credentials.get({
+ identity: {
+ providers: [{
+ configURL: "https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/server_manifest.sjs",
+ clientId: "mochitest",
+ nonce: "nonce"
+ }]
+ }
+ });
+ }
+ ).then((cred) => {
+ ok(true, "successfully got a credential");
+ is(cred.token,
+ "account_id=1234&client_id=mochitest&nonce=nonce&disclosure_text_shown=false",
+ "Correct token on the credential.");
+ is(cred.id,
+ "1234",
+ "Correct id on the credential");
+ is(cred.type,
+ "identity",
+ "Correct type on the credential");
+ }).catch((err) => {
+ ok(false, "must not have an error");
+ }).finally(() => {
+ SimpleTest.finish();
+ })
+ </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">This test is temporary until we have an account chooser. It verifies that when we get more than one account from the IDP we just pick the first one.</div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/dom/credentialmanagement/identity/tests/mochitest/test_two_providers.html b/dom/credentialmanagement/identity/tests/mochitest/test_two_providers.html
new file mode 100644
index 0000000000..5533f71064
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/test_two_providers.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Two Providers in a Credential.get()</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="head.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ setupTest("two_providers").then(
+ function () {
+ return navigator.credentials.get({
+ identity: {
+ providers: [{
+ configURL: "https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/server_manifest.sjs",
+ clientId: "mochitest",
+ nonce: "nonce"
+ },
+ {
+ configURL: "https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/server_manifest.sjs",
+ clientId: "mochitest",
+ nonce: "nonce2"
+ }
+ ]
+ }
+ });
+ }
+ ).then((cred) => {
+ ok(true, "successfully got a credential");
+ is(cred.token,
+ "account_id=1234&client_id=mochitest&nonce=nonce&disclosure_text_shown=false",
+ "Correct token on the credential.");
+ is(cred.id,
+ "1234",
+ "Correct id on the credential");
+ is(cred.type,
+ "identity",
+ "Correct type on the credential");
+ }).catch((err) => {
+ ok(false, "must not have an error");
+ }).finally(() => {
+ SimpleTest.finish();
+ })
+ </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/dom/credentialmanagement/identity/tests/mochitest/test_wrong_provider_in_manifest.html b/dom/credentialmanagement/identity/tests/mochitest/test_wrong_provider_in_manifest.html
new file mode 100644
index 0000000000..8ff1afe04d
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/test_wrong_provider_in_manifest.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Manifest Disagreement</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="head.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ setupTest("wrong_provider_in_manifest").then(
+ function () {
+ return navigator.credentials.get({
+ identity: {
+ providers: [{
+ configURL: "https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/server_manifest_wrong_provider_in_manifest.sjs",
+ clientId: "mochitest",
+ nonce: "nonce"
+ }]
+ }
+ });
+ }
+ ).then((cred) => {
+ ok(false, "incorrectly got a credential");
+ }).catch((err) => {
+ ok(true, "correctly got an error");
+ }).finally(() => {
+ SimpleTest.finish();
+ })
+ </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">This test is an important privacy check. We make sure the manifest from the argument to credentials.get matches the IDP's root manifest.</div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/dom/credentialmanagement/identity/tests/mochitest/web-identity b/dom/credentialmanagement/identity/tests/mochitest/web-identity
new file mode 100644
index 0000000000..33dc9c455b
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/web-identity
@@ -0,0 +1 @@
+{"provider_urls": ["https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/server_manifest.sjs"]}
diff --git a/dom/credentialmanagement/identity/tests/mochitest/web-identity^headers^ b/dom/credentialmanagement/identity/tests/mochitest/web-identity^headers^
new file mode 100644
index 0000000000..75875a7cf3
--- /dev/null
+++ b/dom/credentialmanagement/identity/tests/mochitest/web-identity^headers^
@@ -0,0 +1,2 @@
+Content-Type: application/json
+Access-Control-Allow-Origin: *