| // Copyright 2015 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 "base/command_line.h" |
| #include "base/files/file_path.h" |
| #include "base/json/json_reader.h" |
| #include "base/json/json_writer.h" |
| #include "base/run_loop.h" |
| #include "base/strings/stringprintf.h" |
| #include "chrome/browser/chrome_notification_types.h" |
| #include "chrome/browser/chromeos/login/screens/gaia_view.h" |
| #include "chrome/browser/chromeos/login/test/fake_gaia_mixin.h" |
| #include "chrome/browser/chromeos/login/test/js_checker.h" |
| #include "chrome/browser/chromeos/login/test/oobe_base_test.h" |
| #include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h" |
| #include "chrome/browser/chromeos/login/ui/login_display_host.h" |
| #include "chrome/browser/chromeos/profiles/profile_helper.h" |
| #include "chrome/browser/signin/chrome_device_id_helper.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chromeos/constants/chromeos_switches.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/signin/core/browser/signin_pref_names.h" |
| #include "components/user_manager/known_user.h" |
| #include "components/user_manager/remove_user_delegate.h" |
| #include "components/user_manager/user_manager.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/test/test_utils.h" |
| |
| namespace { |
| |
| char kRefreshToken1[] = "refresh_token_1"; |
| char kRefreshToken2[] = "refresh_token_2"; |
| const base::FilePath::CharType kRefreshTokenToDeviceIdMapFile[] = |
| FILE_PATH_LITERAL("refrest_token_to_device_id.json"); |
| |
| char kSecondUserEmail[] = "second_user@gmail.com"; |
| char kSecondUserPassword[] = "password"; |
| char kSecondUserGaiaId[] = "4321"; |
| char kSecondUserRefreshToken1[] = "refresh_token_second_user_1"; |
| char kSecondUserRefreshToken2[] = "refresh_token_second_user_2"; |
| |
| } // namespace |
| |
| namespace chromeos { |
| |
| class DeviceIDTest : public OobeBaseTest, |
| public user_manager::RemoveUserDelegate { |
| public: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| OobeBaseTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitch(switches::kOobeSkipPostLogin); |
| } |
| |
| void SetUpOnMainThread() override { |
| user_removal_loop_.reset(new base::RunLoop); |
| OobeBaseTest::SetUpOnMainThread(); |
| LoadRefreshTokenToDeviceIdMap(); |
| } |
| |
| void TearDownOnMainThread() override { |
| SaveRefreshTokenToDeviceIdMap(); |
| OobeBaseTest::TearDownOnMainThread(); |
| } |
| |
| std::string GetDeviceId(const AccountId& account_id) { |
| return user_manager::known_user::GetDeviceId(account_id); |
| } |
| |
| std::string GetDeviceIdFromProfile(const AccountId& account_id) { |
| return GetSigninScopedDeviceIdForProfile( |
| ProfileHelper::Get()->GetProfileByUser( |
| user_manager::UserManager::Get()->FindUser(account_id))); |
| } |
| |
| std::string GetDeviceIdFromGAIA(const std::string& refresh_token) { |
| return fake_gaia_.fake_gaia()->GetDeviceIdByRefreshToken(refresh_token); |
| } |
| |
| // Checks that user's device ID retrieved from UserManager and Profile are the |
| // same. |
| // If |refresh_token| is not empty, checks that device ID associated with the |
| // |refresh_token| in GAIA is the same as ID saved on device. |
| void CheckDeviceIDIsConsistent(const AccountId& account_id, |
| const std::string& refresh_token) { |
| const std::string device_id_in_profile = GetDeviceIdFromProfile(account_id); |
| const std::string device_id_in_local_state = GetDeviceId(account_id); |
| |
| EXPECT_FALSE(device_id_in_profile.empty()); |
| EXPECT_EQ(device_id_in_profile, device_id_in_local_state); |
| |
| if (!refresh_token.empty()) { |
| const std::string device_id_in_gaia = GetDeviceIdFromGAIA(refresh_token); |
| EXPECT_EQ(device_id_in_profile, device_id_in_gaia); |
| } |
| } |
| |
| void WaitForSessionStart() { |
| content::WindowedNotificationObserver( |
| chrome::NOTIFICATION_SESSION_STARTED, |
| content::NotificationService::AllSources()) |
| .Wait(); |
| } |
| |
| void SignInOnline(const std::string& user_id, |
| const std::string& password, |
| const std::string& refresh_token, |
| const std::string& gaia_id) { |
| WaitForGaiaPageLoad(); |
| |
| FakeGaia::MergeSessionParams params; |
| params.email = user_id; |
| params.refresh_token = refresh_token; |
| fake_gaia_.fake_gaia()->UpdateMergeSessionParams(params); |
| fake_gaia_.fake_gaia()->MapEmailToGaiaId(user_id, gaia_id); |
| |
| LoginDisplayHost::default_host() |
| ->GetOobeUI() |
| ->GetGaiaScreenView() |
| ->ShowSigninScreenForTest(user_id, password, "[]"); |
| |
| WaitForSessionStart(); |
| } |
| |
| void SignInOffline(const std::string& user_id, const std::string& password) { |
| WaitForSigninScreen(); |
| |
| test::OobeJS().ExecuteAsync(base::StringPrintf( |
| "chrome.send('authenticateUser', ['%s', '%s', false])", user_id.c_str(), |
| password.c_str())); |
| WaitForSessionStart(); |
| } |
| |
| void RemoveUser(const AccountId& account_id) { |
| user_manager::UserManager::Get()->RemoveUser(account_id, this); |
| user_removal_loop_->Run(); |
| } |
| |
| private: |
| // user_manager::RemoveUserDelegate: |
| void OnBeforeUserRemoved(const AccountId& account_id) override {} |
| |
| void OnUserRemoved(const AccountId& account_id) override { |
| user_removal_loop_->Quit(); |
| } |
| |
| base::FilePath GetRefreshTokenToDeviceIdMapFilePath() const { |
| return base::CommandLine::ForCurrentProcess() |
| ->GetSwitchValuePath(::switches::kUserDataDir) |
| .Append(kRefreshTokenToDeviceIdMapFile); |
| } |
| |
| void LoadRefreshTokenToDeviceIdMap() { |
| std::string file_contents; |
| if (!base::ReadFileToString(GetRefreshTokenToDeviceIdMapFilePath(), |
| &file_contents)) |
| return; |
| std::unique_ptr<base::Value> value( |
| base::JSONReader::ReadDeprecated(file_contents)); |
| base::DictionaryValue* dictionary; |
| EXPECT_TRUE(value->GetAsDictionary(&dictionary)); |
| FakeGaia::RefreshTokenToDeviceIdMap map; |
| for (base::DictionaryValue::Iterator it(*dictionary); !it.IsAtEnd(); |
| it.Advance()) { |
| std::string device_id; |
| EXPECT_TRUE(it.value().GetAsString(&device_id)); |
| map[it.key()] = device_id; |
| } |
| fake_gaia_.fake_gaia()->SetRefreshTokenToDeviceIdMap(map); |
| } |
| |
| void SaveRefreshTokenToDeviceIdMap() { |
| base::DictionaryValue dictionary; |
| for (const auto& kv : |
| fake_gaia_.fake_gaia()->refresh_token_to_device_id_map()) |
| dictionary.SetKey(kv.first, base::Value(kv.second)); |
| std::string json; |
| EXPECT_TRUE(base::JSONWriter::Write(dictionary, &json)); |
| EXPECT_EQ(static_cast<int>(json.length()), |
| base::WriteFile(GetRefreshTokenToDeviceIdMapFilePath(), |
| json.c_str(), json.length())); |
| } |
| |
| std::unique_ptr<base::RunLoop> user_removal_loop_; |
| FakeGaiaMixin fake_gaia_{&mixin_host_, embedded_test_server()}; |
| }; |
| |
| // Add the first user and check that device ID is consistent. |
| IN_PROC_BROWSER_TEST_F(DeviceIDTest, PRE_PRE_PRE_PRE_PRE_NewUsers) { |
| SignInOnline(FakeGaiaMixin::kFakeUserEmail, FakeGaiaMixin::kFakeUserPassword, |
| kRefreshToken1, FakeGaiaMixin::kFakeUserGaiaId); |
| CheckDeviceIDIsConsistent( |
| AccountId::FromUserEmail(FakeGaiaMixin::kFakeUserEmail), kRefreshToken1); |
| } |
| |
| // Authenticate the first user through GAIA and verify that device ID remains |
| // the same. |
| IN_PROC_BROWSER_TEST_F(DeviceIDTest, PRE_PRE_PRE_PRE_NewUsers) { |
| const std::string device_id = |
| GetDeviceId(AccountId::FromUserEmail(FakeGaiaMixin::kFakeUserEmail)); |
| EXPECT_FALSE(device_id.empty()); |
| EXPECT_EQ(device_id, GetDeviceIdFromGAIA(kRefreshToken1)); |
| |
| SignInOnline(FakeGaiaMixin::kFakeUserEmail, FakeGaiaMixin::kFakeUserPassword, |
| kRefreshToken2, FakeGaiaMixin::kFakeUserGaiaId); |
| CheckDeviceIDIsConsistent( |
| AccountId::FromUserEmail(FakeGaiaMixin::kFakeUserEmail), kRefreshToken2); |
| |
| CHECK_EQ( |
| device_id, |
| GetDeviceId(AccountId::FromUserEmail(FakeGaiaMixin::kFakeUserEmail))); |
| } |
| |
| // Authenticate the first user offline and verify that device ID remains |
| // the same. |
| IN_PROC_BROWSER_TEST_F(DeviceIDTest, PRE_PRE_PRE_NewUsers) { |
| const std::string device_id = |
| GetDeviceId(AccountId::FromUserEmail(FakeGaiaMixin::kFakeUserEmail)); |
| EXPECT_FALSE(device_id.empty()); |
| |
| SignInOffline(FakeGaiaMixin::kFakeUserEmail, |
| FakeGaiaMixin::kFakeUserPassword); |
| CheckDeviceIDIsConsistent( |
| AccountId::FromUserEmail(FakeGaiaMixin::kFakeUserEmail), kRefreshToken2); |
| |
| // Verify that device ID remained the same after offline auth. |
| CHECK_EQ( |
| device_id, |
| GetDeviceId(AccountId::FromUserEmail(FakeGaiaMixin::kFakeUserEmail))); |
| } |
| |
| // Add the second user. |
| IN_PROC_BROWSER_TEST_F(DeviceIDTest, PRE_PRE_NewUsers) { |
| WaitForSigninScreen(); |
| test::OobeJS().ExecuteAsync("chrome.send('showAddUser')"); |
| SignInOnline(kSecondUserEmail, kSecondUserPassword, kSecondUserRefreshToken1, |
| kSecondUserGaiaId); |
| CheckDeviceIDIsConsistent(AccountId::FromUserEmail(kSecondUserEmail), |
| kSecondUserRefreshToken1); |
| } |
| |
| // Remove the second user. |
| IN_PROC_BROWSER_TEST_F(DeviceIDTest, PRE_NewUsers) { |
| WaitForSigninScreen(); |
| RemoveUser(AccountId::FromUserEmail(kSecondUserEmail)); |
| } |
| |
| // Add the second user back. Verify that device ID has been changed. |
| IN_PROC_BROWSER_TEST_F(DeviceIDTest, NewUsers) { |
| EXPECT_TRUE(GetDeviceId(AccountId::FromUserEmail(kSecondUserEmail)).empty()); |
| SignInOnline(kSecondUserEmail, kSecondUserPassword, kSecondUserRefreshToken2, |
| kSecondUserGaiaId); |
| CheckDeviceIDIsConsistent(AccountId::FromUserEmail(kSecondUserEmail), |
| kSecondUserRefreshToken2); |
| EXPECT_NE(GetDeviceIdFromGAIA(kSecondUserRefreshToken1), |
| GetDeviceId(AccountId::FromUserEmail(kSecondUserEmail))); |
| } |
| |
| // Set up a user that has a device ID stored in preference only. |
| IN_PROC_BROWSER_TEST_F(DeviceIDTest, PRE_Migration) { |
| SignInOnline(FakeGaiaMixin::kFakeUserEmail, FakeGaiaMixin::kFakeUserPassword, |
| kRefreshToken1, FakeGaiaMixin::kFakeUserGaiaId); |
| |
| // Simulate user that has device ID saved only in preferences (pre-M44). |
| PrefService* prefs = |
| ProfileHelper::Get() |
| ->GetProfileByUser(user_manager::UserManager::Get()->GetActiveUser()) |
| ->GetPrefs(); |
| prefs->SetString( |
| prefs::kGoogleServicesSigninScopedDeviceId, |
| GetDeviceId(AccountId::FromUserEmail(FakeGaiaMixin::kFakeUserEmail))); |
| |
| // Can't use SetKnownUserDeviceId here, because it forbids changing a device |
| // ID. |
| user_manager::known_user::SetStringPref( |
| AccountId::FromUserEmail(FakeGaiaMixin::kFakeUserEmail), "device_id", |
| std::string()); |
| } |
| |
| // Tests that after the first sign in the device ID has been moved to the Local |
| // state. |
| IN_PROC_BROWSER_TEST_F(DeviceIDTest, Migration) { |
| EXPECT_TRUE( |
| GetDeviceId(AccountId::FromUserEmail(FakeGaiaMixin::kFakeUserEmail)) |
| .empty()); |
| SignInOffline(FakeGaiaMixin::kFakeUserEmail, |
| FakeGaiaMixin::kFakeUserPassword); |
| CheckDeviceIDIsConsistent( |
| AccountId::FromUserEmail(FakeGaiaMixin::kFakeUserEmail), kRefreshToken1); |
| } |
| |
| // Set up a user that doesn't have a device ID. |
| IN_PROC_BROWSER_TEST_F(DeviceIDTest, PRE_LegacyUsers) { |
| SignInOnline(FakeGaiaMixin::kFakeUserEmail, FakeGaiaMixin::kFakeUserPassword, |
| kRefreshToken1, FakeGaiaMixin::kFakeUserGaiaId); |
| |
| PrefService* prefs = |
| ProfileHelper::Get() |
| ->GetProfileByUser(user_manager::UserManager::Get()->GetActiveUser()) |
| ->GetPrefs(); |
| EXPECT_TRUE( |
| prefs->GetString(prefs::kGoogleServicesSigninScopedDeviceId).empty()); |
| |
| // Can't use SetKnownUserDeviceId here, because it forbids changing a device |
| // ID. |
| user_manager::known_user::SetStringPref( |
| AccountId::FromUserEmail(FakeGaiaMixin::kFakeUserEmail), "device_id", |
| std::string()); |
| } |
| |
| // Tests that device ID has been generated after the first sign in. |
| IN_PROC_BROWSER_TEST_F(DeviceIDTest, LegacyUsers) { |
| EXPECT_TRUE( |
| GetDeviceId(AccountId::FromUserEmail(FakeGaiaMixin::kFakeUserEmail)) |
| .empty()); |
| SignInOffline(FakeGaiaMixin::kFakeUserEmail, |
| FakeGaiaMixin::kFakeUserPassword); |
| // Last param |auth_code| is empty, because we don't pass a device ID to GAIA |
| // in this case. |
| CheckDeviceIDIsConsistent( |
| AccountId::FromUserEmail(FakeGaiaMixin::kFakeUserEmail), std::string()); |
| } |
| |
| } // namespace chromeos |