summaryrefslogtreecommitdiff
path: root/chromium/components/signin/core/browser/consistency_cookie_manager.h
blob: 068adb797fd98ecdf87f94055937e05e968846c4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
// Copyright 2022 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.

#ifndef COMPONENTS_SIGNIN_CORE_BROWSER_CONSISTENCY_COOKIE_MANAGER_H_
#define COMPONENTS_SIGNIN_CORE_BROWSER_CONSISTENCY_COOKIE_MANAGER_H_

#include "base/gtest_prod_util.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observation.h"
#include "components/signin/core/browser/account_reconcilor.h"
#include "net/cookies/canonical_cookie.h"
#include "third_party/abseil-cpp/absl/types/optional.h"

namespace network::mojom {
class CookieManager;
}

class SigninClient;

namespace signin {

// The `ConsistencyCookieManager` manages the CHROME_ID_CONSISTENCY_STATE
// cookie, which is used to display an interstitial page (a.k.a. "Mirror
// Landing") while account additions are in progress. This avoids issues where
// the user has to manually reload the page or retry their navigation after
// adding an account to the OS account manager.
// The value of the cookie depends on the state of the `AccountReconcilor` and
// whether there is a native account addition flow in progress.
// The cookie is updated only if it already exists. The cookie creation is only
// forced when a `ScopedAccountUpdate` is created, which indicates an explicit
// navigation to Gaia.
class ConsistencyCookieManager : public AccountReconcilor::Observer {
 public:
  // Sets the cookie state to "Updating" while it's alive.
  // Instances are vended by `CreateScopedAccountUpdate()` and are allowed to
  // outlive the `ConsistencyCookieManager`.
  class ScopedAccountUpdate final {
   public:
    ~ScopedAccountUpdate();

    // Move operations.
    ScopedAccountUpdate(ScopedAccountUpdate&& other);
    ScopedAccountUpdate& operator=(ScopedAccountUpdate&& other);

    // `ScopedAccountUpdate` is not copyable.
    ScopedAccountUpdate(const ScopedAccountUpdate&) = delete;
    ScopedAccountUpdate& operator=(const ScopedAccountUpdate&) = delete;

   private:
    friend ConsistencyCookieManager;
    ScopedAccountUpdate(base::WeakPtr<ConsistencyCookieManager> manager);
    base::WeakPtr<ConsistencyCookieManager> consistency_cookie_manager_;
  };

  explicit ConsistencyCookieManager(SigninClient* signin_client,
                                    AccountReconcilor* reconcilor);
  ~ConsistencyCookieManager() override;

  ConsistencyCookieManager& operator=(const ConsistencyCookieManager&) = delete;
  ConsistencyCookieManager(const ConsistencyCookieManager&) = delete;

  // Web-signin UI flows should guarantee that at least a scoped update is alive
  // for the whole flow. This starts from the user interaction and finishes when
  // the account has been added to the `IdentityManager`.
  ScopedAccountUpdate CreateScopedAccountUpdate();

  // Adds or removes an extra cookie manager where the cookie updates are
  // duplicated. It is expected that an extra cookie manager is only set
  // temporarily (e.g. for the duration of a single signin flow), with the
  // intent of importing the accounts from the main cookie manager.
  void AddExtraCookieManager(network::mojom::CookieManager* manager);
  void RemoveExtraCookieManager(network::mojom::CookieManager* manager);

  // Creates the `CanonicalCookie` corresponding to the consistency cookie.
  static std::unique_ptr<net::CanonicalCookie> CreateConsistencyCookie(
      const std::string& value);

 private:
  friend class ConsistencyCookieManagerTest;
  FRIEND_TEST_ALL_PREFIXES(ConsistencyCookieManagerTest, MoveOperations);
  FRIEND_TEST_ALL_PREFIXES(ConsistencyCookieManagerTest, ReconcilorState);
  FRIEND_TEST_ALL_PREFIXES(ConsistencyCookieManagerTest, ScopedAccountUpdate);
  FRIEND_TEST_ALL_PREFIXES(ConsistencyCookieManagerTest,
                           ScopedAccountUpdate_Inactive);
  FRIEND_TEST_ALL_PREFIXES(ConsistencyCookieManagerTest,
                           UpdateAfterDestruction);
  FRIEND_TEST_ALL_PREFIXES(ConsistencyCookieManagerTest, FirstCookieUpdate);
  FRIEND_TEST_ALL_PREFIXES(ConsistencyCookieManagerTest, CookieDeleted);
  FRIEND_TEST_ALL_PREFIXES(ConsistencyCookieManagerTest, CookieInvalid);
  FRIEND_TEST_ALL_PREFIXES(ConsistencyCookieManagerTest, CookieAlreadySet);
  FRIEND_TEST_ALL_PREFIXES(ConsistencyCookieManagerTest, CoalesceCookieQueries);
  FRIEND_TEST_ALL_PREFIXES(ConsistencyCookieManagerTest, CancelPendingQuery);
  FRIEND_TEST_ALL_PREFIXES(ConsistencyCookieManagerTest, ExtraCookieManager);

  enum class CookieValue {
    kConsistent,    // Value is "Consistent".
    kInconsistent,  // Value is "Inconsistent".
    kUpdating,      // Value is "Updating".
    kInvalid        // Any other value.
  };

  // Cookie name and values.
  static const char kCookieName[];
  static const char kCookieValueStringConsistent[];
  static const char kCookieValueStringInconsistent[];
  static const char kCookieValueStringUpdating[];

  // Returns whether `cookie` is the consistency cookie.
  static bool IsConsistencyCookie(const net::CanonicalCookie& cookie);

  // Parses the cookie value from its string representation.
  static CookieValue ParseCookieValue(const std::string& value);

  // Sets the cookie to match `value`.
  static void SetCookieValue(network::mojom::CookieManager* cookie_manager,
                             CookieValue value);

  // AccountReconcilor::Observer:
  void OnStateChanged(signin_metrics::AccountReconcilorState state) override;

  // Calculates the cookie value based on the reconcilor state and the count of
  // live `ScopedAccountUpdate` instances. Returns `absl::nullopt` if the value
  // cannot be computed (e.g. if the reconcilor is not started).
  absl::optional<CookieValue> CalculateCookieValue() const;

  // Gets the new value using `CalculateCookieValue()` and sets the cookie if it
  // changed. If `force_creation` is false, triggers a cookie query, as it
  // should only be updated if it already exists.
  void UpdateCookieIfNeeded(bool force_creation);

  // Callback for `CookieManager::GetCookieList()`. Updates the cached value of
  // the cookie, and updates the cookie only if the cookie already exists.
  void UpdateCookieIfExists(
      const net::CookieAccessResultList& cookie_list,
      const net::CookieAccessResultList& /*excluded_cookies*/);

  SigninClient* const signin_client_;
  AccountReconcilor* const account_reconcilor_;
  signin_metrics::AccountReconcilorState account_reconcilor_state_ =
      signin_metrics::ACCOUNT_RECONCILOR_INACTIVE;
  int scoped_update_count_ = 0;

  // Cached value of the cookie, equal to the last value that was either set or
  // queried. `absl::nullopt` when the cookie is missing. Initialized as
  // `CookieValue::kInvalid` so that the first cookie update is always tried,
  // but should never be set to `CookieValue::kInvalid` after that.
  absl::optional<CookieValue> cookie_value_ = CookieValue::kInvalid;

  // Pending cookie update, applied after querying the cookie value. The pending
  // update is only applied if the cookie already exists.
  absl::optional<CookieValue> pending_cookie_update_;

  // Extra cookie managers where the cookie is also written. These are never
  // read, and if they go out of sync with the main cookie manager, they may
  // not be updated correctly.
  std::vector<network::mojom::CookieManager*> extra_cookie_managers_;

  base::ScopedObservation<AccountReconcilor, AccountReconcilor::Observer>
      account_reconcilor_observation_{this};

  base::WeakPtrFactory<ConsistencyCookieManager> weak_factory_{this};
};

}  // namespace signin

#endif  // COMPONENTS_SIGNIN_CORE_BROWSER_CONSISTENCY_COOKIE_MANAGER_H_