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
)
31 already_AddRefed
<Promise
> CreatePromise(nsPIDOMWindowInner
* aParent
,
34 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aParent
);
35 if (NS_WARN_IF(!global
)) {
36 aRv
.Throw(NS_ERROR_FAILURE
);
39 RefPtr
<Promise
> promise
= Promise::Create(global
, aRv
);
40 if (NS_WARN_IF(aRv
.Failed())) {
43 return promise
.forget();
46 already_AddRefed
<Promise
> CreateAndRejectWithNotAllowed(
47 nsPIDOMWindowInner
* aParent
, ErrorResult
& aRv
) {
49 RefPtr
<Promise
> promise
= CreatePromise(aParent
, aRv
);
53 promise
->MaybeRejectWithNotAllowedError(
54 "CredentialContainer request is not allowed."_ns
);
55 return promise
.forget();
58 already_AddRefed
<Promise
> CreateAndRejectWithNotSupported(
59 nsPIDOMWindowInner
* aParent
, ErrorResult
& aRv
) {
61 RefPtr
<Promise
> promise
= CreatePromise(aParent
, aRv
);
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.
75 RefPtr
<Document
> doc
= aParent
->GetExtantDoc();
76 if (NS_WARN_IF(!doc
)) {
80 return IsInActiveTab(doc
);
83 static bool ConsumeUserActivation(nsPIDOMWindowInner
* aParent
) {
84 // Returns whether aParent has transient activation, and consumes the
88 RefPtr
<Document
> doc
= aParent
->GetExtantDoc();
89 if (NS_WARN_IF(!doc
)) {
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).
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.
111 // Check that all ancestors are the same origin, repeating until we find a
113 for (WindowContext
* parentContext
=
114 wgc
->WindowContext()->GetParentWindowContext();
115 parentContext
; parentContext
= parentContext
->GetParentWindowContext()) {
116 if (!wgc
->IsSameOriginWith(parentContext
)) {
117 // same-origin policy is violated
125 CredentialsContainer::CredentialsContainer(nsPIDOMWindowInner
* aParent
)
126 : mParent(aParent
), mActiveIdentityRequest(false) {
130 CredentialsContainer::~CredentialsContainer() = default;
132 void CredentialsContainer::EnsureWebAuthnManager() {
133 MOZ_ASSERT(NS_IsMainThread());
136 mManager
= new WebAuthnManager(mParent
);
140 already_AddRefed
<WebAuthnManager
> CredentialsContainer::GetWebAuthnManager() {
141 MOZ_ASSERT(NS_IsMainThread());
143 EnsureWebAuthnManager();
144 RefPtr
<WebAuthnManager
> ref
= mManager
;
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()) {
160 if (aOptions
.mIdentity
.WasPassed() &&
161 StaticPrefs::dom_security_credentialmanagement_identity_enabled()) {
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()) {
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
);
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
);
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
))
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()) {
244 if (totalOptions
> 1) {
245 return CreateAndRejectWithNotSupported(mParent
, aRv
);
248 if (aOptions
.mPublicKey
.WasPassed() &&
249 StaticPrefs::security_webauth_webauthn()) {
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
) {
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(
295 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(mParent
);
296 if (NS_WARN_IF(!global
)) {
297 aRv
.Throw(NS_ERROR_FAILURE
);
301 RefPtr
<Promise
> promise
= Promise::Create(global
, aRv
);
302 if (NS_WARN_IF(aRv
.Failed())) {
306 promise
->MaybeResolveWithUndefined();
307 return promise
.forget();
310 } // namespace mozilla::dom