Commit 78ff0e21 authored by Maciek Slusarczyk's avatar Maciek Slusarczyk Committed by Commit Bot

Password sync token client implementation.

A new type of token API is introduced to sync password between multiple
ChromeOS devices. On a password change event createToken operation will
invalidate the old token, create and fetch a new one. CrOS devices will
monitor the state of a global token by sending verifyToken requests
and, when local token copy will be identfied as invalid, initiate
online re-auth flow to sync the credentials.

This cl implements notifications to InSessionPasswordSyncManager object
when online login has to be triggered.

Bug: 1090341
Change-Id: I14407e37ad05cc7aaf697634fcd76f005a7613b5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2316106
Commit-Queue: Maciek Slusarczyk <mslus@chromium.org>
Reviewed-by: default avatarDenis Kuznetsov [CET] <antrim@chromium.org>
Reviewed-by: default avatarRoman Sorokin [CET] <rsorokin@chromium.org>
Reviewed-by: default avatarMaksim Ivanov <emaxx@chromium.org>
Reviewed-by: default avatarXiyuan Xia <xiyuan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#795913}
parent 46120d20
......@@ -1561,6 +1561,10 @@ source_set("chromeos") {
"login/saml/password_expiry_notification.h",
"login/saml/password_sync_token_fetcher.cc",
"login/saml/password_sync_token_fetcher.h",
"login/saml/password_sync_token_verifier.cc",
"login/saml/password_sync_token_verifier.h",
"login/saml/password_sync_token_verifier_factory.cc",
"login/saml/password_sync_token_verifier_factory.h",
"login/saml/public_saml_url_fetcher.cc",
"login/saml/public_saml_url_fetcher.h",
"login/saml/saml_metric_utils.cc",
......@@ -3223,6 +3227,7 @@ source_set("unit_tests") {
"login/saml/mock_lock_handler.cc",
"login/saml/mock_lock_handler.h",
"login/saml/password_expiry_notification_unittest.cc",
"login/saml/password_sync_token_verifier_unittest.cc",
"login/saml/saml_offline_signin_limiter_unittest.cc",
"login/screens/network_screen_unittest.cc",
"login/screens/recommend_apps/recommend_apps_fetcher_impl_unittest.cc",
......
......@@ -5,9 +5,8 @@
#include "chrome/browser/chromeos/login/saml/in_session_password_sync_manager.h"
#include "base/time/default_clock.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part_chromeos.h"
#include "chrome/browser/chromeos/login/lock/screen_locker.h"
#include "chrome/browser/chromeos/login/saml/password_sync_token_fetcher.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/common/pref_names.h"
#include "chromeos/components/proximity_auth/screenlock_bridge.h"
......@@ -17,6 +16,7 @@
#include "components/session_manager/core/session_manager_observer.h"
#include "components/user_manager/known_user.h"
#include "components/user_manager/user_manager_base.h"
#include "content/public/browser/storage_partition.h"
namespace chromeos {
......@@ -50,11 +50,19 @@ bool InSessionPasswordSyncManager::IsLockReauthEnabled() {
return prefs->GetBoolean(prefs::kSamlLockScreenReauthenticationEnabled);
}
void InSessionPasswordSyncManager::MaybeForceReauthOnLockScreen() {
if (enforce_reauth_on_lock_) {
void InSessionPasswordSyncManager::MaybeForceReauthOnLockScreen(
ReauthenticationReason reauth_reason) {
if (lock_screen_reauth_reason_ == ReauthenticationReason::kInvalidToken) {
// Re-authentication already enforced, no other action is needed.
return;
}
if (lock_screen_reauth_reason_ == ReauthenticationReason::kPolicy &&
reauth_reason == ReauthenticationReason::kInvalidToken) {
// Re-authentication already enforced but need to reset it to trigger token
// update. No other action is needed.
lock_screen_reauth_reason_ = reauth_reason;
return;
}
if (!primary_user_->force_online_signin()) {
// force_online_signin flag is not set - do not update the screen.
return;
......@@ -65,7 +73,7 @@ void InSessionPasswordSyncManager::MaybeForceReauthOnLockScreen() {
primary_user_->GetAccountId(),
proximity_auth::mojom::AuthType::ONLINE_SIGN_IN, base::string16());
}
enforce_reauth_on_lock_ = true;
lock_screen_reauth_reason_ = reauth_reason;
}
void InSessionPasswordSyncManager::OnAuthSucceeded(
......@@ -80,7 +88,11 @@ void InSessionPasswordSyncManager::OnAuthSucceeded(
}
UpdateOnlineAuth();
enforce_reauth_on_lock_ = false;
if (lock_screen_reauth_reason_ == ReauthenticationReason::kInvalidToken) {
FetchTokenAsync();
} else {
lock_screen_reauth_reason_ = ReauthenticationReason::kNone;
}
if (screenlock_bridge_->IsLocked()) {
screenlock_bridge_->lock_handler()->Unlock(user_context.GetAccountId());
}
......@@ -98,7 +110,7 @@ void InSessionPasswordSyncManager::OnSessionStateChanged() {
// We are unlocking the session, no further action required.
return;
}
if (!enforce_reauth_on_lock_) {
if (lock_screen_reauth_reason_ == ReauthenticationReason::kNone) {
// locking the session but no re-auth flag set - show standard UI.
return;
}
......@@ -120,4 +132,45 @@ void InSessionPasswordSyncManager::UpdateOnlineAuth() {
now);
}
void InSessionPasswordSyncManager::CreateTokenAsync() {
password_sync_token_fetcher_ = std::make_unique<PasswordSyncTokenFetcher>(
primary_profile_->GetURLLoaderFactory(), primary_profile_, this);
password_sync_token_fetcher_->StartTokenCreate();
}
void InSessionPasswordSyncManager::OnTokenCreated(const std::string& token) {
user_manager::known_user::SetPasswordSyncToken(primary_user_->GetAccountId(),
token);
lock_screen_reauth_reason_ = ReauthenticationReason::kNone;
}
void InSessionPasswordSyncManager::FetchTokenAsync() {
password_sync_token_fetcher_ = std::make_unique<PasswordSyncTokenFetcher>(
primary_profile_->GetURLLoaderFactory(), primary_profile_, this);
password_sync_token_fetcher_->StartTokenGet();
}
void InSessionPasswordSyncManager::OnTokenFetched(const std::string& token) {
if (!token.empty()) {
// Set token fetched from the endpoint.
user_manager::known_user::SetPasswordSyncToken(
primary_user_->GetAccountId(), token);
lock_screen_reauth_reason_ = ReauthenticationReason::kNone;
} else {
// This is the first time a sync token is created for the user: we need to
// initialize its value by calling the API and store it locally.
CreateTokenAsync();
}
}
void InSessionPasswordSyncManager::OnTokenVerified(bool is_valid) {
// InSessionPasswordSyncManager does not verify the sync token.
}
void InSessionPasswordSyncManager::OnApiCallFailed(
PasswordSyncTokenFetcher::ErrorType error_type) {
// Ignore API errors since they are logged by TokenFetcher and will be
// re-tried after the next verify interval.
}
} // namespace chromeos
......@@ -11,6 +11,7 @@
#include "base/observer_list.h"
#include "base/time/clock.h"
#include "base/time/time.h"
#include "chrome/browser/chromeos/login/saml/password_sync_token_fetcher.h"
#include "chrome/browser/profiles/profile.h"
#include "chromeos/components/proximity_auth/screenlock_bridge.h"
#include "components/account_id/account_id.h"
......@@ -34,8 +35,18 @@ class UserContext;
// policy is set.
class InSessionPasswordSyncManager
: public KeyedService,
public session_manager::SessionManagerObserver {
public session_manager::SessionManagerObserver,
public PasswordSyncTokenFetcher::Consumer {
public:
enum class ReauthenticationReason {
kNone,
// Enforced by the timeout set in SAMLOfflineSigninTimeLimit policy.
kPolicy,
// Enforced by mismatch between sync token API endpoint and the local copy
// of the token.
kInvalidToken
};
explicit InSessionPasswordSyncManager(Profile* primary_profile);
~InSessionPasswordSyncManager() override;
......@@ -53,7 +64,7 @@ class InSessionPasswordSyncManager
// Sets online re-auth on lock flag and changes the UI to online
// re-auth when called on the lock screen.
void MaybeForceReauthOnLockScreen();
void MaybeForceReauthOnLockScreen(ReauthenticationReason reauth_reason);
// Set special clock for testing.
void SetClockForTesting(const base::Clock* clock);
......@@ -64,17 +75,30 @@ class InSessionPasswordSyncManager
// session_manager::SessionManagerObserver::
void OnSessionStateChanged() override;
// PasswordSyncTokenFetcher::Consumer
void OnTokenCreated(const std::string& sync_token) override;
void OnTokenFetched(const std::string& sync_token) override;
void OnTokenVerified(bool is_valid) override;
void OnApiCallFailed(PasswordSyncTokenFetcher::ErrorType error_type) override;
private:
void UpdateOnlineAuth();
// Password sync token API calls.
void CreateTokenAsync();
void FetchTokenAsync();
Profile* const primary_profile_;
const base::Clock* clock_;
const user_manager::User* const primary_user_;
bool enforce_reauth_on_lock_ = false;
ReauthenticationReason lock_screen_reauth_reason_ =
ReauthenticationReason::kNone;
proximity_auth::ScreenlockBridge* screenlock_bridge_;
std::unique_ptr<PasswordSyncTokenFetcher> password_sync_token_fetcher_;
friend class InSessionPasswordSyncManagerTest;
friend class InSessionPasswordSyncManagerFactory;
base::WeakPtrFactory<InSessionPasswordSyncManager> weak_factory_{this};
};
} // namespace chromeos
......
......@@ -63,7 +63,7 @@ class InSessionPasswordSyncManagerTest : public testing::Test {
void CreateInSessionSyncManager();
void DestroyInSessionSyncManager();
bool InSessionReauthFlagSet();
InSessionPasswordSyncManager::ReauthenticationReason InSessionReauthReason();
void LockScreen();
void UnlockScreen();
......@@ -145,8 +145,9 @@ void InSessionPasswordSyncManagerTest::UnlockScreen() {
proximity_auth::ScreenlockBridge::Get()->SetLockHandler(nullptr);
}
bool InSessionPasswordSyncManagerTest::InSessionReauthFlagSet() {
return manager_->enforce_reauth_on_lock_;
InSessionPasswordSyncManager::ReauthenticationReason
InSessionPasswordSyncManagerTest::InSessionReauthReason() {
return manager_->lock_screen_reauth_reason_;
}
TEST_F(InSessionPasswordSyncManagerTest, ReauthenticateSetInSession) {
......@@ -155,8 +156,25 @@ TEST_F(InSessionPasswordSyncManagerTest, ReauthenticateSetInSession) {
CreateInSessionSyncManager();
UnlockScreen();
user_manager_->SaveForceOnlineSignin(saml_login_account_id1_, true);
manager_->MaybeForceReauthOnLockScreen();
EXPECT_TRUE(InSessionReauthFlagSet());
manager_->MaybeForceReauthOnLockScreen(
InSessionPasswordSyncManager::ReauthenticationReason::kPolicy);
EXPECT_EQ(InSessionReauthReason(),
InSessionPasswordSyncManager::ReauthenticationReason::kPolicy);
}
TEST_F(InSessionPasswordSyncManagerTest, ReauthenticateResetByToken) {
primary_profile_->GetPrefs()->SetBoolean(
prefs::kSamlLockScreenReauthenticationEnabled, true);
CreateInSessionSyncManager();
UnlockScreen();
user_manager_->SaveForceOnlineSignin(saml_login_account_id1_, true);
manager_->MaybeForceReauthOnLockScreen(
InSessionPasswordSyncManager::ReauthenticationReason::kPolicy);
manager_->MaybeForceReauthOnLockScreen(
InSessionPasswordSyncManager::ReauthenticationReason::kInvalidToken);
EXPECT_EQ(
InSessionReauthReason(),
InSessionPasswordSyncManager::ReauthenticationReason::kInvalidToken);
}
TEST_F(InSessionPasswordSyncManagerTest, ReauthenticateSetOnLock) {
......@@ -170,8 +188,10 @@ TEST_F(InSessionPasswordSyncManagerTest, ReauthenticateSetOnLock) {
base::string16()))
.Times(1);
user_manager_->SaveForceOnlineSignin(saml_login_account_id1_, true);
manager_->MaybeForceReauthOnLockScreen();
EXPECT_TRUE(InSessionReauthFlagSet());
manager_->MaybeForceReauthOnLockScreen(
InSessionPasswordSyncManager::ReauthenticationReason::kPolicy);
EXPECT_EQ(InSessionReauthReason(),
InSessionPasswordSyncManager::ReauthenticationReason::kPolicy);
}
// User tries to unlock the screen using valid SAML credentials but not for the
......@@ -189,12 +209,15 @@ TEST_F(InSessionPasswordSyncManagerTest, AuthenticateWithIncorrectUser) {
.Times(1);
EXPECT_CALL(*lock_handler_, Unlock(saml_login_account_id1_)).Times(0);
user_manager_->SaveForceOnlineSignin(saml_login_account_id1_, true);
manager_->MaybeForceReauthOnLockScreen();
EXPECT_TRUE(InSessionReauthFlagSet());
manager_->MaybeForceReauthOnLockScreen(
InSessionPasswordSyncManager::ReauthenticationReason::kPolicy);
EXPECT_EQ(InSessionReauthReason(),
InSessionPasswordSyncManager::ReauthenticationReason::kPolicy);
UserContext user_context(user_manager::USER_TYPE_REGULAR,
saml_login_account_id2_);
manager_->OnAuthSucceeded(user_context);
EXPECT_TRUE(InSessionReauthFlagSet());
EXPECT_EQ(InSessionReauthReason(),
InSessionPasswordSyncManager::ReauthenticationReason::kPolicy);
EXPECT_TRUE(proximity_auth::ScreenlockBridge::Get()->IsLocked());
}
......@@ -217,12 +240,15 @@ TEST_F(InSessionPasswordSyncManagerTest, AuthenticateWithCorrectUser) {
EXPECT_CALL(*lock_handler_, Unlock(saml_login_account_id1_)).Times(1);
user_manager_->SaveForceOnlineSignin(saml_login_account_id1_, true);
test_environment_.FastForwardBy(kSamlOnlineShortDelay);
manager_->MaybeForceReauthOnLockScreen();
EXPECT_TRUE(InSessionReauthFlagSet());
manager_->MaybeForceReauthOnLockScreen(
InSessionPasswordSyncManager::ReauthenticationReason::kPolicy);
EXPECT_EQ(InSessionReauthReason(),
InSessionPasswordSyncManager::ReauthenticationReason::kPolicy);
UserContext user_context(user_manager::USER_TYPE_REGULAR,
saml_login_account_id1_);
manager_->OnAuthSucceeded(user_context);
EXPECT_FALSE(InSessionReauthFlagSet());
EXPECT_EQ(InSessionReauthReason(),
InSessionPasswordSyncManager::ReauthenticationReason::kNone);
now = user_manager::known_user::GetLastOnlineSignin(saml_login_account_id1_);
EXPECT_EQ(now, expected_signin_time);
}
......
// Copyright 2020 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 "chrome/browser/chromeos/login/saml/password_sync_token_verifier.h"
#include "base/task/post_task.h"
#include "chrome/browser/chromeos/login/saml/in_session_password_sync_manager.h"
#include "chrome/browser/chromeos/login/saml/in_session_password_sync_manager_factory.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/common/pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/known_user.h"
#include "content/public/browser/storage_partition.h"
namespace chromeos {
namespace {
const char dummy_token[] = "dummy-token";
}
const net::BackoffEntry::Policy
PasswordSyncTokenVerifier::kFetchTokenRetryBackoffPolicy = {
0, // Number of initial errors to ignore.
5 * 60 * 1000, // Initial request delay in ms.
2.0, // Factor by which the waiting time will be multiplied.
0.1, // Fuzzing percentage.
6 * 60 * 60 * 1000, // Maximum request delay in ms.
-1, // Never discard the entry.
true, // Don't use initial delay unless last request was an error.
};
PasswordSyncTokenVerifier::PasswordSyncTokenVerifier(Profile* primary_profile)
: primary_profile_(primary_profile),
primary_user_(ProfileHelper::Get()->GetUserByProfile(primary_profile)),
retry_backoff_(&kFetchTokenRetryBackoffPolicy) {
DCHECK(primary_profile_);
DCHECK(primary_user_);
}
PasswordSyncTokenVerifier::~PasswordSyncTokenVerifier() = default;
void PasswordSyncTokenVerifier::RecheckAfter(base::TimeDelta delay) {
CancelPendingChecks();
base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&PasswordSyncTokenVerifier::CheckForPasswordNotInSync,
weak_ptr_factory_.GetWeakPtr()),
delay);
}
void PasswordSyncTokenVerifier::CheckForPasswordNotInSync() {
// In-session password change is as of now the only way to trigger the sync
// token update. We do not need to poll if this feature is not enabled.
PrefService* prefs = primary_profile_->GetPrefs();
if (!prefs->GetBoolean(prefs::kSamlInSessionPasswordChangeEnabled)) {
return;
}
// Get current sync token for primary_user_.
std::string sync_token = user_manager::known_user::GetPasswordSyncToken(
primary_user_->GetAccountId());
// No local sync token on the device - create it by sending user through the
// online re-auth.
if (sync_token.empty())
sync_token = dummy_token;
password_sync_token_fetcher_ = std::make_unique<PasswordSyncTokenFetcher>(
primary_profile_->GetURLLoaderFactory(), primary_profile_, this);
password_sync_token_fetcher_->StartTokenVerify(sync_token);
}
void PasswordSyncTokenVerifier::CancelPendingChecks() {
// We should not have any active request at this point. DCHECK makes sure it
// is really the case for the dev build. In a release build InvalidateWeakPtrs
// helps to recover by cancelling potential existing requests.
DCHECK(!weak_ptr_factory_.HasWeakPtrs());
weak_ptr_factory_.InvalidateWeakPtrs();
}
void PasswordSyncTokenVerifier::OnTokenCreated(const std::string& sync_token) {}
void PasswordSyncTokenVerifier::OnTokenFetched(const std::string& sync_token) {}
void PasswordSyncTokenVerifier::OnTokenVerified(bool is_valid) {
retry_backoff_.InformOfRequest(true);
// Schedule next token check after base interval.
RecheckAfter(retry_backoff_.GetTimeUntilRelease());
if (is_valid)
return;
user_manager::UserManager::Get()->SaveForceOnlineSignin(
primary_user_->GetAccountId(), true);
// Re-auth on lock.
InSessionPasswordSyncManager* password_sync_manager =
InSessionPasswordSyncManagerFactory::GetForProfile(primary_profile_);
if (password_sync_manager && password_sync_manager->IsLockReauthEnabled()) {
password_sync_manager->MaybeForceReauthOnLockScreen(
InSessionPasswordSyncManager::ReauthenticationReason::kInvalidToken);
}
}
void PasswordSyncTokenVerifier::OnApiCallFailed(
PasswordSyncTokenFetcher::ErrorType error_type) {
retry_backoff_.InformOfRequest(false);
// Schedule next token check with interval calculated with exponential
// backoff.
RecheckAfter(retry_backoff_.GetTimeUntilRelease());
}
} // namespace chromeos
// Copyright 2020 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 CHROME_BROWSER_CHROMEOS_LOGIN_SAML_PASSWORD_SYNC_TOKEN_VERIFIER_H_
#define CHROME_BROWSER_CHROMEOS_LOGIN_SAML_PASSWORD_SYNC_TOKEN_VERIFIER_H_
#include <memory>
#include <string>
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "chrome/browser/chromeos/login/saml/password_sync_token_fetcher.h"
#include "chrome/browser/profiles/profile.h"
#include "components/account_id/account_id.h"
#include "components/keyed_service/core/keyed_service.h"
#include "net/base/backoff_entry.h"
class Profile;
namespace user_manager {
class User;
}
namespace chromeos {
// Verifies local copy of the password sync token by executing API call. If
// token is invalid calls InSessionPasswordSyncManager to request online re-auth
// that will sync the password and update the token.
class PasswordSyncTokenVerifier : public KeyedService,
public PasswordSyncTokenFetcher::Consumer {
public:
// Backoff policy for token fetch retry attempts in case token fetch failed or
// returned invalid data.
static const net::BackoffEntry::Policy kFetchTokenRetryBackoffPolicy;
explicit PasswordSyncTokenVerifier(Profile* primary_profile);
~PasswordSyncTokenVerifier() override;
PasswordSyncTokenVerifier(const PasswordSyncTokenVerifier&) = delete;
PasswordSyncTokenVerifier& operator=(const PasswordSyncTokenVerifier&) =
delete;
// Execute verification API call.
void CheckForPasswordNotInSync();
// Cancel all pending check requests.
void CancelPendingChecks();
// PasswordSyncTokenFetcher::Consumer
void OnTokenCreated(const std::string& sync_token) override;
void OnTokenFetched(const std::string& sync_token) override;
void OnTokenVerified(bool is_valid) override;
void OnApiCallFailed(PasswordSyncTokenFetcher::ErrorType error_type) override;
private:
// Recheck after given |delay|.
void RecheckAfter(base::TimeDelta delay);
Profile* const primary_profile_;
const user_manager::User* const primary_user_;
std::unique_ptr<PasswordSyncTokenFetcher> password_sync_token_fetcher_;
net::BackoffEntry retry_backoff_;
base::WeakPtrFactory<PasswordSyncTokenVerifier> weak_ptr_factory_{this};
friend class PasswordSyncTokenVerifierTest;
};
} // namespace chromeos
#endif // CHROME_BROWSER_CHROMEOS_LOGIN_SAML_PASSWORD_SYNC_TOKEN_VERIFIER_H_
// Copyright 2020 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 "chrome/browser/chromeos/login/saml/password_sync_token_verifier_factory.h"
#include "chrome/browser/chromeos/login/saml/in_session_password_sync_manager_factory.h"
#include "chrome/browser/chromeos/login/saml/password_sync_token_verifier.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/browser_context.h"
namespace chromeos {
// static
PasswordSyncTokenVerifierFactory*
PasswordSyncTokenVerifierFactory::GetInstance() {
return base::Singleton<PasswordSyncTokenVerifierFactory>::get();
}
// static
PasswordSyncTokenVerifier* PasswordSyncTokenVerifierFactory::GetForProfile(
Profile* profile) {
return static_cast<PasswordSyncTokenVerifier*>(
GetInstance()->GetServiceForBrowserContext(profile, true));
}
PasswordSyncTokenVerifierFactory::PasswordSyncTokenVerifierFactory()
: BrowserContextKeyedServiceFactory(
"PasswordSyncTokenVerifier",
BrowserContextDependencyManager::GetInstance()) {
DependsOn(InSessionPasswordSyncManagerFactory::GetInstance());
}
PasswordSyncTokenVerifierFactory::~PasswordSyncTokenVerifierFactory() = default;
KeyedService* PasswordSyncTokenVerifierFactory::BuildServiceInstanceFor(
content::BrowserContext* context) const {
Profile* profile = Profile::FromBrowserContext(context);
user_manager::User* user = ProfileHelper::Get()->GetUserByProfile(profile);
// PasswordSyncTokenVerifier should be created for the primary user only.
if (!ProfileHelper::IsPrimaryProfile(profile) || !user ||
!user->using_saml()) {
return nullptr;
}
return new PasswordSyncTokenVerifier(profile);
}
} // namespace chromeos
// Copyright 2020 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 CHROME_BROWSER_CHROMEOS_LOGIN_SAML_PASSWORD_SYNC_TOKEN_VERIFIER_FACTORY_H_
#define CHROME_BROWSER_CHROMEOS_LOGIN_SAML_PASSWORD_SYNC_TOKEN_VERIFIER_FACTORY_H_
#include "base/memory/singleton.h"
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
class Profile;
namespace chromeos {
class PasswordSyncTokenVerifier;
// Singleton that owns all PasswordSyncTokenVerifiers and associates them
// with Profiles.
class PasswordSyncTokenVerifierFactory
: public BrowserContextKeyedServiceFactory {
public:
static PasswordSyncTokenVerifierFactory* GetInstance();
static PasswordSyncTokenVerifier* GetForProfile(Profile* profile);
private:
friend struct base::DefaultSingletonTraits<PasswordSyncTokenVerifierFactory>;
PasswordSyncTokenVerifierFactory();
~PasswordSyncTokenVerifierFactory() override;
// BrowserContextKeyedServiceFactory:
KeyedService* BuildServiceInstanceFor(
content::BrowserContext* context) const override;
};
} // namespace chromeos
#endif // CHROME_BROWSER_CHROMEOS_LOGIN_SAML_PASSWORD_SYNC_TOKEN_VERIFIER_FACTORY_H_
// Copyright 2020 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 "chrome/browser/chromeos/login/saml/password_sync_token_verifier.h"
#include "base/time/default_clock.h"
#include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/chromeos/login/users/mock_user_manager.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "chromeos/login/auth/user_context.h"
#include "components/user_manager/known_user.h"
#include "components/user_manager/scoped_user_manager.h"
#include "components/user_manager/user_names.h"
#include "content/public/test/browser_task_environment.h"
namespace chromeos {
namespace {
const char kSAMLUserId1[] = "12345";
const char kSAMLUserEmail1[] = "alice@corp.example.com";
const char kSyncToken[] = "sync-token-1";
constexpr base::TimeDelta kSyncTokenCheckInterval =
base::TimeDelta::FromMinutes(6);
constexpr base::TimeDelta kSyncTokenCheckBelowInterval =
base::TimeDelta::FromMinutes(4);
class FakeUserManagerWithLocalState : public chromeos::FakeChromeUserManager {
public:
FakeUserManagerWithLocalState()
: test_local_state_(std::make_unique<TestingPrefServiceSimple>()) {
RegisterPrefs(test_local_state_->registry());
}
~FakeUserManagerWithLocalState() override = default;
PrefService* GetLocalState() const override {
return test_local_state_.get();
}
private:
std::unique_ptr<TestingPrefServiceSimple> test_local_state_;
};
} // namespace
class PasswordSyncTokenVerifierTest : public testing::Test {
protected:
PasswordSyncTokenVerifierTest();
~PasswordSyncTokenVerifierTest() override;
// testing::Test:
void SetUp() override;
void CreatePasswordSyncTokenVerifier();
void DestroyPasswordSyncTokenVerifier();
void OnTokenVerified(bool is_verified);
bool PasswordSyncTokenFetcherIsAllocated();
void InvalidatePasswordSyncTokenFetcher();
const AccountId saml_login_account_id_ =
AccountId::FromUserEmailGaiaId(kSAMLUserEmail1, kSAMLUserId1);
content::BrowserTaskEnvironment test_environment_{
base::test::TaskEnvironment::MainThreadType::UI,
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
TestingProfileManager profile_manager_{TestingBrowserProcess::GetGlobal()};
TestingProfile* primary_profile_ = nullptr;
FakeChromeUserManager* user_manager_ = nullptr;
std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_;
std::unique_ptr<PasswordSyncTokenVerifier> verifier_;
};
PasswordSyncTokenVerifierTest::PasswordSyncTokenVerifierTest() {
std::unique_ptr<FakeChromeUserManager> fake_user_manager =
std::make_unique<FakeUserManagerWithLocalState>();
scoped_user_manager_ = std::make_unique<user_manager::ScopedUserManager>(
std::move(fake_user_manager));
user_manager_ =
static_cast<FakeChromeUserManager*>(user_manager::UserManager::Get());
}
PasswordSyncTokenVerifierTest::~PasswordSyncTokenVerifierTest() {
DestroyPasswordSyncTokenVerifier();
}
void PasswordSyncTokenVerifierTest::SetUp() {
ASSERT_TRUE(profile_manager_.SetUp());
primary_profile_ = profile_manager_.CreateTestingProfile("test1");
user_manager_->AddUserWithAffiliationAndTypeAndProfile(
saml_login_account_id_, /* is_afiliated = */ false,
user_manager::UserType::USER_TYPE_REGULAR, primary_profile_);
user_manager_->LoginUser(saml_login_account_id_);
// ActiveUser in FakeChromeUserManager needs to be set explicitly.
user_manager_->SwitchActiveUser(saml_login_account_id_);
ASSERT_TRUE(user_manager_->GetActiveUser());
primary_profile_->GetPrefs()->SetBoolean(
prefs::kSamlInSessionPasswordChangeEnabled, true);
}
void PasswordSyncTokenVerifierTest::CreatePasswordSyncTokenVerifier() {
DestroyPasswordSyncTokenVerifier();
verifier_ = std::make_unique<PasswordSyncTokenVerifier>(primary_profile_);
}
void PasswordSyncTokenVerifierTest::DestroyPasswordSyncTokenVerifier() {
if (verifier_) {
verifier_->Shutdown();
verifier_.reset();
}
}
void PasswordSyncTokenVerifierTest::OnTokenVerified(bool is_verified) {
verifier_->OnTokenVerified(is_verified);
}
bool PasswordSyncTokenVerifierTest::PasswordSyncTokenFetcherIsAllocated() {
return verifier_->password_sync_token_fetcher_ != nullptr;
}
void PasswordSyncTokenVerifierTest::InvalidatePasswordSyncTokenFetcher() {
verifier_->password_sync_token_fetcher_.reset();
}
TEST_F(PasswordSyncTokenVerifierTest, EmptySyncToken) {
CreatePasswordSyncTokenVerifier();
verifier_->CheckForPasswordNotInSync();
OnTokenVerified(false);
EXPECT_TRUE(PasswordSyncTokenFetcherIsAllocated());
EXPECT_TRUE(user_manager_->GetActiveUser()->force_online_signin());
}
TEST_F(PasswordSyncTokenVerifierTest, SyncTokenValidationPassed) {
user_manager::known_user::SetPasswordSyncToken(saml_login_account_id_,
kSyncToken);
CreatePasswordSyncTokenVerifier();
verifier_->CheckForPasswordNotInSync();
OnTokenVerified(true);
EXPECT_FALSE(user_manager_->GetActiveUser()->force_online_signin());
}
TEST_F(PasswordSyncTokenVerifierTest, SyncTokenValidationFailed) {
user_manager::known_user::SetPasswordSyncToken(saml_login_account_id_,
kSyncToken);
CreatePasswordSyncTokenVerifier();
verifier_->CheckForPasswordNotInSync();
OnTokenVerified(false);
EXPECT_TRUE(user_manager_->GetActiveUser()->force_online_signin());
}
TEST_F(PasswordSyncTokenVerifierTest, SyncTokenValidationAfterDelay) {
user_manager::known_user::SetPasswordSyncToken(saml_login_account_id_,
kSyncToken);
CreatePasswordSyncTokenVerifier();
verifier_->CheckForPasswordNotInSync();
OnTokenVerified(true);
EXPECT_FALSE(user_manager_->GetActiveUser()->force_online_signin());
InvalidatePasswordSyncTokenFetcher();
test_environment_.FastForwardBy(kSyncTokenCheckInterval);
EXPECT_TRUE(PasswordSyncTokenFetcherIsAllocated());
OnTokenVerified(false);
EXPECT_TRUE(user_manager_->GetActiveUser()->force_online_signin());
}
TEST_F(PasswordSyncTokenVerifierTest, SyncTokenNoRecheckExecuted) {
user_manager::known_user::SetPasswordSyncToken(saml_login_account_id_,
kSyncToken);
CreatePasswordSyncTokenVerifier();
verifier_->CheckForPasswordNotInSync();
OnTokenVerified(true);
EXPECT_FALSE(user_manager_->GetActiveUser()->force_online_signin());
user_manager::known_user::SetPasswordSyncToken(saml_login_account_id_,
std::string());
test_environment_.FastForwardBy(kSyncTokenCheckBelowInterval);
EXPECT_FALSE(user_manager_->GetActiveUser()->force_online_signin());
}
TEST_F(PasswordSyncTokenVerifierTest, PasswordChangePolicyNotSet) {
primary_profile_->GetPrefs()->SetBoolean(
prefs::kSamlInSessionPasswordChangeEnabled, false);
user_manager::known_user::SetPasswordSyncToken(saml_login_account_id_,
kSyncToken);
CreatePasswordSyncTokenVerifier();
verifier_->CheckForPasswordNotInSync();
OnTokenVerified(true);
user_manager::known_user::SetPasswordSyncToken(saml_login_account_id_,
std::string());
test_environment_.FastForwardBy(kSyncTokenCheckInterval);
EXPECT_FALSE(user_manager_->GetActiveUser()->force_online_signin());
}
} // namespace chromeos
......@@ -178,7 +178,8 @@ void SAMLOfflineSigninLimiter::ForceOnlineLogin() {
InSessionPasswordSyncManager* password_sync_manager =
InSessionPasswordSyncManagerFactory::GetForProfile(profile_);
if (password_sync_manager && password_sync_manager->IsLockReauthEnabled()) {
password_sync_manager->MaybeForceReauthOnLockScreen();
password_sync_manager->MaybeForceReauthOnLockScreen(
InSessionPasswordSyncManager::ReauthenticationReason::kPolicy);
}
RecordReauthReason(user->GetAccountId(), ReauthReason::SAML_REAUTH_POLICY);
offline_signin_limit_timer_->Stop();
......
......@@ -56,6 +56,8 @@
#include "chrome/browser/chromeos/login/login_pref_names.h"
#include "chrome/browser/chromeos/login/profile_auth_data.h"
#include "chrome/browser/chromeos/login/quick_unlock/pin_backend.h"
#include "chrome/browser/chromeos/login/saml/password_sync_token_verifier.h"
#include "chrome/browser/chromeos/login/saml/password_sync_token_verifier_factory.h"
#include "chrome/browser/chromeos/login/saml/saml_offline_signin_limiter.h"
#include "chrome/browser/chromeos/login/saml/saml_offline_signin_limiter_factory.h"
#include "chrome/browser/chromeos/login/screens/arc_terms_of_service_screen.h"
......@@ -1607,6 +1609,11 @@ void UserSessionManager::FinalizePrepareProfile(Profile* profile) {
user_context_.GetAccountId(),
user_context_.IsUsingSamlPrincipalsApi());
}
PasswordSyncTokenVerifier* password_sync_token_verifier =
PasswordSyncTokenVerifierFactory::GetForProfile(profile);
if (password_sync_token_verifier)
password_sync_token_verifier->CheckForPasswordNotInSync();
SAMLOfflineSigninLimiter* saml_offline_signin_limiter =
SAMLOfflineSigninLimiterFactory::GetForProfile(profile);
if (saml_offline_signin_limiter)
......
......@@ -91,6 +91,9 @@ const char kPinAutosubmitLength[] = "pin_autosubmit_length";
// Key for the PIN auto submit backfill needed indicator.
const char kPinAutosubmitBackfillNeeded[] = "pin_autosubmit_backfill_needed";
// Sync token for SAML password multi-device sync
const char kPasswordSyncToken[] = "password_sync_token";
// List containing all the known user preferences keys.
const char* kReservedKeys[] = {kCanonicalEmail,
kGAIAIdKey,
......@@ -111,7 +114,8 @@ const char* kReservedKeys[] = {kCanonicalEmail,
kIsEnterpriseManaged,
kLastInputMethod,
kPinAutosubmitLength,
kPinAutosubmitBackfillNeeded};
kPinAutosubmitBackfillNeeded,
kPasswordSyncToken};
PrefService* GetLocalState() {
if (!UserManager::IsInitialized())
......@@ -712,6 +716,19 @@ void PinAutosubmitSetBackfillNeededForTests(const AccountId& account_id) {
SetBooleanPref(account_id, kPinAutosubmitBackfillNeeded, true);
}
void SetPasswordSyncToken(const AccountId& account_id,
const std::string& token) {
SetStringPref(account_id, kPasswordSyncToken, token);
}
std::string GetPasswordSyncToken(const AccountId& account_id) {
std::string token;
if (GetStringPref(account_id, kPasswordSyncToken, &token))
return token;
// Return empty string if sync token was not set for the account yet.
return std::string();
}
void RemovePrefs(const AccountId& account_id) {
PrefService* local_state = GetLocalState();
......
......@@ -256,6 +256,14 @@ PinAutosubmitSetBackfillNotNeeded(const AccountId& account_id);
void USER_MANAGER_EXPORT
PinAutosubmitSetBackfillNeededForTests(const AccountId& account_id);
// Setter and getter for password sync token used for syncing SAML passwords
// across multiple user devices.
void USER_MANAGER_EXPORT SetPasswordSyncToken(const AccountId& account_id,
const std::string& token);
std::string USER_MANAGER_EXPORT
GetPasswordSyncToken(const AccountId& account_id);
// Removes all user preferences associated with |account_id|.
// Not exported as code should not be calling this outside this component
void RemovePrefs(const AccountId& account_id);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment