Bug 1892041 - Part 1: Update test262 features. r=spidermonkey-reviewers,dminor
[gecko.git] / dom / credentialmanagement / CredentialsContainer.cpp
blob9a39d66527934f8c7b7773f9bb0f16a2605de9bb
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/Credential.h"
8 #include "mozilla/dom/CredentialsContainer.h"
9 #include "mozilla/dom/FeaturePolicyUtils.h"
10 #include "mozilla/dom/IdentityCredential.h"
11 #include "mozilla/dom/Promise.h"
12 #include "mozilla/StaticPrefs_dom.h"
13 #include "mozilla/StaticPrefs_security.h"
14 #include "mozilla/dom/WebAuthnManager.h"
15 #include "mozilla/dom/WindowGlobalChild.h"
16 #include "mozilla/dom/WindowContext.h"
17 #include "nsContentUtils.h"
18 #include "nsFocusManager.h"
19 #include "nsIDocShell.h"
21 namespace mozilla::dom {
23 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CredentialsContainer, mParent, mManager)
24 NS_IMPL_CYCLE_COLLECTING_ADDREF(CredentialsContainer)
25 NS_IMPL_CYCLE_COLLECTING_RELEASE(CredentialsContainer)
26 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CredentialsContainer)
27 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
28 NS_INTERFACE_MAP_ENTRY(nsISupports)
29 NS_INTERFACE_MAP_END
31 already_AddRefed<Promise> CreatePromise(nsPIDOMWindowInner* aParent,
32 ErrorResult& aRv) {
33 MOZ_ASSERT(aParent);
34 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aParent);
35 if (NS_WARN_IF(!global)) {
36 aRv.Throw(NS_ERROR_FAILURE);
37 return nullptr;
39 RefPtr<Promise> promise = Promise::Create(global, aRv);
40 if (NS_WARN_IF(aRv.Failed())) {
41 return nullptr;
43 return promise.forget();
46 already_AddRefed<Promise> CreateAndRejectWithNotAllowed(
47 nsPIDOMWindowInner* aParent, ErrorResult& aRv) {
48 MOZ_ASSERT(aParent);
49 RefPtr<Promise> promise = CreatePromise(aParent, aRv);
50 if (!promise) {
51 return nullptr;
53 promise->MaybeRejectWithNotAllowedError(
54 "CredentialContainer request is not allowed."_ns);
55 return promise.forget();
58 already_AddRefed<Promise> CreateAndRejectWithNotSupported(
59 nsPIDOMWindowInner* aParent, ErrorResult& aRv) {
60 MOZ_ASSERT(aParent);
61 RefPtr<Promise> promise = CreatePromise(aParent, aRv);
62 if (!promise) {
63 return nullptr;
65 promise->MaybeRejectWithNotSupportedError(
66 "CredentialContainer request is not supported."_ns);
67 return promise.forget();
70 static bool IsInActiveTab(nsPIDOMWindowInner* aParent) {
71 // Returns whether aParent is an inner window somewhere in the active tab.
72 // The active tab is the selected (i.e. visible) tab in the focused window.
73 MOZ_ASSERT(aParent);
75 RefPtr<Document> doc = aParent->GetExtantDoc();
76 if (NS_WARN_IF(!doc)) {
77 return false;
80 return IsInActiveTab(doc);
83 static bool ConsumeUserActivation(nsPIDOMWindowInner* aParent) {
84 // Returns whether aParent has transient activation, and consumes the
85 // activation.
86 MOZ_ASSERT(aParent);
88 RefPtr<Document> doc = aParent->GetExtantDoc();
89 if (NS_WARN_IF(!doc)) {
90 return false;
93 return doc->ConsumeTransientUserGestureActivation();
96 static bool IsSameOriginWithAncestors(nsPIDOMWindowInner* aParent) {
97 // This method returns true if aParent is either not in a frame / iframe, or
98 // is in a frame or iframe and all ancestors for aParent are the same origin.
99 // This is useful for Credential Management because we need to prohibit
100 // iframes, but not break mochitests (which use iframes to embed the tests).
101 MOZ_ASSERT(aParent);
103 WindowGlobalChild* wgc = aParent->GetWindowGlobalChild();
105 // If there's no WindowGlobalChild, the inner window has already been
106 // destroyed, so fail safe and return false.
107 if (!wgc) {
108 return false;
111 // Check that all ancestors are the same origin, repeating until we find a
112 // null parent
113 for (WindowContext* parentContext =
114 wgc->WindowContext()->GetParentWindowContext();
115 parentContext; parentContext = parentContext->GetParentWindowContext()) {
116 if (!wgc->IsSameOriginWith(parentContext)) {
117 // same-origin policy is violated
118 return false;
122 return true;
125 CredentialsContainer::CredentialsContainer(nsPIDOMWindowInner* aParent)
126 : mParent(aParent), mActiveIdentityRequest(false) {
127 MOZ_ASSERT(aParent);
130 CredentialsContainer::~CredentialsContainer() = default;
132 void CredentialsContainer::EnsureWebAuthnManager() {
133 MOZ_ASSERT(NS_IsMainThread());
135 if (!mManager) {
136 mManager = new WebAuthnManager(mParent);
140 already_AddRefed<WebAuthnManager> CredentialsContainer::GetWebAuthnManager() {
141 MOZ_ASSERT(NS_IsMainThread());
143 EnsureWebAuthnManager();
144 RefPtr<WebAuthnManager> ref = mManager;
145 return ref.forget();
148 JSObject* CredentialsContainer::WrapObject(JSContext* aCx,
149 JS::Handle<JSObject*> aGivenProto) {
150 return CredentialsContainer_Binding::Wrap(aCx, this, aGivenProto);
153 already_AddRefed<Promise> CredentialsContainer::Get(
154 const CredentialRequestOptions& aOptions, ErrorResult& aRv) {
155 uint64_t totalOptions = 0;
156 if (aOptions.mPublicKey.WasPassed() &&
157 StaticPrefs::security_webauth_webauthn()) {
158 totalOptions += 1;
160 if (aOptions.mIdentity.WasPassed() &&
161 StaticPrefs::dom_security_credentialmanagement_identity_enabled()) {
162 totalOptions += 1;
164 if (totalOptions > 1) {
165 return CreateAndRejectWithNotSupported(mParent, aRv);
168 bool conditionallyMediated =
169 aOptions.mMediation == CredentialMediationRequirement::Conditional;
170 if (aOptions.mPublicKey.WasPassed() &&
171 StaticPrefs::security_webauth_webauthn()) {
172 MOZ_ASSERT(mParent);
173 if (!FeaturePolicyUtils::IsFeatureAllowed(
174 mParent->GetExtantDoc(), u"publickey-credentials-get"_ns) ||
175 !IsInActiveTab(mParent)) {
176 return CreateAndRejectWithNotAllowed(mParent, aRv);
179 if (conditionallyMediated &&
180 !StaticPrefs::security_webauthn_enable_conditional_mediation()) {
181 RefPtr<Promise> promise = CreatePromise(mParent, aRv);
182 if (!promise) {
183 return nullptr;
185 promise->MaybeRejectWithTypeError<MSG_INVALID_ENUM_VALUE>(
186 "mediation", "conditional", "CredentialMediationRequirement");
187 return promise.forget();
190 EnsureWebAuthnManager();
191 return mManager->GetAssertion(aOptions.mPublicKey.Value(),
192 conditionallyMediated, aOptions.mSignal, aRv);
195 if (aOptions.mIdentity.WasPassed() &&
196 StaticPrefs::dom_security_credentialmanagement_identity_enabled()) {
197 RefPtr<Promise> promise = CreatePromise(mParent, aRv);
198 if (!promise) {
199 return nullptr;
202 if (conditionallyMediated) {
203 promise->MaybeRejectWithTypeError<MSG_INVALID_ENUM_VALUE>(
204 "mediation", "conditional", "CredentialMediationRequirement");
205 return promise.forget();
208 if (mActiveIdentityRequest) {
209 promise->MaybeRejectWithInvalidStateError(
210 "Concurrent 'identity' credentials.get requests are not supported."_ns);
211 return promise.forget();
213 mActiveIdentityRequest = true;
215 RefPtr<CredentialsContainer> self = this;
217 IdentityCredential::DiscoverFromExternalSource(
218 mParent, aOptions, IsSameOriginWithAncestors(mParent))
219 ->Then(
220 GetCurrentSerialEventTarget(), __func__,
221 [self, promise](const RefPtr<IdentityCredential>& credential) {
222 self->mActiveIdentityRequest = false;
223 promise->MaybeResolve(credential);
225 [self, promise](nsresult error) {
226 self->mActiveIdentityRequest = false;
227 promise->MaybeReject(error);
230 return promise.forget();
233 return CreateAndRejectWithNotSupported(mParent, aRv);
236 already_AddRefed<Promise> CredentialsContainer::Create(
237 const CredentialCreationOptions& aOptions, ErrorResult& aRv) {
238 // Count the types of options provided. Must not be >1.
239 uint64_t totalOptions = 0;
240 if (aOptions.mPublicKey.WasPassed() &&
241 StaticPrefs::security_webauth_webauthn()) {
242 totalOptions += 1;
244 if (totalOptions > 1) {
245 return CreateAndRejectWithNotSupported(mParent, aRv);
248 if (aOptions.mPublicKey.WasPassed() &&
249 StaticPrefs::security_webauth_webauthn()) {
250 MOZ_ASSERT(mParent);
251 // In a cross-origin iframe this request consumes user activation, i.e.
252 // subsequent requests cannot be made without further user interaction.
253 // See step 2.2 of https://w3c.github.io/webauthn/#sctn-createCredential
254 bool hasRequiredActivation =
255 IsInActiveTab(mParent) &&
256 (IsSameOriginWithAncestors(mParent) || ConsumeUserActivation(mParent));
257 if (!FeaturePolicyUtils::IsFeatureAllowed(
258 mParent->GetExtantDoc(), u"publickey-credentials-create"_ns) ||
259 !hasRequiredActivation) {
260 return CreateAndRejectWithNotAllowed(mParent, aRv);
263 EnsureWebAuthnManager();
264 return mManager->MakeCredential(aOptions.mPublicKey.Value(),
265 aOptions.mSignal, aRv);
268 return CreateAndRejectWithNotSupported(mParent, aRv);
271 already_AddRefed<Promise> CredentialsContainer::Store(
272 const Credential& aCredential, ErrorResult& aRv) {
273 nsString type;
274 aCredential.GetType(type);
275 if (type.EqualsLiteral("public-key") &&
276 StaticPrefs::security_webauth_webauthn()) {
277 if (!IsSameOriginWithAncestors(mParent) || !IsInActiveTab(mParent)) {
278 return CreateAndRejectWithNotAllowed(mParent, aRv);
281 EnsureWebAuthnManager();
282 return mManager->Store(aCredential, aRv);
285 if (type.EqualsLiteral("identity") &&
286 StaticPrefs::dom_security_credentialmanagement_identity_enabled()) {
287 return CreateAndRejectWithNotSupported(mParent, aRv);
290 return CreateAndRejectWithNotSupported(mParent, aRv);
293 already_AddRefed<Promise> CredentialsContainer::PreventSilentAccess(
294 ErrorResult& aRv) {
295 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mParent);
296 if (NS_WARN_IF(!global)) {
297 aRv.Throw(NS_ERROR_FAILURE);
298 return nullptr;
301 RefPtr<Promise> promise = Promise::Create(global, aRv);
302 if (NS_WARN_IF(aRv.Failed())) {
303 return nullptr;
306 promise->MaybeResolveWithUndefined();
307 return promise.forget();
310 } // namespace mozilla::dom