Bug 1509047 - Part 3: Add support for automatically granting storage access permissions on a temporary session-based basis on the Gecko side r=baku
☠☠ backed out by 0f4be93319f6 ☠ ☠
authorEhsan Akhgari <ehsan@mozilla.com>
Wed, 28 Nov 2018 22:02:51 +0000
changeset 507866 cc9d468d6ba6a52d06f7fb03b8778e1ce6e70e69
parent 507865 1aa87e9e3c71eb756c446b285f098d0dc267b38e
child 507867 37172d2d6f99802975761c9e26bac9f2b94d8c44
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1509047
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1509047 - Part 3: Add support for automatically granting storage access permissions on a temporary session-based basis on the Gecko side r=baku Depends on D12863 Differential Revision: https://phabricator.services.mozilla.com/D12864
dom/base/StorageAccessPermissionRequest.cpp
dom/base/StorageAccessPermissionRequest.h
dom/base/nsDocument.cpp
toolkit/components/antitracking/AntiTrackingCommon.cpp
toolkit/components/antitracking/AntiTrackingCommon.h
--- a/dom/base/StorageAccessPermissionRequest.cpp
+++ b/dom/base/StorageAccessPermissionRequest.cpp
@@ -14,22 +14,24 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(Stora
 
 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(StorageAccessPermissionRequest,
                                                ContentPermissionRequestBase)
 
 StorageAccessPermissionRequest::StorageAccessPermissionRequest(
     nsPIDOMWindowInner* aWindow,
     nsIPrincipal* aNodePrincipal,
     AllowCallback&& aAllowCallback,
+    AllowAutoGrantCallback&& aAllowAutoGrantCallback,
     AllowAnySiteCallback&& aAllowAnySiteCallback,
     CancelCallback&& aCancelCallback)
   : ContentPermissionRequestBase(aNodePrincipal, false, aWindow,
                                  NS_LITERAL_CSTRING("dom.storage_access"),
                                  NS_LITERAL_CSTRING("storage-access")),
     mAllowCallback(std::move(aAllowCallback)),
+    mAllowAutoGrantCallback(std::move(aAllowAutoGrantCallback)),
     mAllowAnySiteCallback(std::move(aAllowAnySiteCallback)),
     mCancelCallback(std::move(aCancelCallback)),
     mCallbackCalled(false)
 {
   mPermissionRequests.AppendElement(PermissionRequest(mType, nsTArray<nsString>()));
 }
 
 StorageAccessPermissionRequest::~StorageAccessPermissionRequest()
@@ -56,39 +58,44 @@ StorageAccessPermissionRequest::Allow(JS
     return rv;
   }
 
   if (!mCallbackCalled) {
     mCallbackCalled = true;
     if (choices.Length() == 1 &&
         choices[0].choice().EqualsLiteral("allow-on-any-site")) {
       mAllowAnySiteCallback();
+    } else if (choices.Length() == 1 &&
+               choices[0].choice().EqualsLiteral("allow-auto-grant")) {
+      mAllowAutoGrantCallback();
     } else {
       mAllowCallback();
     }
   }
   return NS_OK;
 }
 
 already_AddRefed<StorageAccessPermissionRequest>
 StorageAccessPermissionRequest::Create(nsPIDOMWindowInner* aWindow,
                                        AllowCallback&& aAllowCallback,
+                                       AllowAutoGrantCallback&& aAllowAutoGrantCallback,
                                        AllowAnySiteCallback&& aAllowAnySiteCallback,
                                        CancelCallback&& aCancelCallback)
 {
   if (!aWindow) {
     return nullptr;
   }
   nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(aWindow);
   if (!win->GetPrincipal()) {
     return nullptr;
   }
   RefPtr<StorageAccessPermissionRequest> request =
     new StorageAccessPermissionRequest(aWindow,
                                        win->GetPrincipal(),
                                        std::move(aAllowCallback),
+                                       std::move(aAllowAutoGrantCallback),
                                        std::move(aAllowAnySiteCallback),
                                        std::move(aCancelCallback));
   return request.forget();
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/StorageAccessPermissionRequest.h
+++ b/dom/base/StorageAccessPermissionRequest.h
@@ -23,34 +23,38 @@ public:
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(StorageAccessPermissionRequest,
                                            ContentPermissionRequestBase)
 
   // nsIContentPermissionRequest
   NS_IMETHOD Cancel(void) override;
   NS_IMETHOD Allow(JS::HandleValue choices) override;
 
   typedef std::function<void()> AllowCallback;
+  typedef std::function<void()> AllowAutoGrantCallback;
   typedef std::function<void()> AllowAnySiteCallback;
   typedef std::function<void()> CancelCallback;
 
   static already_AddRefed<StorageAccessPermissionRequest> Create(
     nsPIDOMWindowInner* aWindow,
     AllowCallback&& aAllowCallback,
+    AllowAutoGrantCallback&& aAllowAutoGrantCallback,
     AllowAnySiteCallback&& aAllowAnySiteCallback,
     CancelCallback&& aCancelCallback);
 
 private:
   StorageAccessPermissionRequest(nsPIDOMWindowInner* aWindow,
                                  nsIPrincipal* aNodePrincipal,
                                  AllowCallback&& aAllowCallback,
+                                 AllowAutoGrantCallback&& aAllowAutoGrantCallback,
                                  AllowAnySiteCallback&& aAllowAnySiteCallback,
                                  CancelCallback&& aCancelCallback);
   ~StorageAccessPermissionRequest();
 
   AllowCallback mAllowCallback;
+  AllowAutoGrantCallback mAllowAutoGrantCallback;
   AllowAnySiteCallback mAllowAnySiteCallback;
   CancelCallback mCancelCallback;
   nsTArray<PermissionRequest> mPermissionRequests;
   bool mCallbackCalled;
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -13983,19 +13983,21 @@ nsIDocument::RequestStorageAccess(mozill
                     !isOnAllowList);
 
       auto performFinalChecks = [inner] () -> RefPtr<AntiTrackingCommon::StorageAccessFinalCheckPromise> {
           RefPtr<AntiTrackingCommon::StorageAccessFinalCheckPromise::Private> p =
             new AntiTrackingCommon::StorageAccessFinalCheckPromise::Private(__func__);
           RefPtr<StorageAccessPermissionRequest> sapr =
             StorageAccessPermissionRequest::Create(inner,
               // Allow
-              [p] { p->Resolve(false, __func__); },
+              [p] { p->Resolve(AntiTrackingCommon::eAllow, __func__); },
+              // Allow auto grant
+              [p] { p->Resolve(AntiTrackingCommon::eAllowAutoGrant, __func__); },
               // Allow on any site
-              [p] { p->Resolve(true, __func__); },
+              [p] { p->Resolve(AntiTrackingCommon::eAllowOnAnySite, __func__); },
               // Block
               [p] { p->Reject(false, __func__); });
 
           typedef ContentPermissionRequestBase::PromptResult PromptResult;
           PromptResult pr = sapr->CheckPromptPrefs();
           bool onAnySite = false;
           if (pr == PromptResult::Pending) {
             // Also check our custom pref for the "Allow on any site" case
@@ -14006,17 +14008,18 @@ nsIDocument::RequestStorageAccess(mozill
             }
           }
 
           if (pr != PromptResult::Pending) {
             MOZ_ASSERT_IF(pr != PromptResult::Granted,
                           pr == PromptResult::Denied);
             if (pr == PromptResult::Granted) {
               return AntiTrackingCommon::StorageAccessFinalCheckPromise::
-                CreateAndResolve(onAnySite, __func__);
+                CreateAndResolve(onAnySite ? AntiTrackingCommon::eAllowOnAnySite :
+                                             AntiTrackingCommon::eAllow, __func__);
             }
             return AntiTrackingCommon::StorageAccessFinalCheckPromise::
               CreateAndReject(false, __func__);
           }
 
           sapr->RequestDelayedTask(inner->EventTargetFor(TaskCategory::Other),
                                    ContentPermissionRequestBase::DelayedTaskType::Request);
           return p.forget();
--- a/toolkit/components/antitracking/AntiTrackingCommon.cpp
+++ b/toolkit/components/antitracking/AntiTrackingCommon.cpp
@@ -403,16 +403,99 @@ CompareBaseDomains(nsIURI* aTrackingURI,
     LOG(("Can't get the base domain from parent principal"));
     return false;
   }
 
   return trackingBaseDomain.Equals(parentPrincipalBaseDomain,
                                    nsCaseInsensitiveCStringComparator());
 }
 
+class TemporaryAccessGrantObserver final : public nsIObserver
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
+  static void
+  Create(nsIPermissionManager* aPM,
+         nsIPrincipal* aPrincipal,
+         const nsACString& aType)
+  {
+    nsCOMPtr<nsITimer> timer;
+    RefPtr<TemporaryAccessGrantObserver> observer =
+      new TemporaryAccessGrantObserver(aPM, aPrincipal, aType);
+    nsresult rv =
+      NS_NewTimerWithObserver(getter_AddRefs(timer),
+                              observer,
+                              24 * 60 * 60 * 1000, // 24 hours
+                              nsITimer::TYPE_ONE_SHOT);
+
+    if (NS_SUCCEEDED(rv)) {
+      observer->SetTimer(timer);
+    } else {
+      timer->Cancel();
+    }
+  }
+
+  void SetTimer(nsITimer* aTimer)
+  {
+    mTimer = aTimer;
+    nsCOMPtr<nsIObserverService> observerService =
+      mozilla::services::GetObserverService();
+    if (observerService) {
+      observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+    }
+  }
+
+private:
+  TemporaryAccessGrantObserver(nsIPermissionManager* aPM,
+                               nsIPrincipal* aPrincipal,
+                               const nsACString& aType)
+    : mPM(aPM)
+    , mPrincipal(aPrincipal)
+    , mType(aType)
+  {
+    MOZ_ASSERT(XRE_IsParentProcess(),
+               "Enforcing temporary access grant lifetimes can only be done in "
+               "the parent process");
+  }
+
+  ~TemporaryAccessGrantObserver() = default;
+
+private:
+  nsCOMPtr<nsITimer> mTimer;
+  nsCOMPtr<nsIPermissionManager> mPM;
+  nsCOMPtr<nsIPrincipal> mPrincipal;
+  nsCString mType;
+};
+
+NS_IMPL_ISUPPORTS(TemporaryAccessGrantObserver, nsIObserver)
+
+NS_IMETHODIMP
+TemporaryAccessGrantObserver::Observe(nsISupports* aSubject,
+                                      const char* aTopic,
+                                      const char16_t* aData)
+{
+  if (strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC) == 0) {
+    Unused << mPM->RemoveFromPrincipal(mPrincipal, mType.get());
+  } else if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
+    nsCOMPtr<nsIObserverService> observerService =
+      mozilla::services::GetObserverService();
+    if (observerService) {
+      observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+    }
+    if (mTimer) {
+      mTimer->Cancel();
+      mTimer = nullptr;
+    }
+  }
+
+  return NS_OK;
+}
+
 } // anonymous
 
 /* static */ RefPtr<AntiTrackingCommon::StorageAccessGrantPromise>
 AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(nsIPrincipal* aPrincipal,
                                                          nsPIDOMWindowInner* aParentWindow,
                                                          StorageAccessGrantedReason aReason,
                                                          const AntiTrackingCommon::PerformFinalChecks& aPerformFinalChecks)
 {
@@ -434,21 +517,21 @@ AntiTrackingCommon::AddFirstPartyStorage
 
   LOG(("Adding a first-party storage exception for %s...",
        NS_ConvertUTF16toUTF8(origin).get()));
 
   if (StaticPrefs::network_cookie_cookieBehavior() !=
         nsICookieService::BEHAVIOR_REJECT_TRACKER) {
     LOG(("Disabled by network.cookie.cookieBehavior pref (%d), bailing out early",
          StaticPrefs::network_cookie_cookieBehavior()));
-    return StorageAccessGrantPromise::CreateAndResolve(true, __func__);
+    return StorageAccessGrantPromise::CreateAndResolve(eAllowOnAnySite, __func__);
   }
 
   if (CheckContentBlockingAllowList(aParentWindow)) {
-    return StorageAccessGrantPromise::CreateAndResolve(true, __func__);
+    return StorageAccessGrantPromise::CreateAndResolve(eAllowOnAnySite, __func__);
   }
 
   nsCOMPtr<nsIPrincipal> topLevelStoragePrincipal;
   nsCOMPtr<nsIURI> trackingURI;
   nsAutoCString trackingOrigin;
   nsCOMPtr<nsIPrincipal> trackingPrincipal;
 
   RefPtr<nsGlobalWindowInner> parentWindow = nsGlobalWindowInner::Cast(aParentWindow);
@@ -529,17 +612,17 @@ AntiTrackingCommon::AddFirstPartyStorage
   if (!pwin) {
     LOG(("Couldn't get the top window"));
     return StorageAccessGrantPromise::CreateAndReject(false, __func__);
   }
 
   auto storePermission = [pwin, parentWindow, origin, trackingOrigin,
                           trackingPrincipal, trackingURI, topInnerWindow,
                           topLevelStoragePrincipal, aReason]
-                         (bool aAnySite) -> RefPtr<StorageAccessGrantPromise> {
+                         (int aAllowMode) -> RefPtr<StorageAccessGrantPromise> {
     NS_ConvertUTF16toUTF8 grantedOrigin(origin);
 
     nsAutoCString permissionKey;
     CreatePermissionKey(trackingOrigin, grantedOrigin, permissionKey);
 
     // Let's store the permission in the current parent window.
     topInnerWindow->SaveStorageAccessGranted(permissionKey);
 
@@ -557,21 +640,22 @@ AntiTrackingCommon::AddFirstPartyStorage
     if (XRE_IsParentProcess()) {
       LOG(("Saving the permission: trackingOrigin=%s, grantedOrigin=%s",
            trackingOrigin.get(), grantedOrigin.get()));
 
       return SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(topLevelStoragePrincipal,
                                                                         trackingPrincipal,
                                                                         trackingOrigin,
                                                                         grantedOrigin,
-                                                                        aAnySite)
+                                                                        aAllowMode)
         ->Then(GetCurrentThreadSerialEventTarget(), __func__,
                [] (FirstPartyStorageAccessGrantPromise::ResolveOrRejectValue&& aValue) {
                  if (aValue.IsResolve()) {
-                   return StorageAccessGrantPromise::CreateAndResolve(NS_SUCCEEDED(aValue.ResolveValue()), __func__);
+                   return StorageAccessGrantPromise::CreateAndResolve(NS_SUCCEEDED(aValue.ResolveValue()) ?
+                                                                      eAllowOnAnySite : eAllow, __func__);
                  }
                  return StorageAccessGrantPromise::CreateAndReject(false, __func__);
                });
     }
 
     ContentChild* cc = ContentChild::GetSingleton();
     MOZ_ASSERT(cc);
 
@@ -579,17 +663,17 @@ AntiTrackingCommon::AddFirstPartyStorage
          trackingOrigin.get(), grantedOrigin.get()));
 
     // This is not really secure, because here we have the content process sending
     // the request of storing a permission.
     return cc->SendFirstPartyStorageAccessGrantedForOrigin(IPC::Principal(topLevelStoragePrincipal),
                                                            IPC::Principal(trackingPrincipal),
                                                            trackingOrigin,
                                                            grantedOrigin,
-                                                           aAnySite)
+                                                           aAllowMode)
       ->Then(GetCurrentThreadSerialEventTarget(), __func__,
              [] (const ContentChild::FirstPartyStorageAccessGrantedForOriginPromise::ResolveOrRejectValue& aValue) {
                if (aValue.IsResolve()) {
                  return StorageAccessGrantPromise::CreateAndResolve(aValue.ResolveValue(), __func__);
                }
                return StorageAccessGrantPromise::CreateAndReject(false, __func__);
              });
   };
@@ -607,19 +691,22 @@ AntiTrackingCommon::AddFirstPartyStorage
   return storePermission(false);
 }
 
 /* static */ RefPtr<mozilla::AntiTrackingCommon::FirstPartyStorageAccessGrantPromise>
 AntiTrackingCommon::SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(nsIPrincipal* aParentPrincipal,
                                                                                nsIPrincipal* aTrackingPrincipal,
                                                                                const nsCString& aTrackingOrigin,
                                                                                const nsCString& aGrantedOrigin,
-                                                                               bool aAnySite)
+                                                                               int aAllowMode)
 {
   MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(aAllowMode == eAllow ||
+             aAllowMode == eAllowAutoGrant ||
+             aAllowMode == eAllowOnAnySite);
 
   nsCOMPtr<nsIURI> parentPrincipalURI;
   Unused << aParentPrincipal->GetURI(getter_AddRefs(parentPrincipalURI));
   LOG_SPEC(("Saving a first-party storage permission on %s for trackingOrigin=%s grantedOrigin=%s",
             _spec, aTrackingOrigin.get(), aGrantedOrigin.get()), parentPrincipalURI);
 
   if (NS_WARN_IF(!aParentPrincipal)) {
     // The child process is sending something wrong. Let's ignore it.
@@ -635,17 +722,17 @@ AntiTrackingCommon::SaveFirstPartyStorag
 
   // Remember that this pref is stored in seconds!
   uint32_t expirationType = nsIPermissionManager::EXPIRE_TIME;
   uint32_t expirationTime =
     StaticPrefs::privacy_restrict3rdpartystorage_expiration() * 1000;
   int64_t when = (PR_Now() / PR_USEC_PER_MSEC) + expirationTime;
 
   nsresult rv;
-  if (aAnySite) {
+  if (aAllowMode == eAllowOnAnySite) {
     uint32_t privateBrowsingId = 0;
     rv = aTrackingPrincipal->GetPrivateBrowsingId(&privateBrowsingId);
     if (!NS_WARN_IF(NS_FAILED(rv)) && privateBrowsingId > 0) {
       // If we are coming from a private window, make sure to store a session-only
       // permission which won't get persisted to disk.
       expirationType = nsIPermissionManager::EXPIRE_SESSION;
       when = 0;
     }
@@ -654,32 +741,39 @@ AntiTrackingCommon::SaveFirstPartyStorag
          expirationTime));
 
     rv = pm->AddFromPrincipal(aTrackingPrincipal, "cookie",
                               nsICookiePermission::ACCESS_ALLOW,
                               expirationType, when);
   } else {
     uint32_t privateBrowsingId = 0;
     rv = aParentPrincipal->GetPrivateBrowsingId(&privateBrowsingId);
-    if (!NS_WARN_IF(NS_FAILED(rv)) && privateBrowsingId > 0) {
-      // If we are coming from a private window, make sure to store a session-only
-      // permission which won't get persisted to disk.
+    if ((!NS_WARN_IF(NS_FAILED(rv)) && privateBrowsingId > 0) ||
+        (aAllowMode == eAllowAutoGrant)) {
+      // If we are coming from a private window or are automatically granting a
+      // permission, make sure to store a session-only permission which won't
+      // get persisted to disk.
       expirationType = nsIPermissionManager::EXPIRE_SESSION;
       when = 0;
     }
 
     nsAutoCString type;
     CreatePermissionKey(aTrackingOrigin, aGrantedOrigin, type);
 
     LOG(("Computed permission key: %s, expiry: %u, proceeding to save in the permission manager",
          type.get(), expirationTime));
 
     rv = pm->AddFromPrincipal(aParentPrincipal, type.get(),
                               nsIPermissionManager::ALLOW_ACTION,
                               expirationType, when);
+
+    if (NS_SUCCEEDED(rv) && (aAllowMode == eAllowAutoGrant)) {
+      // Make sure temporary access grants do not survive more than 24 hours.
+      TemporaryAccessGrantObserver::Create(pm, aParentPrincipal, type);
+    }
   }
   Unused << NS_WARN_IF(NS_FAILED(rv));
 
   LOG(("Result: %s", NS_SUCCEEDED(rv) ? "success" : "failure"));
   return FirstPartyStorageAccessGrantPromise::CreateAndResolve(rv, __func__);
 }
 
 // static
--- a/toolkit/components/antitracking/AntiTrackingCommon.h
+++ b/toolkit/components/antitracking/AntiTrackingCommon.h
@@ -72,35 +72,41 @@ public:
   IsFirstPartyStorageAccessGrantedFor(nsIPrincipal* aPrincipal);
 
   enum StorageAccessGrantedReason
   {
     eStorageAccessAPI,
     eOpenerAfterUserInteraction,
     eOpener
   };
+  enum StorageAccessPromptChoices
+  {
+    eAllow,
+    eAllowAutoGrant,
+    eAllowOnAnySite
+  };
 
   // Grant the permission for aOrigin to have access to the first party storage.
   // This method can handle 2 different scenarios:
   // - aParentWindow is a 3rd party context, it opens an aOrigin window and the
   //   user interacts with it. We want to grant the permission at the
   //   combination: top-level + aParentWindow + aOrigin.
   //   Ex: example.net loads an iframe tracker.com, which opens a popup
   //   tracker.prg and the user interacts with it. tracker.org is allowed if
   //   loaded by tracker.com when loaded by example.net.
   // - aParentWindow is a first party context and a 3rd party resource (probably
   //   becuase of a script) opens a popup and the user interacts with it. We
   //   want to grant the permission for the 3rd party context to have access to
   //   the first party stoage when loaded in aParentWindow.
   //   Ex: example.net import tracker.com/script.js which does opens a popup and
   //   the user interacts with it. tracker.com is allowed when loaded by
   //   example.net.
-  typedef MozPromise<bool, bool, true> StorageAccessFinalCheckPromise;
+  typedef MozPromise<int, bool, true> StorageAccessFinalCheckPromise;
   typedef std::function<RefPtr<StorageAccessFinalCheckPromise>()> PerformFinalChecks;
-  typedef MozPromise<bool, bool, true> StorageAccessGrantPromise;
+  typedef MozPromise<int, bool, true> StorageAccessGrantPromise;
   static MOZ_MUST_USE RefPtr<StorageAccessGrantPromise>
   AddFirstPartyStorageAccessGrantedFor(nsIPrincipal* aPrincipal,
                                        nsPIDOMWindowInner* aParentWindow,
                                        StorageAccessGrantedReason aReason,
                                        const PerformFinalChecks& aPerformFinalChecks = nullptr);
 
   // Returns true if the permission passed in is a storage access permission
   // for the passed in principal argument.
@@ -115,17 +121,17 @@ public:
 
   // For IPC only.
   typedef MozPromise<nsresult, bool, true> FirstPartyStorageAccessGrantPromise;
   static RefPtr<FirstPartyStorageAccessGrantPromise>
   SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(nsIPrincipal* aPrincipal,
                                                              nsIPrincipal* aTrackingPrinciapl,
                                                              const nsCString& aParentOrigin,
                                                              const nsCString& aGrantedOrigin,
-                                                             bool aAnySite);
+                                                             int aAllowMode);
 
   enum ContentBlockingAllowListPurpose {
     eStorageChecks,
     eTrackingProtection,
     eTrackingAnnotations,
   };
 
   // Check whether a top window URI is on the content blocking allow list.