summaryrefslogtreecommitdiff
path: root/chromium/content/browser/cookie_store/cookie_store_manager.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/content/browser/cookie_store/cookie_store_manager.cc')
-rw-r--r--chromium/content/browser/cookie_store/cookie_store_manager.cc530
1 files changed, 530 insertions, 0 deletions
diff --git a/chromium/content/browser/cookie_store/cookie_store_manager.cc b/chromium/content/browser/cookie_store/cookie_store_manager.cc
new file mode 100644
index 00000000000..8e08a32ed55
--- /dev/null
+++ b/chromium/content/browser/cookie_store/cookie_store_manager.cc
@@ -0,0 +1,530 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/cookie_store/cookie_store_manager.h"
+
+#include <utility>
+
+#include "base/optional.h"
+#include "content/browser/cookie_store/cookie_change_subscriptions.pb.h"
+#include "content/browser/service_worker/embedded_worker_status.h"
+#include "content/browser/service_worker/service_worker_context_wrapper.h"
+#include "content/browser/service_worker/service_worker_metrics.h"
+#include "content/browser/service_worker/service_worker_registration.h"
+#include "content/browser/service_worker/service_worker_version.h"
+#include "content/common/service_worker/service_worker_status_code.h"
+#include "content/public/browser/browser_context.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "third_party/blink/public/mojom/service_worker/service_worker_event_status.mojom.h"
+#include "url/gurl.h"
+
+namespace content {
+
+namespace {
+
+// ServiceWorkerStorage user data key for cookie change subscriptions.
+const char kSubscriptionsUserKey[] = "cookie_store_subscriptions";
+
+// Handles the result of ServiceWorkerContextWrapper::StoreRegistrationUserData.
+void HandleStoreRegistrationUserDataStatus(ServiceWorkerStatusCode status) {
+ // The current implementation does not have a good way to handle errors in
+ // StoreRegistrationUserData. Cookie change subscriptions have been added to
+ // the registration during the install event, so it's too late to surface the
+ // error to the renderer. The registration has already been persisted, and the
+ // Service Worker is likely active by now.
+ DLOG_IF(ERROR, status != SERVICE_WORKER_OK)
+ << "StoreRegistrationUserData failed";
+}
+
+} // namespace
+
+CookieStoreManager::CookieStoreManager(
+ scoped_refptr<ServiceWorkerContextWrapper> service_worker_context)
+ : service_worker_context_(std::move(service_worker_context)),
+ cookie_change_listener_binding_(this),
+ registration_user_data_key_(kSubscriptionsUserKey),
+ weak_factory_(this) {
+ service_worker_context_->AddObserver(this);
+}
+
+CookieStoreManager::~CookieStoreManager() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ service_worker_context_->RemoveObserver(this);
+}
+
+void CookieStoreManager::CreateService(blink::mojom::CookieStoreRequest request,
+ const url::Origin& origin) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ bindings_.AddBinding(std::make_unique<CookieStoreHost>(this, origin),
+ std::move(request));
+}
+
+void CookieStoreManager::LoadAllSubscriptions(
+ base::OnceCallback<void(bool)> callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ DCHECK(!done_loading_subscriptions_) << __func__ << " already called";
+
+ service_worker_context_->GetUserDataForAllRegistrations(
+ registration_user_data_key_,
+ base::BindOnce(&CookieStoreManager::ProcessOnDiskSubscriptions,
+ weak_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void CookieStoreManager::ListenToCookieChanges(
+ ::network::mojom::CookieManagerPtr cookie_manager,
+ base::OnceCallback<void(bool)> callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ DCHECK(!cookie_manager_) << __func__ << " already called";
+ cookie_manager_ = std::move(cookie_manager);
+
+ DCHECK(!cookie_change_listener_binding_.is_bound());
+ ::network::mojom::CookieChangeListenerPtr cookie_change_listener;
+ cookie_change_listener_binding_.Bind(
+ mojo::MakeRequest(&cookie_change_listener));
+
+ // TODO(pwnall): Switch to an API with subscription confirmation.
+ cookie_manager_->AddGlobalChangeListener(std::move(cookie_change_listener));
+ std::move(callback).Run(true);
+}
+
+void CookieStoreManager::ProcessOnDiskSubscriptions(
+ base::OnceCallback<void(bool)> load_callback,
+ const std::vector<std::pair<int64_t, std::string>>& user_data,
+ ServiceWorkerStatusCode status) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ DCHECK(!done_loading_subscriptions_) << __func__ << " already called";
+ done_loading_subscriptions_ = true;
+
+ if (status != SERVICE_WORKER_OK) {
+ DidLoadAllSubscriptions(false, std::move(load_callback));
+ return;
+ }
+
+ DCHECK(subscriptions_by_registration_.empty());
+ subscriptions_by_registration_.reserve(user_data.size());
+ bool load_success = true;
+ for (const auto& pair : user_data) {
+ int64_t service_worker_registration_id = pair.first;
+ const std::string& proto_string = pair.second;
+
+ base::Optional<std::vector<CookieChangeSubscription>> subscriptions_opt =
+ CookieChangeSubscription::DeserializeVector(
+ proto_string, service_worker_registration_id);
+ if (!subscriptions_opt.has_value()) {
+ load_success = false;
+ continue;
+ }
+
+ ActivateSubscriptions(&subscriptions_opt.value());
+ DCHECK(
+ !subscriptions_by_registration_.count(service_worker_registration_id));
+ subscriptions_by_registration_.emplace(
+ std::move(service_worker_registration_id),
+ std::move(subscriptions_opt).value());
+ }
+
+ DidLoadAllSubscriptions(load_success, std::move(load_callback));
+}
+
+void CookieStoreManager::DidLoadAllSubscriptions(
+ bool succeeded,
+ base::OnceCallback<void(bool)> load_callback) {
+ DCHECK(done_loading_subscriptions_);
+ succeeded_loading_subscriptions_ = succeeded;
+
+ for (auto& callback : subscriptions_loaded_callbacks_)
+ std::move(callback).Run();
+ subscriptions_loaded_callbacks_.clear();
+
+ std::move(load_callback).Run(succeeded);
+}
+
+void CookieStoreManager::AppendSubscriptions(
+ int64_t service_worker_registration_id,
+ const url::Origin& origin,
+ std::vector<blink::mojom::CookieChangeSubscriptionPtr> mojo_subscriptions,
+ blink::mojom::CookieStore::AppendSubscriptionsCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (!done_loading_subscriptions_) {
+ subscriptions_loaded_callbacks_.emplace_back(base::BindOnce(
+ &CookieStoreManager::AppendSubscriptions, weak_factory_.GetWeakPtr(),
+ service_worker_registration_id, origin, std::move(mojo_subscriptions),
+ std::move(callback)));
+ return;
+ }
+
+ if (!succeeded_loading_subscriptions_) {
+ std::move(callback).Run(false);
+ return;
+ }
+
+ // GetLiveRegistration() is sufficient here (as opposed to a flavor of
+ // FindRegistration()) because AppendSubscriptions is only called from the
+ // implementation of the Cookie Store API, which is exposed to
+ // ServiceWorkerGlobalScope. ServiceWorkerGlobalScope references the
+ // service worker's registration via a ServiceWorkerRegistration JavaScript
+ // object, so the registration is guaranteed to be live while the service
+ // worker is executing.
+ //
+ // It is possible for the service worker to get killed while this API call is
+ // in progress, for example, if the service worker code exceeds an event
+ // handling time limit. In that case, the return value will not be observed,
+ // so a false negative is acceptable.
+ ServiceWorkerRegistration* service_worker_registration =
+ service_worker_context_->GetLiveRegistration(
+ service_worker_registration_id);
+ if (!service_worker_registration ||
+ !origin.IsSameOriginWith(
+ url::Origin::Create(service_worker_registration->pattern()))) {
+ // This error case is a good fit for mojo::ReportBadMessage(), because the
+ // renderer has passed an invalid registration ID. However, the code here
+ // might run without a mojo call context, if the original call was delayed
+ // while loading on-disk subscription data.
+ //
+ // While it would be possible to have two code paths for the two situations,
+ // the extra complexity doesn't seem warranted for the limited debuggig
+ // benefits provided by mojo::ReportBadMessage.
+ std::move(callback).Run(false);
+ return;
+ }
+
+ // TODO(crbug.com/843079): This check incorrectly allows an active service
+ // worker version to call the API, if another version
+ // is installing at the same time.
+ if (!service_worker_registration->installing_version()) {
+ // A service worker's cookie change subscriptions can only be modified while
+ // the service worker's install event is handled.
+ std::move(callback).Run(false);
+ return;
+ }
+
+ if (mojo_subscriptions.empty()) {
+ // Empty subscriptions are special-cased so we never have to serialize an
+ // empty array of subscriptions. This is advantageous because the protobuf
+ // serialization of an empty array is the empty string, which is also used
+ // by the convenience protobuf serialization API to signal serialization
+ // failure. So, supporting serializing an empty array would mean we can't
+ // use the convenience serialization API.
+ std::move(callback).Run(true);
+ return;
+ }
+
+ std::vector<CookieChangeSubscription> new_subscriptions =
+ CookieChangeSubscription::FromMojoVector(
+ std::move(mojo_subscriptions), service_worker_registration->id());
+ DCHECK(!new_subscriptions.empty());
+
+ auto old_subscriptions_it =
+ subscriptions_by_registration_.find(service_worker_registration_id);
+ if (old_subscriptions_it == subscriptions_by_registration_.end()) {
+ subscriptions_by_registration_.emplace(service_worker_registration_id,
+ std::move(new_subscriptions));
+ std::move(callback).Run(true);
+ return;
+ }
+
+ std::vector<CookieChangeSubscription>& old_subscriptions =
+ old_subscriptions_it->second;
+ old_subscriptions.reserve(old_subscriptions.size() +
+ new_subscriptions.size());
+ for (auto& new_subscription : new_subscriptions)
+ old_subscriptions.emplace_back(std::move(new_subscription));
+
+ std::move(callback).Run(true);
+}
+
+void CookieStoreManager::GetSubscriptions(
+ int64_t service_worker_registration_id,
+ const url::Origin& origin,
+ blink::mojom::CookieStore::GetSubscriptionsCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (!done_loading_subscriptions_) {
+ subscriptions_loaded_callbacks_.emplace_back(base::BindOnce(
+ &CookieStoreManager::GetSubscriptions, weak_factory_.GetWeakPtr(),
+ service_worker_registration_id, origin, std::move(callback)));
+ return;
+ }
+
+ if (!succeeded_loading_subscriptions_) {
+ std::move(callback).Run(
+ std::vector<blink::mojom::CookieChangeSubscriptionPtr>(), false);
+ return;
+ }
+
+ auto it = subscriptions_by_registration_.find(service_worker_registration_id);
+ if (it == subscriptions_by_registration_.end() || it->second.empty()) {
+ std::move(callback).Run(
+ std::vector<blink::mojom::CookieChangeSubscriptionPtr>(), true);
+ return;
+ }
+
+ const url::Origin& first_origin = url::Origin::Create(it->second[0].url());
+#if DCHECK_IS_ON()
+ for (const auto& subscription : it->second) {
+ DCHECK(
+ first_origin.IsSameOriginWith(url::Origin::Create(subscription.url())))
+ << "Service worker's change subscriptions don't have the same origin";
+ }
+#endif // DCHECK_IS_ON()
+
+ if (!origin.IsSameOriginWith(first_origin)) {
+ // This error case is a good fit for mojo::ReportBadMessage(), because the
+ // renderer has passed an invalid registration ID. However, the code here
+ // might run without a mojo call context, if the original call was delayed
+ // while loading on-disk subscription data.
+ //
+ // While it would be possible to have two code paths for the two situations,
+ // the extra complexity doesn't seem warranted for the limited debuggig
+ // benefits provided by mojo::ReportBadMessage.
+ std::move(callback).Run(
+ std::vector<blink::mojom::CookieChangeSubscriptionPtr>(), false);
+ return;
+ }
+
+ std::move(callback).Run(CookieChangeSubscription::ToMojoVector(it->second),
+ true);
+}
+
+void CookieStoreManager::OnNewLiveRegistration(
+ int64_t service_worker_registration_id,
+ const GURL& pattern) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+void CookieStoreManager::OnRegistrationStored(
+ int64_t service_worker_registration_id,
+ const GURL& pattern) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // Waiting for the on-disk subscriptions to be loaded ensures that the
+ // registration's subscriptions aren't activated twice. Without waiting,
+ // there's a risk that LoadAllSubscriptions() sees the result of the
+ // StoreRegistrationUserData() call below.
+ if (!done_loading_subscriptions_) {
+ subscriptions_loaded_callbacks_.emplace_back(base::BindOnce(
+ &CookieStoreManager::OnRegistrationStored, weak_factory_.GetWeakPtr(),
+ service_worker_registration_id, pattern));
+ return;
+ }
+
+ auto it = subscriptions_by_registration_.find(service_worker_registration_id);
+ if (it == subscriptions_by_registration_.end())
+ return;
+
+ ActivateSubscriptions(&it->second);
+
+ std::string subscriptions_data =
+ CookieChangeSubscription::SerializeVector(it->second);
+ DCHECK(!subscriptions_data.empty())
+ << "Failed to create cookie change subscriptions protobuf";
+
+ service_worker_context_->StoreRegistrationUserData(
+ service_worker_registration_id, pattern.GetOrigin(),
+ std::vector<std::pair<std::string, std::string>>(
+ {{registration_user_data_key_, subscriptions_data}}),
+ base::BindOnce(&HandleStoreRegistrationUserDataStatus));
+}
+
+void CookieStoreManager::OnRegistrationDeleted(
+ int64_t service_worker_registration_id,
+ const GURL& pattern) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // Waiting for the on-disk subscriptions to be loaded ensures that the
+ // registration's subscriptions are removed. Without waiting, there's a risk
+ // that a registration's subscriptions will finish loading (and thus remain
+ // active) right after this function runs.
+ if (!done_loading_subscriptions_) {
+ subscriptions_loaded_callbacks_.emplace_back(base::BindOnce(
+ &CookieStoreManager::OnRegistrationDeleted, weak_factory_.GetWeakPtr(),
+ service_worker_registration_id, pattern));
+ return;
+ }
+
+ auto it = subscriptions_by_registration_.find(service_worker_registration_id);
+ if (it == subscriptions_by_registration_.end())
+ return;
+
+ DeactivateSubscriptions(&it->second);
+ subscriptions_by_registration_.erase(it);
+}
+
+void CookieStoreManager::ActivateSubscriptions(
+ std::vector<CookieChangeSubscription>* subscriptions) {
+ if (subscriptions->empty())
+ return;
+
+ // Service workers can only observe changes to cookies for URLs under their
+ // scope. This means all the URLs that the worker is observing must map to the
+ // same domain key (eTLD+1).
+ //
+ // TODO(pwnall): This is the same as implementation as
+ // net::CookieMonsterChangeDispatcher::DomainKey. Extract that
+ // implementation into net/cookies.cookie_util.h and call it.
+ std::string url_key = net::registry_controlled_domains::GetDomainAndRegistry(
+
+ (*subscriptions)[0].url(),
+ net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
+ base::LinkedList<CookieChangeSubscription>& url_key_subscriptions_list =
+ subscriptions_by_url_key_[url_key];
+
+ for (auto& subscription : *subscriptions) {
+ DCHECK(!subscription.next() && !subscription.previous())
+ << "Subscription passed to " << __func__ << " already activated";
+ DCHECK_EQ(url_key,
+ net::registry_controlled_domains::GetDomainAndRegistry(
+ subscription.url(),
+ net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES))
+ << __func__ << " subscriptions belong to different registrations";
+ url_key_subscriptions_list.Append(&subscription);
+ }
+}
+
+void CookieStoreManager::DeactivateSubscriptions(
+ std::vector<CookieChangeSubscription>* subscriptions) {
+ if (subscriptions->empty())
+ return;
+
+ // Service workers can only observe changes to cookies for URLs under their
+ // scope. This means all the URLs that the worker is observing must map to the
+ // same domain key (eTLD+1).
+ //
+ // TODO(pwnall): This has the same implementation as
+ // net::CookieMonsterChangeDispatcher::DomainKey. Extract that
+ // implementation into net/cookies.cookie_util.h and call it.
+ std::string url_key = net::registry_controlled_domains::GetDomainAndRegistry(
+ (*subscriptions)[0].url(),
+ net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
+ for (auto& subscription : *subscriptions) {
+ DCHECK(subscription.next() && subscription.previous())
+ << "Subscription passed to " << __func__ << " not previously activated";
+ DCHECK_EQ(url_key,
+ net::registry_controlled_domains::GetDomainAndRegistry(
+ subscription.url(),
+ net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES))
+ << __func__ << " subscriptions belong to different registrations";
+ subscription.RemoveFromList();
+ }
+ auto it = subscriptions_by_url_key_.find(url_key);
+ DCHECK(it != subscriptions_by_url_key_.end());
+ if (it->second.empty())
+ subscriptions_by_url_key_.erase(it);
+}
+
+void CookieStoreManager::OnStorageWiped() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // Waiting for the on-disk subscriptions to be loaded ensures that all
+ // subscriptions are removed. Without waiting, there's a risk that some
+ // subscriptions will finish loading (and thus remain active) after this
+ // function runs.
+ if (!done_loading_subscriptions_) {
+ subscriptions_loaded_callbacks_.emplace_back(base::BindOnce(
+ &CookieStoreManager::OnStorageWiped, weak_factory_.GetWeakPtr()));
+ return;
+ }
+
+ subscriptions_by_url_key_.clear();
+ subscriptions_by_registration_.clear();
+}
+
+void CookieStoreManager::OnCookieChange(
+ const net::CanonicalCookie& cookie,
+ ::network::mojom::CookieChangeCause cause) {
+ // Waiting for on-disk subscriptions to be loaded ensures that changes are
+ // delivered to all service workers that subscribed to them in previous
+ // browser sessions. Without waiting, workers might miss cookie changes.
+ if (!done_loading_subscriptions_) {
+ subscriptions_loaded_callbacks_.emplace_back(
+ base::BindOnce(&CookieStoreManager::OnCookieChange,
+ weak_factory_.GetWeakPtr(), cookie, cause));
+ return;
+ }
+
+ // Compute the list of service workers interested in this change. A worker
+ // might have multiple subscriptions that cover this change, but should still
+ // receive a single change event.
+ // TODO(pwnall): This has same as implementation as
+ // net::CookieMonsterChangeDispatcher::DomainKey. Extract that
+ // implementation into net/cookies.cookie_util.h and call it.
+ std::string url_key = net::registry_controlled_domains::GetDomainAndRegistry(
+ cookie.Domain(),
+ net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
+ auto it = subscriptions_by_url_key_.find(url_key);
+ if (it == subscriptions_by_url_key_.end())
+ return;
+ std::set<int64_t> interested_registration_ids;
+ const base::LinkedList<CookieChangeSubscription>& subscriptions = it->second;
+ for (const base::LinkNode<CookieChangeSubscription>* node =
+ subscriptions.head();
+ node != subscriptions.end(); node = node->next()) {
+ const CookieChangeSubscription* subscription = node->value();
+ if (subscription->ShouldObserveChangeTo(cookie)) {
+ interested_registration_ids.insert(
+ subscription->service_worker_registration_id());
+ }
+ }
+
+ // Dispatch the change to interested workers.
+ for (int64_t registration_id : interested_registration_ids) {
+ service_worker_context_->FindReadyRegistrationForIdOnly(
+ registration_id,
+ base::BindOnce(
+ [](base::WeakPtr<CookieStoreManager> manager,
+ const net::CanonicalCookie& cookie,
+ ::network::mojom::CookieChangeCause cause,
+ ServiceWorkerStatusCode find_status,
+ scoped_refptr<ServiceWorkerRegistration> registration) {
+ if (find_status != SERVICE_WORKER_OK)
+ return;
+
+ DCHECK(registration);
+ if (!manager)
+ return;
+ manager->DispatchChangeEvent(std::move(registration), cookie,
+ cause);
+ },
+ weak_factory_.GetWeakPtr(), cookie, cause));
+ }
+}
+
+void CookieStoreManager::DispatchChangeEvent(
+ scoped_refptr<ServiceWorkerRegistration> registration,
+ const net::CanonicalCookie& cookie,
+ ::network::mojom::CookieChangeCause cause) {
+ scoped_refptr<ServiceWorkerVersion> active_version =
+ registration->active_version();
+ if (active_version->running_status() != EmbeddedWorkerStatus::RUNNING) {
+ active_version->RunAfterStartWorker(
+ ServiceWorkerMetrics::EventType::COOKIE_CHANGE,
+ base::BindOnce(&CookieStoreManager::DidStartWorkerForChangeEvent,
+ weak_factory_.GetWeakPtr(), std::move(registration),
+ cookie, cause));
+ return;
+ }
+
+ int request_id = active_version->StartRequest(
+ ServiceWorkerMetrics::EventType::COOKIE_CHANGE, base::DoNothing());
+
+ active_version->event_dispatcher()->DispatchCookieChangeEvent(
+ cookie, cause, active_version->CreateSimpleEventCallback(request_id));
+}
+
+void CookieStoreManager::DidStartWorkerForChangeEvent(
+ scoped_refptr<ServiceWorkerRegistration> registration,
+ const net::CanonicalCookie& cookie,
+ ::network::mojom::CookieChangeCause cause,
+ ServiceWorkerStatusCode start_worker_status) {
+ if (start_worker_status != SERVICE_WORKER_OK)
+ return;
+ DispatchChangeEvent(std::move(registration), cookie, cause);
+}
+
+} // namespace content