1 // Copyright 2014 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/chromeos/login/signin/signin_error_notifier_ash.h"
6 
7 #include <stddef.h>
8 
9 #include <memory>
10 #include <string>
11 
12 #include "base/memory/ptr_util.h"
13 #include "base/stl_util.h"
14 #include "build/build_config.h"
15 #include "chrome/browser/browser_process.h"
16 #include "chrome/browser/chromeos/login/signin/signin_error_notifier_factory_ash.h"
17 #include "chrome/browser/chromeos/login/users/mock_user_manager.h"
18 #include "chrome/browser/notifications/notification_display_service_tester.h"
19 #include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
20 #include "chrome/browser/supervised_user/supervised_user_constants.h"
21 #include "chrome/browser/supervised_user/supervised_user_service.h"
22 #include "chrome/browser/supervised_user/supervised_user_service_factory.h"
23 #include "chrome/common/pref_names.h"
24 #include "chrome/test/base/browser_with_test_window_test.h"
25 #include "chrome/test/base/testing_profile.h"
26 #include "components/prefs/pref_service.h"
27 #include "components/signin/public/identity_manager/identity_test_environment.h"
28 #include "components/signin/public/identity_manager/identity_test_utils.h"
29 #include "components/user_manager/scoped_user_manager.h"
30 #include "testing/gtest/include/gtest/gtest.h"
31 #include "ui/message_center/public/cpp/notification.h"
32 
33 namespace {
34 
35 const char kTestEmail[] = "email@example.com";
36 const char kTestSecondaryEmail[] = "email2@example.com";
37 
38 // Notification ID corresponding to kProfileSigninNotificationId +
39 // kTestAccountId.
40 const char kPrimaryAccountErrorNotificationId[] =
41     "chrome://settings/signin/testing_profile";
42 const char kSecondaryAccountErrorNotificationId[] =
43     "chrome://settings/signin/testing_profile/secondary-account";
44 
45 class SigninErrorNotifierTest : public BrowserWithTestWindowTest {
46  public:
SetUp()47   void SetUp() override {
48     BrowserWithTestWindowTest::SetUp();
49 
50     mock_user_manager_ = new chromeos::MockUserManager();
51     user_manager_enabler_ = std::make_unique<user_manager::ScopedUserManager>(
52         base::WrapUnique(mock_user_manager_));
53 
54     SigninErrorNotifierFactory::GetForProfile(GetProfile());
55     display_service_ =
56         std::make_unique<NotificationDisplayServiceTester>(profile());
57 
58     identity_test_env_profile_adaptor_ =
59         std::make_unique<IdentityTestEnvironmentProfileAdaptor>(GetProfile());
60   }
61 
TearDown()62   void TearDown() override {
63     // Need to be destroyed before the profile associated to this test, which
64     // will be destroyed as part of the TearDown() process.
65     identity_test_env_profile_adaptor_.reset();
66 
67     BrowserWithTestWindowTest::TearDown();
68   }
69 
GetTestingFactories()70   TestingProfile::TestingFactories GetTestingFactories() override {
71     return IdentityTestEnvironmentProfileAdaptor::
72         GetIdentityTestEnvironmentFactories();
73   }
74 
SetAuthError(const CoreAccountId & account_id,const GoogleServiceAuthError & error)75   void SetAuthError(const CoreAccountId& account_id,
76                     const GoogleServiceAuthError& error) {
77     signin::UpdatePersistentErrorOfRefreshTokenForAccount(
78         identity_test_env()->identity_manager(), account_id, error);
79   }
80 
identity_test_env()81   signin::IdentityTestEnvironment* identity_test_env() {
82     return identity_test_env_profile_adaptor_->identity_test_env();
83   }
84 
85  protected:
86   std::unique_ptr<NotificationDisplayServiceTester> display_service_;
87   chromeos::MockUserManager* mock_user_manager_;  // Not owned.
88   std::unique_ptr<user_manager::ScopedUserManager> user_manager_enabler_;
89   std::unique_ptr<IdentityTestEnvironmentProfileAdaptor>
90       identity_test_env_profile_adaptor_;
91 };
92 
TEST_F(SigninErrorNotifierTest,NoNotification)93 TEST_F(SigninErrorNotifierTest, NoNotification) {
94   EXPECT_FALSE(
95       display_service_->GetNotification(kPrimaryAccountErrorNotificationId));
96   EXPECT_FALSE(
97       display_service_->GetNotification(kSecondaryAccountErrorNotificationId));
98 }
99 
100 // Verify that if Supervision has just been added for the current user
101 // the notification isn't shown.  This is because the Add Supervision
102 // flow itself will prompt the user to sign out, so the notification
103 // is unnecessary.
TEST_F(SigninErrorNotifierTest,NoNotificationAfterAddSupervisionEnabled)104 TEST_F(SigninErrorNotifierTest, NoNotificationAfterAddSupervisionEnabled) {
105   CoreAccountId account_id =
106       identity_test_env()->MakeAccountAvailable(kTestEmail).account_id;
107   identity_test_env()->SetPrimaryAccount(kTestEmail);
108 
109   // Mark signout required.
110   SupervisedUserService* service =
111       SupervisedUserServiceFactory::GetForProfile(profile());
112   service->set_signout_required_after_supervision_enabled();
113 
114   SetAuthError(
115       identity_test_env()->identity_manager()->GetPrimaryAccountId(),
116       GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
117 
118   EXPECT_FALSE(
119       display_service_->GetNotification(kPrimaryAccountErrorNotificationId));
120 }
121 
TEST_F(SigninErrorNotifierTest,ErrorResetForPrimaryAccount)122 TEST_F(SigninErrorNotifierTest, ErrorResetForPrimaryAccount) {
123   EXPECT_FALSE(
124       display_service_->GetNotification(kPrimaryAccountErrorNotificationId));
125 
126   CoreAccountId account_id =
127       identity_test_env()->MakePrimaryAccountAvailable(kTestEmail).account_id;
128   SetAuthError(
129       account_id,
130       GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
131   EXPECT_TRUE(
132       display_service_->GetNotification(kPrimaryAccountErrorNotificationId));
133 
134   SetAuthError(account_id, GoogleServiceAuthError::AuthErrorNone());
135   EXPECT_FALSE(
136       display_service_->GetNotification(kPrimaryAccountErrorNotificationId));
137 }
138 
TEST_F(SigninErrorNotifierTest,ErrorShownForUnconsentedPrimaryAccount)139 TEST_F(SigninErrorNotifierTest, ErrorShownForUnconsentedPrimaryAccount) {
140   EXPECT_FALSE(
141       display_service_->GetNotification(kPrimaryAccountErrorNotificationId));
142 
143   CoreAccountId account_id =
144       identity_test_env()
145           ->MakeUnconsentedPrimaryAccountAvailable(kTestEmail)
146           .account_id;
147   SetAuthError(
148       account_id,
149       GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
150   EXPECT_TRUE(
151       display_service_->GetNotification(kPrimaryAccountErrorNotificationId));
152 
153   SetAuthError(account_id, GoogleServiceAuthError::AuthErrorNone());
154   EXPECT_FALSE(
155       display_service_->GetNotification(kPrimaryAccountErrorNotificationId));
156 }
157 
TEST_F(SigninErrorNotifierTest,ErrorResetForSecondaryAccount)158 TEST_F(SigninErrorNotifierTest, ErrorResetForSecondaryAccount) {
159   EXPECT_FALSE(
160       display_service_->GetNotification(kSecondaryAccountErrorNotificationId));
161 
162   CoreAccountId account_id =
163       identity_test_env()->MakeAccountAvailable(kTestEmail).account_id;
164   SetAuthError(
165       account_id,
166       GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
167   // Uses the run loop from `BrowserTaskEnvironment`.
168   base::RunLoop().RunUntilIdle();
169   EXPECT_TRUE(
170       display_service_->GetNotification(kSecondaryAccountErrorNotificationId));
171 
172   SetAuthError(account_id, GoogleServiceAuthError::AuthErrorNone());
173   EXPECT_FALSE(
174       display_service_->GetNotification(kSecondaryAccountErrorNotificationId));
175 }
176 
TEST_F(SigninErrorNotifierTest,ErrorTransitionForPrimaryAccount)177 TEST_F(SigninErrorNotifierTest, ErrorTransitionForPrimaryAccount) {
178   CoreAccountId account_id =
179       identity_test_env()->MakePrimaryAccountAvailable(kTestEmail).account_id;
180   SetAuthError(
181       account_id,
182       GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
183 
184   base::Optional<message_center::Notification> notification =
185       display_service_->GetNotification(kPrimaryAccountErrorNotificationId);
186   ASSERT_TRUE(notification);
187   base::string16 message = notification->message();
188   EXPECT_FALSE(message.empty());
189 
190   // Now set another auth error.
191   SetAuthError(account_id,
192                GoogleServiceAuthError(
193                    GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE));
194 
195   notification =
196       display_service_->GetNotification(kPrimaryAccountErrorNotificationId);
197   ASSERT_TRUE(notification);
198   base::string16 new_message = notification->message();
199   EXPECT_FALSE(new_message.empty());
200 
201   ASSERT_NE(new_message, message);
202 }
203 
204 // Verify that SigninErrorNotifier ignores certain errors.
TEST_F(SigninErrorNotifierTest,AuthStatusEnumerateAllErrors)205 TEST_F(SigninErrorNotifierTest, AuthStatusEnumerateAllErrors) {
206   typedef struct {
207     GoogleServiceAuthError::State error_state;
208     bool is_error;
209   } ErrorTableEntry;
210 
211   ErrorTableEntry table[] = {
212       {GoogleServiceAuthError::NONE, false},
213       {GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS, true},
214       {GoogleServiceAuthError::USER_NOT_SIGNED_UP, true},
215       {GoogleServiceAuthError::CONNECTION_FAILED, false},
216       {GoogleServiceAuthError::SERVICE_UNAVAILABLE, false},
217       {GoogleServiceAuthError::REQUEST_CANCELED, false},
218       {GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE, true},
219       {GoogleServiceAuthError::SERVICE_ERROR, true},
220   };
221   static_assert(
222       base::size(table) == GoogleServiceAuthError::NUM_STATES -
223                                GoogleServiceAuthError::kDeprecatedStateCount,
224       "table size should match number of auth error types");
225   CoreAccountId account_id =
226       identity_test_env()->MakePrimaryAccountAvailable(kTestEmail).account_id;
227 
228   for (size_t i = 0; i < base::size(table); ++i) {
229     SetAuthError(account_id, GoogleServiceAuthError(table[i].error_state));
230     base::Optional<message_center::Notification> notification =
231         display_service_->GetNotification(kPrimaryAccountErrorNotificationId);
232     ASSERT_EQ(table[i].is_error, !!notification) << "Failed case #" << i;
233     if (table[i].is_error) {
234       EXPECT_FALSE(notification->title().empty());
235       EXPECT_FALSE(notification->message().empty());
236       EXPECT_EQ((size_t)1, notification->buttons().size());
237     }
238     SetAuthError(account_id, GoogleServiceAuthError::AuthErrorNone());
239   }
240 }
241 
TEST_F(SigninErrorNotifierTest,ChildSecondaryAccountMigrationTest)242 TEST_F(SigninErrorNotifierTest, ChildSecondaryAccountMigrationTest) {
243   CoreAccountId primary_account =
244       identity_test_env()->MakePrimaryAccountAvailable(kTestEmail).account_id;
245   CoreAccountId secondary_account =
246       identity_test_env()->MakeAccountAvailable(kTestSecondaryEmail).account_id;
247 
248   // Mark the profile as a child user.
249   GetProfile()->SetSupervisedUserId(supervised_users::kChildAccountSUID);
250   base::RunLoop().RunUntilIdle();
251 
252   // Invalidate the secondary account.
253   SetAuthError(
254       secondary_account,
255       GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
256 
257   // Expect that there is a notification, accounts didn't migrate yet.
258   base::Optional<message_center::Notification> notification =
259       display_service_->GetNotification(kSecondaryAccountErrorNotificationId);
260   ASSERT_TRUE(notification);
261   base::string16 message = notification->message();
262   EXPECT_FALSE(message.empty());
263 
264   // Clear error.
265   SetAuthError(secondary_account, GoogleServiceAuthError::AuthErrorNone());
266   EXPECT_FALSE(
267       display_service_->GetNotification(kSecondaryAccountErrorNotificationId));
268 
269   // Mark secondary account as migrated, message should be different.
270   profile()->GetPrefs()->SetBoolean(prefs::kEduCoexistenceArcMigrationCompleted,
271                                     true);
272 
273   // Invalidate the secondary account.
274   SetAuthError(
275       secondary_account,
276       GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
277   notification =
278       display_service_->GetNotification(kSecondaryAccountErrorNotificationId);
279   ASSERT_TRUE(notification);
280   base::string16 new_message = notification->message();
281   EXPECT_NE(new_message, message);
282 }
283 
284 }  // namespace
285