no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / dom / credentialmanagement / CredentialsContainer.cpp
blob9fb879770deb89d4308485434dffbb846682d90d
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 IsSameOriginWithAncestors(nsPIDOMWindowInner* aParent) {
84 // This method returns true if aParent is either not in a frame / iframe, or
85 // is in a frame or iframe and all ancestors for aParent are the same origin.
86 // This is useful for Credential Management because we need to prohibit
87 // iframes, but not break mochitests (which use iframes to embed the tests).
88 MOZ_ASSERT(aParent);
90 WindowGlobalChild* wgc = aParent->GetWindowGlobalChild();
92 // If there's no WindowGlobalChild, the inner window has already been
93 // destroyed, so fail safe and return false.
94 if (!wgc) {
95 return false;
98 // Check that all ancestors are the same origin, repeating until we find a
99 // null parent
100 for (WindowContext* parentContext =
101 wgc->WindowContext()->GetParentWindowContext();
102 parentContext; parentContext = parentContext->GetParentWindowContext()) {
103 if (!wgc->IsSameOriginWith(parentContext)) {
104 // same-origin policy is violated
105 return false;
109 return true;
112 CredentialsContainer::CredentialsContainer(nsPIDOMWindowInner* aParent)
113 : mParent(aParent), mActiveIdentityRequest(false) {
114 MOZ_ASSERT(aParent);
117 CredentialsContainer::~CredentialsContainer() = default;
119 void CredentialsContainer::EnsureWebAuthnManager() {
120 MOZ_ASSERT(NS_IsMainThread());
122 if (!mManager) {
123 mManager = new WebAuthnManager(mParent);
127 already_AddRefed<WebAuthnManager> CredentialsContainer::GetWebAuthnManager() {
128 MOZ_ASSERT(NS_IsMainThread());
130 EnsureWebAuthnManager();
131 RefPtr<WebAuthnManager> ref = mManager;
132 return ref.forget();
135 JSObject* CredentialsContainer::WrapObject(JSContext* aCx,
136 JS::Handle<JSObject*> aGivenProto) {
137 return CredentialsContainer_Binding::Wrap(aCx, this, aGivenProto);
140 already_AddRefed<Promise> CredentialsContainer::Get(
141 const CredentialRequestOptions& aOptions, ErrorResult& aRv) {
142 uint64_t totalOptions = 0;
143 if (aOptions.mPublicKey.WasPassed() &&
144 StaticPrefs::security_webauth_webauthn()) {
145 totalOptions += 1;
147 if (aOptions.mIdentity.WasPassed() &&
148 StaticPrefs::dom_security_credentialmanagement_identity_enabled()) {
149 totalOptions += 1;
151 if (totalOptions > 1) {
152 return CreateAndRejectWithNotSupported(mParent, aRv);
155 bool conditionallyMediated =
156 aOptions.mMediation == CredentialMediationRequirement::Conditional;
157 if (aOptions.mPublicKey.WasPassed() &&
158 StaticPrefs::security_webauth_webauthn()) {
159 MOZ_ASSERT(mParent);
160 if (!FeaturePolicyUtils::IsFeatureAllowed(
161 mParent->GetExtantDoc(), u"publickey-credentials-get"_ns) ||
162 !IsInActiveTab(mParent)) {
163 return CreateAndRejectWithNotAllowed(mParent, aRv);
166 if (conditionallyMediated) {
167 // Conditional mediation for WebAuthn Get() will be implemented in
168 // Bug 1838932.
169 RefPtr<Promise> promise = CreatePromise(mParent, aRv);
170 if (!promise) {
171 return nullptr;
173 promise->MaybeRejectWithTypeError<MSG_INVALID_ENUM_VALUE>(
174 "mediation", "conditional", "CredentialMediationRequirement");
175 return promise.forget();
178 EnsureWebAuthnManager();
179 return mManager->GetAssertion(aOptions.mPublicKey.Value(), aOptions.mSignal,
180 aRv);
183 if (aOptions.mIdentity.WasPassed() &&
184 StaticPrefs::dom_security_credentialmanagement_identity_enabled()) {
185 RefPtr<Promise> promise = CreatePromise(mParent, aRv);
186 if (!promise) {
187 return nullptr;
190 if (conditionallyMediated) {
191 promise->MaybeRejectWithTypeError<MSG_INVALID_ENUM_VALUE>(
192 "mediation", "conditional", "CredentialMediationRequirement");
193 return promise.forget();
196 if (mActiveIdentityRequest) {
197 promise->MaybeRejectWithInvalidStateError(
198 "Concurrent 'identity' credentials.get requests are not supported."_ns);
199 return promise.forget();
201 mActiveIdentityRequest = true;
203 RefPtr<CredentialsContainer> self = this;
205 IdentityCredential::DiscoverFromExternalSource(
206 mParent, aOptions, IsSameOriginWithAncestors(mParent))
207 ->Then(
208 GetCurrentSerialEventTarget(), __func__,
209 [self, promise](const RefPtr<IdentityCredential>& credential) {
210 self->mActiveIdentityRequest = false;
211 promise->MaybeResolve(credential);
213 [self, promise](nsresult error) {
214 self->mActiveIdentityRequest = false;
215 promise->MaybeReject(error);
218 return promise.forget();
221 return CreateAndRejectWithNotSupported(mParent, aRv);
224 already_AddRefed<Promise> CredentialsContainer::Create(
225 const CredentialCreationOptions& aOptions, ErrorResult& aRv) {
226 // Count the types of options provided. Must not be >1.
227 uint64_t totalOptions = 0;
228 if (aOptions.mPublicKey.WasPassed() &&
229 StaticPrefs::security_webauth_webauthn()) {
230 totalOptions += 1;
232 if (totalOptions > 1) {
233 return CreateAndRejectWithNotSupported(mParent, aRv);
236 if (aOptions.mPublicKey.WasPassed() &&
237 StaticPrefs::security_webauth_webauthn()) {
238 if (!IsSameOriginWithAncestors(mParent) || !IsInActiveTab(mParent)) {
239 return CreateAndRejectWithNotAllowed(mParent, aRv);
242 EnsureWebAuthnManager();
243 return mManager->MakeCredential(aOptions.mPublicKey.Value(),
244 aOptions.mSignal, aRv);
247 return CreateAndRejectWithNotSupported(mParent, aRv);
250 already_AddRefed<Promise> CredentialsContainer::Store(
251 const Credential& aCredential, ErrorResult& aRv) {
252 nsString type;
253 aCredential.GetType(type);
254 if (type.EqualsLiteral("public-key") &&
255 StaticPrefs::security_webauth_webauthn()) {
256 if (!IsSameOriginWithAncestors(mParent) || !IsInActiveTab(mParent)) {
257 return CreateAndRejectWithNotAllowed(mParent, aRv);
260 EnsureWebAuthnManager();
261 return mManager->Store(aCredential, aRv);
264 if (type.EqualsLiteral("identity") &&
265 StaticPrefs::dom_security_credentialmanagement_identity_enabled()) {
266 return CreateAndRejectWithNotSupported(mParent, aRv);
269 return CreateAndRejectWithNotSupported(mParent, aRv);
272 already_AddRefed<Promise> CredentialsContainer::PreventSilentAccess(
273 ErrorResult& aRv) {
274 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mParent);
275 if (NS_WARN_IF(!global)) {
276 aRv.Throw(NS_ERROR_FAILURE);
277 return nullptr;
280 RefPtr<Promise> promise = Promise::Create(global, aRv);
281 if (NS_WARN_IF(aRv.Failed())) {
282 return nullptr;
285 promise->MaybeResolveWithUndefined();
286 return promise.forget();
289 } // namespace mozilla::dom