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