From d7866d603a24914d8af9ebfb61bb004273bc3d03 Mon Sep 17 00:00:00 2001 From: Benjamin VanderSloot Date: Wed, 26 Mar 2025 18:09:51 +0000 Subject: [PATCH] Bug 1945576, part 2 - Add implementation for Login Status JS hooks - r=anti-tracking-reviewers,timhuang Differential Revision: https://phabricator.services.mozilla.com/D240218 --- .../CredentialsContainer.cpp | 4 +- .../CredentialsContainer.h | 2 + .../IdentityCredentialSerializationHelpers.h | 5 + .../identity/NavigatorLogin.cpp | 128 ++++++++++++++++-- .../identity/NavigatorLogin.h | 10 +- dom/ipc/PWindowGlobal.ipdl | 4 + dom/ipc/WindowGlobalParent.cpp | 13 ++ dom/ipc/WindowGlobalParent.h | 3 + 8 files changed, 156 insertions(+), 13 deletions(-) diff --git a/dom/credentialmanagement/CredentialsContainer.cpp b/dom/credentialmanagement/CredentialsContainer.cpp index 5d0fa5ed29b2..6b2672342f02 100644 --- a/dom/credentialmanagement/CredentialsContainer.cpp +++ b/dom/credentialmanagement/CredentialsContainer.cpp @@ -98,7 +98,9 @@ static bool ConsumeUserActivation(nsPIDOMWindowInner* aParent) { return doc->ConsumeTransientUserGestureActivation(); } -static bool IsSameOriginWithAncestors(nsPIDOMWindowInner* aParent) { +// static +bool CredentialsContainer::IsSameOriginWithAncestors( + nsPIDOMWindowInner* aParent) { // This method returns true if aParent is either not in a frame / iframe, or // is in a frame or iframe and all ancestors for aParent are the same origin. // This is useful for Credential Management because we need to prohibit diff --git a/dom/credentialmanagement/CredentialsContainer.h b/dom/credentialmanagement/CredentialsContainer.h index cdb0e6cc188e..a453f64b8b9d 100644 --- a/dom/credentialmanagement/CredentialsContainer.h +++ b/dom/credentialmanagement/CredentialsContainer.h @@ -38,6 +38,8 @@ class CredentialsContainer final : public nsISupports, public nsWrapperCache { already_AddRefed PreventSilentAccess(ErrorResult& aRv); + static bool IsSameOriginWithAncestors(nsPIDOMWindowInner* aParent); + private: ~CredentialsContainer(); diff --git a/dom/credentialmanagement/identity/IdentityCredentialSerializationHelpers.h b/dom/credentialmanagement/identity/IdentityCredentialSerializationHelpers.h index 89fbcc7d7181..daef1628d42a 100644 --- a/dom/credentialmanagement/identity/IdentityCredentialSerializationHelpers.h +++ b/dom/credentialmanagement/identity/IdentityCredentialSerializationHelpers.h @@ -11,6 +11,7 @@ #include "mozilla/dom/IdentityCredential.h" #include "mozilla/dom/IdentityCredentialBinding.h" #include "mozilla/dom/CredentialManagementBinding.h" +#include "mozilla/dom/LoginStatusBinding.h" namespace IPC { @@ -81,6 +82,10 @@ struct ParamTraits { } }; +template <> +struct ParamTraits + : public mozilla::dom::WebIDLEnumSerializer {}; + } // namespace IPC #endif // mozilla_dom_identitycredentialserializationhelpers_h__ diff --git a/dom/credentialmanagement/identity/NavigatorLogin.cpp b/dom/credentialmanagement/identity/NavigatorLogin.cpp index cd0d612c7ade..ac676d34e554 100644 --- a/dom/credentialmanagement/identity/NavigatorLogin.cpp +++ b/dom/credentialmanagement/identity/NavigatorLogin.cpp @@ -4,10 +4,48 @@ * 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/Components.h" +#include "mozilla/dom/CredentialsContainer.h" +#include "mozilla/dom/Document.h" #include "mozilla/dom/NavigatorLogin.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/Maybe.h" +#include "mozilla/dom/WindowGlobalChild.h" #include "nsCycleCollectionParticipant.h" +#include "nsIGlobalObject.h" +#include "nsIPermissionManager.h" +#include "nsIPrincipal.h" +#include "nsString.h" -namespace mozilla::dom { +namespace mozilla { +namespace dom { + +static constexpr nsLiteralCString kLoginStatusPermission = + "self-reported-logged-in"_ns; + +uint32_t ConvertStatusToPermission(LoginStatus aStatus) { + if (aStatus == LoginStatus::Logged_in) { + return nsIPermissionManager::ALLOW_ACTION; + } else if (aStatus == LoginStatus::Logged_out) { + return nsIPermissionManager::DENY_ACTION; + } else { + // This should be unreachable, but let's return a real value + return nsIPermissionManager::UNKNOWN_ACTION; + } +} + +Maybe PermissionToStatus(uint32_t aPermission) { + if (aPermission == nsIPermissionManager::ALLOW_ACTION) { + return Some(LoginStatus::Logged_in); + } else if (aPermission == nsIPermissionManager::DENY_ACTION) { + return Some(LoginStatus::Logged_out); + } else { + if (aPermission == nsIPermissionManager::UNKNOWN_ACTION) { + MOZ_ASSERT(false, "Unexpected permission action from login status"); + } + return Nothing(); + } +} NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(NavigatorLogin, mOwner) @@ -18,21 +56,91 @@ JSObject* NavigatorLogin::WrapObject(JSContext* aCx, return NavigatorLogin_Binding::Wrap(aCx, this, aGivenProto); } -NavigatorLogin::NavigatorLogin(nsIGlobalObject* aGlobal) - : mOwner(aGlobal){ - MOZ_ASSERT(mOwner); - }; +NavigatorLogin::NavigatorLogin(nsIGlobalObject* aGlobal) : mOwner(aGlobal) { + MOZ_ASSERT(mOwner); +}; already_AddRefed NavigatorLogin::SetStatus( - const LoginStatus& aStatus, mozilla::ErrorResult& aRv) { - + LoginStatus aStatus, mozilla::ErrorResult& aRv) { RefPtr promise = Promise::Create(mOwner, aRv); if (aRv.Failed()) { return nullptr; } - promise->MaybeRejectWithNotSupportedError( - "navigator.login.setStatus not implemented"_ns); + + nsPIDOMWindowInner* window = mOwner->GetAsInnerWindow(); + if (!window) { + promise->MaybeRejectWithUnknownError( + "navigator.login.setStatus called on unavailable window"_ns); + return promise.forget(); + } + + if (!CredentialsContainer::IsSameOriginWithAncestors(window)) { + promise->MaybeRejectWithSecurityError( + "navigator.login.setStatus must be called in a frame that is same-origin with its ancestors"_ns); + return promise.forget(); + } + + WindowGlobalChild* wgc = window->GetWindowGlobalChild(); + if (!wgc) { + promise->MaybeRejectWithUnknownError( + "navigator.login.setStatus called while window already destroyed"_ns); + return promise.forget(); + } + + wgc->SendSetLoginStatus(aStatus)->Then( + GetCurrentSerialEventTarget(), __func__, + [promise]( + const WindowGlobalChild::SetLoginStatusPromise::ResolveValueType& + aResult) { + if (NS_SUCCEEDED(aResult)) { + promise->MaybeResolveWithUndefined(); + } else { + promise->MaybeRejectWithUnknownError( + "navigator.login.setStatus had an unexpected internal error"); + } + }, + [promise](const WindowGlobalChild::SetLoginStatusPromise::RejectValueType& + aResult) { + promise->MaybeRejectWithUnknownError( + "navigator.login.setStatus had an unexpected internal error"); + }); return promise.forget(); } -} // namespace mozilla::dom +// static +nsresult NavigatorLogin::SetLoginStatus(nsIPrincipal* aPrincipal, + LoginStatus aStatus) { + MOZ_ASSERT(XRE_IsParentProcess()); + + nsCOMPtr permMgr = + components::PermissionManager::Service(); + if (!permMgr) { + return NS_ERROR_NOT_AVAILABLE; + } + + return permMgr->AddFromPrincipal(aPrincipal, kLoginStatusPermission, + ConvertStatusToPermission(aStatus), + nsIPermissionManager::EXPIRE_NEVER, 0); +} + +// static +nsresult GetLoginStatus(nsIPrincipal* aPrincipal, Maybe& aStatus) { + nsCOMPtr permMgr = + components::PermissionManager::Service(); + if (!permMgr) { + aStatus = Nothing(); + return NS_ERROR_SERVICE_NOT_AVAILABLE; + } + uint32_t action; + nsresult rv = permMgr->TestPermissionFromPrincipal( + aPrincipal, kLoginStatusPermission, &action); + if (NS_FAILED(rv)) { + aStatus = Nothing(); + return rv; + } + aStatus = PermissionToStatus(action); + return NS_OK; +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/credentialmanagement/identity/NavigatorLogin.h b/dom/credentialmanagement/identity/NavigatorLogin.h index d7cee3db4f1f..e62ee7d30f44 100644 --- a/dom/credentialmanagement/identity/NavigatorLogin.h +++ b/dom/credentialmanagement/identity/NavigatorLogin.h @@ -7,7 +7,10 @@ #ifndef mozilla_dom_NavigatorLogin_h #define mozilla_dom_NavigatorLogin_h +#include "ErrorList.h" #include "mozilla/dom/LoginStatusBinding.h" +#include "mozilla/Maybe.h" +#include "nsIGlobalObject.h" #include "nsISupports.h" #include "nsWrapperCache.h" #include "nsCOMPtr.h" @@ -24,9 +27,12 @@ class NavigatorLogin : public nsWrapperCache { virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; - already_AddRefed SetStatus(const LoginStatus& aStatus, + already_AddRefed SetStatus(LoginStatus aStatus, mozilla::ErrorResult& aRv); + static Maybe GetLoginStatus(nsIPrincipal* aPrincipal); + static nsresult SetLoginStatus(nsIPrincipal* aPrincipal, LoginStatus aStatus); + protected: virtual ~NavigatorLogin(); @@ -36,4 +42,4 @@ class NavigatorLogin : public nsWrapperCache { } // namespace mozilla::dom -#endif // mozilla_dom_NavigatorLogin_h \ No newline at end of file +#endif // mozilla_dom_NavigatorLogin_h diff --git a/dom/ipc/PWindowGlobal.ipdl b/dom/ipc/PWindowGlobal.ipdl index f4816b9e064f..62c8a9a57ced 100644 --- a/dom/ipc/PWindowGlobal.ipdl +++ b/dom/ipc/PWindowGlobal.ipdl @@ -40,6 +40,7 @@ using mozilla::dom::IdentityCredentialDisconnectOptions from "mozilla/dom/Identi using mozilla::dom::IdentityCredentialInit from "mozilla/dom/IdentityCredentialBinding.h"; using mozilla::dom::IdentityCredentialRequestOptions from "mozilla/dom/IdentityCredentialBinding.h"; using mozilla::dom::IdentityLoginTargetType from "mozilla/dom/IdentityCredentialBinding.h"; +using mozilla::dom::LoginStatus from "mozilla/dom/LoginStatusBinding.h"; namespace mozilla { namespace dom { @@ -218,6 +219,9 @@ parent: async DisconnectIdentityCredential(IdentityCredentialDisconnectOptions aOptions) returns (nsresult rv); async PreventSilentAccess() returns (nsresult rv); + // Indicate that the page has reported itself as logged in + async SetLoginStatus(LoginStatus foo) returns (nsresult rv); + async GetStorageAccessPermission(bool aIncludeIdentityCredential) returns(uint32_t permission_action); diff --git a/dom/ipc/WindowGlobalParent.cpp b/dom/ipc/WindowGlobalParent.cpp index cc114b37eec5..9c386c4665d5 100644 --- a/dom/ipc/WindowGlobalParent.cpp +++ b/dom/ipc/WindowGlobalParent.cpp @@ -26,6 +26,7 @@ #include "mozilla/dom/BrowserParent.h" #include "mozilla/dom/IdentityCredential.h" #include "mozilla/dom/MediaController.h" +#include "mozilla/dom/NavigatorLogin.h" #include "mozilla/dom/WebAuthnTransactionParent.h" #include "mozilla/dom/WindowGlobalChild.h" #include "mozilla/dom/ChromeUtils.h" @@ -1503,6 +1504,18 @@ IPCResult WindowGlobalParent::RecvPreventSilentAccess( return IPC_OK(); } +mozilla::ipc::IPCResult WindowGlobalParent::RecvSetLoginStatus( + LoginStatus aStatus, const SetLoginStatusResolver& aResolver) { + nsIPrincipal* principal = DocumentPrincipal(); + if (!principal) { + aResolver(NS_ERROR_DOM_NOT_ALLOWED_ERR); + return IPC_OK(); + } + nsresult rv = NavigatorLogin::SetLoginStatus(principal, aStatus); + aResolver(rv); + return IPC_OK(); +} + IPCResult WindowGlobalParent::RecvGetStorageAccessPermission( bool aIncludeIdentityCredential, GetStorageAccessPermissionResolver&& aResolve) { diff --git a/dom/ipc/WindowGlobalParent.h b/dom/ipc/WindowGlobalParent.h index bb2a0d4ebcdd..18346053c2b1 100644 --- a/dom/ipc/WindowGlobalParent.h +++ b/dom/ipc/WindowGlobalParent.h @@ -328,6 +328,9 @@ class WindowGlobalParent final : public WindowContext, mozilla::ipc::IPCResult RecvDisconnectIdentityCredential( const IdentityCredentialDisconnectOptions& aOptions, const DisconnectIdentityCredentialResolver& aResolver); + mozilla::ipc::IPCResult RecvSetLoginStatus( + LoginStatus aStatus, const SetLoginStatusResolver& aResolver); + mozilla::ipc::IPCResult RecvPreventSilentAccess( const PreventSilentAccessResolver& aResolver);