1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/signin/chrome_signin_client.h"
6 #include <memory>
7 #include <utility>
8 
9 #include "base/bind.h"
10 #include "base/notreached.h"
11 #include "base/run_loop.h"
12 #include "base/stl_util.h"
13 #include "build/build_config.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/signin/chrome_signin_client_factory.h"
16 #include "chrome/browser/signin/signin_util.h"
17 #include "chrome/common/pref_names.h"
18 #include "chrome/test/base/testing_browser_process.h"
19 #include "chrome/test/base/testing_profile.h"
20 #include "content/public/browser/network_service_instance.h"
21 #include "content/public/test/browser_task_environment.h"
22 #include "services/network/test/test_network_connection_tracker.h"
23 #include "testing/gmock/include/gmock/gmock.h"
24 #include "testing/gtest/include/gtest/gtest.h"
25 
26 #if !defined(OS_ANDROID)
27 #include "chrome/test/base/browser_with_test_window_test.h"
28 #endif
29 
30 // ChromeOS has its own network delay logic.
31 #if !defined(OS_CHROMEOS)
32 
33 namespace {
34 
35 class CallbackTester {
36  public:
CallbackTester()37   CallbackTester() : called_(0) {}
38 
39   void Increment();
40   void IncrementAndUnblock(base::RunLoop* run_loop);
41   bool WasCalledExactlyOnce();
42 
43  private:
44   int called_;
45 };
46 
Increment()47 void CallbackTester::Increment() {
48   called_++;
49 }
50 
IncrementAndUnblock(base::RunLoop * run_loop)51 void CallbackTester::IncrementAndUnblock(base::RunLoop* run_loop) {
52   Increment();
53   run_loop->QuitWhenIdle();
54 }
55 
WasCalledExactlyOnce()56 bool CallbackTester::WasCalledExactlyOnce() {
57   return called_ == 1;
58 }
59 
60 }  // namespace
61 
62 class ChromeSigninClientTest : public testing::Test {
63  public:
ChromeSigninClientTest()64   ChromeSigninClientTest() {
65     // Create a signed-in profile.
66     TestingProfile::Builder builder;
67     profile_ = builder.Build();
68 
69     signin_client_ = ChromeSigninClientFactory::GetForProfile(profile());
70   }
71 
72  protected:
SetUpNetworkConnection(bool respond_synchronously,network::mojom::ConnectionType connection_type)73   void SetUpNetworkConnection(bool respond_synchronously,
74                               network::mojom::ConnectionType connection_type) {
75     auto* tracker = network::TestNetworkConnectionTracker::GetInstance();
76     tracker->SetRespondSynchronously(respond_synchronously);
77     tracker->SetConnectionType(connection_type);
78   }
79 
SetConnectionType(network::mojom::ConnectionType connection_type)80   void SetConnectionType(network::mojom::ConnectionType connection_type) {
81     network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
82         connection_type);
83   }
84 
profile()85   Profile* profile() { return profile_.get(); }
signin_client()86   SigninClient* signin_client() { return signin_client_; }
87 
88  private:
89   content::BrowserTaskEnvironment task_environment_;
90   std::unique_ptr<Profile> profile_;
91   SigninClient* signin_client_;
92 };
93 
TEST_F(ChromeSigninClientTest,DelayNetworkCallRunsImmediatelyWithNetwork)94 TEST_F(ChromeSigninClientTest, DelayNetworkCallRunsImmediatelyWithNetwork) {
95   SetUpNetworkConnection(true, network::mojom::ConnectionType::CONNECTION_3G);
96   CallbackTester tester;
97   signin_client()->DelayNetworkCall(
98       base::BindOnce(&CallbackTester::Increment, base::Unretained(&tester)));
99   ASSERT_TRUE(tester.WasCalledExactlyOnce());
100 }
101 
TEST_F(ChromeSigninClientTest,DelayNetworkCallRunsAfterGetConnectionType)102 TEST_F(ChromeSigninClientTest, DelayNetworkCallRunsAfterGetConnectionType) {
103   SetUpNetworkConnection(false, network::mojom::ConnectionType::CONNECTION_3G);
104 
105   base::RunLoop run_loop;
106   CallbackTester tester;
107   signin_client()->DelayNetworkCall(
108       base::BindOnce(&CallbackTester::IncrementAndUnblock,
109                      base::Unretained(&tester), &run_loop));
110   ASSERT_FALSE(tester.WasCalledExactlyOnce());
111   run_loop.Run();  // Wait for IncrementAndUnblock().
112   ASSERT_TRUE(tester.WasCalledExactlyOnce());
113 }
114 
TEST_F(ChromeSigninClientTest,DelayNetworkCallRunsAfterNetworkChange)115 TEST_F(ChromeSigninClientTest, DelayNetworkCallRunsAfterNetworkChange) {
116   SetUpNetworkConnection(true, network::mojom::ConnectionType::CONNECTION_NONE);
117 
118   base::RunLoop run_loop;
119   CallbackTester tester;
120   signin_client()->DelayNetworkCall(
121       base::BindOnce(&CallbackTester::IncrementAndUnblock,
122                      base::Unretained(&tester), &run_loop));
123 
124   ASSERT_FALSE(tester.WasCalledExactlyOnce());
125   SetConnectionType(network::mojom::ConnectionType::CONNECTION_3G);
126   run_loop.Run();  // Wait for IncrementAndUnblock().
127   ASSERT_TRUE(tester.WasCalledExactlyOnce());
128 }
129 
130 #if !defined(OS_ANDROID)
131 
132 class MockChromeSigninClient : public ChromeSigninClient {
133  public:
MockChromeSigninClient(Profile * profile)134   explicit MockChromeSigninClient(Profile* profile)
135       : ChromeSigninClient(profile) {}
136 
137   MOCK_METHOD1(ShowUserManager, void(const base::FilePath&));
138   MOCK_METHOD1(LockForceSigninProfile, void(const base::FilePath&));
139 
140   MOCK_METHOD3(SignOutCallback,
141                void(signin_metrics::ProfileSignout,
142                     signin_metrics::SignoutDelete,
143                     SigninClient::SignoutDecision signout_decision));
144 };
145 
146 class ChromeSigninClientSignoutTest : public BrowserWithTestWindowTest {
147  public:
SetUp()148   void SetUp() override {
149     BrowserWithTestWindowTest::SetUp();
150 
151     signin_util::SetForceSigninForTesting(true);
152     CreateClient(browser()->profile());
153   }
154 
TearDown()155   void TearDown() override {
156     BrowserWithTestWindowTest::TearDown();
157     TestingBrowserProcess::GetGlobal()->SetLocalState(nullptr);
158   }
159 
CreateClient(Profile * profile)160   void CreateClient(Profile* profile) {
161     client_ = std::make_unique<MockChromeSigninClient>(profile);
162   }
163 
PreSignOut(signin_metrics::ProfileSignout source_metric,signin_metrics::SignoutDelete delete_metric)164   void PreSignOut(signin_metrics::ProfileSignout source_metric,
165                   signin_metrics::SignoutDelete delete_metric) {
166     client_->PreSignOut(base::BindOnce(&MockChromeSigninClient::SignOutCallback,
167                                        base::Unretained(client_.get()),
168                                        source_metric, delete_metric),
169                         source_metric);
170   }
171 
172   std::unique_ptr<MockChromeSigninClient> client_;
173 };
174 
TEST_F(ChromeSigninClientSignoutTest,SignOut)175 TEST_F(ChromeSigninClientSignoutTest, SignOut) {
176   signin_metrics::ProfileSignout source_metric =
177       signin_metrics::ProfileSignout::USER_CLICKED_SIGNOUT_SETTINGS;
178   signin_metrics::SignoutDelete delete_metric =
179       signin_metrics::SignoutDelete::IGNORE_METRIC;
180 
181   EXPECT_CALL(*client_, ShowUserManager(browser()->profile()->GetPath()))
182       .Times(1);
183   EXPECT_CALL(*client_, LockForceSigninProfile(browser()->profile()->GetPath()))
184       .Times(1);
185   EXPECT_CALL(
186       *client_,
187       SignOutCallback(source_metric, delete_metric,
188                       SigninClient::SignoutDecision::ALLOW_SIGNOUT))
189       .Times(1);
190 
191   PreSignOut(source_metric, delete_metric);
192 }
193 
TEST_F(ChromeSigninClientSignoutTest,SignOutWithoutForceSignin)194 TEST_F(ChromeSigninClientSignoutTest, SignOutWithoutForceSignin) {
195   signin_util::SetForceSigninForTesting(false);
196   CreateClient(browser()->profile());
197 
198   signin_metrics::ProfileSignout source_metric =
199       signin_metrics::ProfileSignout::USER_CLICKED_SIGNOUT_SETTINGS;
200   signin_metrics::SignoutDelete delete_metric =
201       signin_metrics::SignoutDelete::IGNORE_METRIC;
202 
203   EXPECT_CALL(*client_, ShowUserManager(browser()->profile()->GetPath()))
204       .Times(0);
205   EXPECT_CALL(*client_, LockForceSigninProfile(browser()->profile()->GetPath()))
206       .Times(0);
207   EXPECT_CALL(
208       *client_,
209       SignOutCallback(source_metric, delete_metric,
210                       SigninClient::SignoutDecision::ALLOW_SIGNOUT))
211       .Times(1);
212   PreSignOut(source_metric, delete_metric);
213 }
214 
215 class ChromeSigninClientSignoutSourceTest
216     : public ::testing::WithParamInterface<signin_metrics::ProfileSignout>,
217       public ChromeSigninClientSignoutTest {};
218 
219 // Returns true if signout can be disallowed by policy for the given source.
IsSignoutDisallowedByPolicy(signin_metrics::ProfileSignout signout_source)220 bool IsSignoutDisallowedByPolicy(
221     signin_metrics::ProfileSignout signout_source) {
222   switch (signout_source) {
223     // NOTE: SIGNOUT_TEST == SIGNOUT_PREF_CHANGED.
224     case signin_metrics::ProfileSignout::SIGNOUT_PREF_CHANGED:
225     case signin_metrics::ProfileSignout::GOOGLE_SERVICE_NAME_PATTERN_CHANGED:
226     case signin_metrics::ProfileSignout::SIGNIN_PREF_CHANGED_DURING_SIGNIN:
227     case signin_metrics::ProfileSignout::USER_CLICKED_SIGNOUT_SETTINGS:
228     case signin_metrics::ProfileSignout::SERVER_FORCED_DISABLE:
229     case signin_metrics::ProfileSignout::TRANSFER_CREDENTIALS:
230     case signin_metrics::ProfileSignout::
231         AUTHENTICATION_FAILED_WITH_FORCE_SIGNIN:
232     case signin_metrics::ProfileSignout::SIGNIN_NOT_ALLOWED_ON_PROFILE_INIT:
233     case signin_metrics::ProfileSignout::USER_TUNED_OFF_SYNC_FROM_DICE_UI:
234       return true;
235     case signin_metrics::ProfileSignout::ACCOUNT_REMOVED_FROM_DEVICE:
236       // TODO(msarda): Add more of the above cases to this "false" branch.
237       // For now only ACCOUNT_REMOVED_FROM_DEVICE is here to preserve the status
238       // quo. Additional internal sources of sign-out will be moved here in a
239       // follow up CL.
240       return false;
241     case signin_metrics::ProfileSignout::ABORT_SIGNIN:
242       // Allow signout because data has not been synced yet.
243       return false;
244     case signin_metrics::ProfileSignout::FORCE_SIGNOUT_ALWAYS_ALLOWED_FOR_TEST:
245       // Allow signout for tests that want to force it.
246       return false;
247     case signin_metrics::ProfileSignout::USER_DELETED_ACCOUNT_COOKIES:
248     case signin_metrics::ProfileSignout::MOBILE_IDENTITY_CONSISTENCY_ROLLBACK:
249       // There's no special-casing for these in ChromeSigninClient, as they only
250       // happen when there's no sync account and policies aren't enforced.
251       // PrimaryAccountManager won't actually invoke PreSignOut in this case,
252       // thus it is fine for ChromeSigninClient to not have any special-casing.
253       return true;
254     case signin_metrics::ProfileSignout::NUM_PROFILE_SIGNOUT_METRICS:
255       NOTREACHED();
256       return false;
257   }
258 }
259 
TEST_P(ChromeSigninClientSignoutSourceTest,UserSignoutAllowed)260 TEST_P(ChromeSigninClientSignoutSourceTest, UserSignoutAllowed) {
261   signin_metrics::ProfileSignout signout_source = GetParam();
262 
263   TestingProfile::Builder builder;
264   builder.SetGuestSession();
265   std::unique_ptr<TestingProfile> profile = builder.Build();
266 
267   CreateClient(profile.get());
268   ASSERT_TRUE(signin_util::IsUserSignoutAllowedForProfile(profile.get()));
269 
270   // Verify IdentityManager gets callback indicating sign-out is always allowed.
271   signin_metrics::SignoutDelete delete_metric =
272       signin_metrics::SignoutDelete::IGNORE_METRIC;
273   EXPECT_CALL(
274       *client_,
275       SignOutCallback(signout_source, delete_metric,
276                       SigninClient::SignoutDecision::ALLOW_SIGNOUT))
277       .Times(1);
278 
279   PreSignOut(signout_source, delete_metric);
280 }
281 
282 #if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_CHROMEOS) || \
283     defined(OS_MAC)
TEST_P(ChromeSigninClientSignoutSourceTest,UserSignoutDisallowed)284 TEST_P(ChromeSigninClientSignoutSourceTest, UserSignoutDisallowed) {
285   signin_metrics::ProfileSignout signout_source = GetParam();
286 
287   TestingProfile::Builder builder;
288   builder.SetGuestSession();
289   std::unique_ptr<TestingProfile> profile = builder.Build();
290 
291   CreateClient(profile.get());
292 
293   ASSERT_TRUE(signin_util::IsUserSignoutAllowedForProfile(profile.get()));
294   signin_util::SetUserSignoutAllowedForProfile(profile.get(), false);
295   ASSERT_FALSE(signin_util::IsUserSignoutAllowedForProfile(profile.get()));
296 
297   // Verify IdentityManager gets callback indicating sign-out is disallowed iff
298   // the source of the sign-out is a user-action.
299   SigninClient::SignoutDecision signout_decision =
300       IsSignoutDisallowedByPolicy(signout_source)
301           ? SigninClient::SignoutDecision::DISALLOW_SIGNOUT
302           : SigninClient::SignoutDecision::ALLOW_SIGNOUT;
303   signin_metrics::SignoutDelete delete_metric =
304       signin_metrics::SignoutDelete::IGNORE_METRIC;
305   EXPECT_CALL(*client_,
306               SignOutCallback(signout_source, delete_metric, signout_decision))
307       .Times(1);
308 
309   PreSignOut(signout_source, delete_metric);
310 }
311 #endif
312 
313 const signin_metrics::ProfileSignout kSignoutSources[] = {
314     signin_metrics::ProfileSignout::SIGNOUT_PREF_CHANGED,
315     signin_metrics::ProfileSignout::GOOGLE_SERVICE_NAME_PATTERN_CHANGED,
316     signin_metrics::ProfileSignout::SIGNIN_PREF_CHANGED_DURING_SIGNIN,
317     signin_metrics::ProfileSignout::USER_CLICKED_SIGNOUT_SETTINGS,
318     signin_metrics::ProfileSignout::ABORT_SIGNIN,
319     signin_metrics::ProfileSignout::SERVER_FORCED_DISABLE,
320     signin_metrics::ProfileSignout::TRANSFER_CREDENTIALS,
321     signin_metrics::ProfileSignout::AUTHENTICATION_FAILED_WITH_FORCE_SIGNIN,
322     signin_metrics::ProfileSignout::USER_TUNED_OFF_SYNC_FROM_DICE_UI,
323     signin_metrics::ProfileSignout::ACCOUNT_REMOVED_FROM_DEVICE,
324     signin_metrics::ProfileSignout::SIGNIN_NOT_ALLOWED_ON_PROFILE_INIT,
325     signin_metrics::ProfileSignout::FORCE_SIGNOUT_ALWAYS_ALLOWED_FOR_TEST,
326     signin_metrics::ProfileSignout::USER_DELETED_ACCOUNT_COOKIES,
327     signin_metrics::ProfileSignout::MOBILE_IDENTITY_CONSISTENCY_ROLLBACK,
328 };
329 static_assert(base::size(kSignoutSources) ==
330                   signin_metrics::ProfileSignout::NUM_PROFILE_SIGNOUT_METRICS,
331               "kSignoutSources should enumerate all ProfileSignout values");
332 
333 INSTANTIATE_TEST_SUITE_P(AllSignoutSources,
334                          ChromeSigninClientSignoutSourceTest,
335                          testing::ValuesIn(kSignoutSources));
336 
337 #endif  // !defined(OS_ANDROID)
338 #endif  // !defined(OS_CHROMEOS)
339