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 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).
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.
98 // Check that all ancestors are the same origin, repeating until we find a
100 for (WindowContext
* parentContext
=
101 wgc
->WindowContext()->GetParentWindowContext();
102 parentContext
; parentContext
= parentContext
->GetParentWindowContext()) {
103 if (!wgc
->IsSameOriginWith(parentContext
)) {
104 // same-origin policy is violated
112 CredentialsContainer::CredentialsContainer(nsPIDOMWindowInner
* aParent
)
113 : mParent(aParent
), mActiveIdentityRequest(false) {
117 CredentialsContainer::~CredentialsContainer() = default;
119 void CredentialsContainer::EnsureWebAuthnManager() {
120 MOZ_ASSERT(NS_IsMainThread());
123 mManager
= new WebAuthnManager(mParent
);
127 already_AddRefed
<WebAuthnManager
> CredentialsContainer::GetWebAuthnManager() {
128 MOZ_ASSERT(NS_IsMainThread());
130 EnsureWebAuthnManager();
131 RefPtr
<WebAuthnManager
> ref
= mManager
;
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()) {
147 if (aOptions
.mIdentity
.WasPassed() &&
148 StaticPrefs::dom_security_credentialmanagement_identity_enabled()) {
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()) {
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
169 RefPtr
<Promise
> promise
= CreatePromise(mParent
, aRv
);
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
,
183 if (aOptions
.mIdentity
.WasPassed() &&
184 StaticPrefs::dom_security_credentialmanagement_identity_enabled()) {
185 RefPtr
<Promise
> promise
= CreatePromise(mParent
, aRv
);
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
))
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()) {
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
) {
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(
274 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(mParent
);
275 if (NS_WARN_IF(!global
)) {
276 aRv
.Throw(NS_ERROR_FAILURE
);
280 RefPtr
<Promise
> promise
= Promise::Create(global
, aRv
);
281 if (NS_WARN_IF(aRv
.Failed())) {
285 promise
->MaybeResolveWithUndefined();
286 return promise
.forget();
289 } // namespace mozilla::dom