// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ash/login/user_online_signin_notifier.h"

#include <string>
#include <utility>

#include "base/time/clock.h"
#include "base/time/default_clock.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/ash/login/existing_user_controller_base_test.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/test/base/testing_browser_process.h"
#include "components/user_manager/known_user.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {
namespace {

// Mock implementation of UserOnlineSigninNotifier::Observer.
class MockUserOnlineSigninNotifierObserver
    : public UserOnlineSigninNotifier::Observer {
 public:
  MOCK_METHOD(void, OnOnlineSigninEnforced, (const AccountId& account_id));
};

constexpr base::TimeDelta kLoginOnlineShortDelay = base::Seconds(10);
constexpr base::TimeDelta kLoginOnlineLongDelay = base::Seconds(100);
constexpr base::TimeDelta kLoginOnlineVeryLongDelay = base::Seconds(1000);
constexpr base::TimeDelta kLoginOnlineOffset = base::Seconds(1);

}  // namespace

class UserOnlineSigninNotifierTest : public ExistingUserControllerBaseTest {
 public:
  void SetUp() override {
    mock_online_signin_notifier_observer_ =
        std::make_unique<MockUserOnlineSigninNotifierObserver>();
  }
  UserOnlineSigninNotifier* user_online_signin_notifier() const {
    return user_online_signin_notifier_.get();
  }

  // UserOnlineSigninNotifier private member accessors.
  base::OneShotTimer* online_login_refresh_timer() {
    return user_online_signin_notifier_->online_login_refresh_timer_.get();
  }

 protected:
  std::unique_ptr<MockUserOnlineSigninNotifierObserver>
      mock_online_signin_notifier_observer_;
  std::unique_ptr<UserOnlineSigninNotifier> user_online_signin_notifier_;
};

// Tests login screen update when SAMLOfflineSigninTimeLimit policy is set.
TEST_F(UserOnlineSigninNotifierTest, SamlOnlineAuthSingleUser) {
  const base::Time now = base::DefaultClock::GetInstance()->Now();
  user_manager::KnownUser known_user(
      TestingBrowserProcess::GetGlobal()->local_state());
  known_user.SetLastOnlineSignin(saml_login_account1_id_, now);
  known_user.SetOfflineSigninLimit(saml_login_account1_id_,
                                   kLoginOnlineShortDelay);

  auto* user_manager = GetFakeUserManager();
  user_manager->AddSamlUser(saml_login_account1_id_);
  user_online_signin_notifier_ = std::make_unique<UserOnlineSigninNotifier>(
      TestingBrowserProcess::GetGlobal()->local_state(),
      user_manager->GetPersistedUsers());
  user_online_signin_notifier_->AddObserver(
      mock_online_signin_notifier_observer_.get());
  EXPECT_CALL(*mock_online_signin_notifier_observer_,
              OnOnlineSigninEnforced(saml_login_account1_id_))
      .Times(1);
  user_online_signin_notifier()->CheckForPolicyEnforcedOnlineSignin();
  EXPECT_TRUE(online_login_refresh_timer()->IsRunning());
  // Check timer again 1s after its expected expiry.
  task_environment_.FastForwardBy(kLoginOnlineShortDelay + kLoginOnlineOffset);
  EXPECT_FALSE(online_login_refresh_timer()->IsRunning());
}

// Verfies that `OfflineSigninLimiter` does affect SAML and non SAML user.
TEST_F(UserOnlineSigninNotifierTest, OfflineLimiteOutOfSessionSAMLAndNonSAML) {
  const base::Time now = base::DefaultClock::GetInstance()->Now();
  user_manager::KnownUser known_user(
      TestingBrowserProcess::GetGlobal()->local_state());
  known_user.SetLastOnlineSignin(saml_login_account1_id_, now);
  known_user.SetOfflineSigninLimit(saml_login_account1_id_,
                                   kLoginOnlineShortDelay);

  auto* user_manager = GetFakeUserManager();
  user_manager->AddUser(saml_login_account1_id_);
  user_online_signin_notifier_ = std::make_unique<UserOnlineSigninNotifier>(
      TestingBrowserProcess::GetGlobal()->local_state(),
      user_manager->GetPersistedUsers());
  user_online_signin_notifier_->AddObserver(
      mock_online_signin_notifier_observer_.get());
  EXPECT_CALL(*mock_online_signin_notifier_observer_,
              OnOnlineSigninEnforced(saml_login_account1_id_))
      .Times(0);
  user_online_signin_notifier()->CheckForPolicyEnforcedOnlineSignin();
  // Expect true since user has a value set in `kOfflineSigninLimit`.
  EXPECT_TRUE(online_login_refresh_timer()->IsRunning());
}

// Tests login screen update functionality for 2 SAML users.
TEST_F(UserOnlineSigninNotifierTest, SamlOnlineAuthTwoSamlUsers) {
  base::Time now = base::DefaultClock::GetInstance()->Now();
  user_manager::KnownUser known_user(
      TestingBrowserProcess::GetGlobal()->local_state());
  known_user.SetLastOnlineSignin(saml_login_account1_id_, now);
  known_user.SetOfflineSigninLimit(saml_login_account1_id_,
                                   kLoginOnlineLongDelay);

  task_environment_.FastForwardBy(kLoginOnlineShortDelay);
  now = base::DefaultClock::GetInstance()->Now();
  known_user.SetLastOnlineSignin(saml_login_account2_id_, now);
  known_user.SetOfflineSigninLimit(saml_login_account2_id_,
                                   kLoginOnlineVeryLongDelay);

  auto* user_manager = GetFakeUserManager();
  user_manager->AddSamlUser(saml_login_account1_id_);
  user_manager->AddSamlUser(saml_login_account2_id_);
  user_online_signin_notifier_ = std::make_unique<UserOnlineSigninNotifier>(
      TestingBrowserProcess::GetGlobal()->local_state(),
      user_manager->GetPersistedUsers());
  user_online_signin_notifier_->AddObserver(
      mock_online_signin_notifier_observer_.get());
  EXPECT_CALL(*mock_online_signin_notifier_observer_,
              OnOnlineSigninEnforced(saml_login_account1_id_))
      .Times(1);
  user_online_signin_notifier()->CheckForPolicyEnforcedOnlineSignin();
  EXPECT_TRUE(online_login_refresh_timer()->IsRunning());
  // The timer should be re-started after
  // (kLoginOnlineLongDelay - kLoginOnlineShortDelay) s.
  task_environment_.FastForwardBy(kLoginOnlineLongDelay -
                                  kLoginOnlineShortDelay + kLoginOnlineOffset);
  EXPECT_TRUE(online_login_refresh_timer()->IsRunning());
}

// Tests login screen update functionality for 2 users: SAML and non-SAML.
TEST_F(UserOnlineSigninNotifierTest, SamlOnlineAuthSamlAndNonSamlUsers) {
  const base::Time now = base::DefaultClock::GetInstance()->Now();
  user_manager::KnownUser known_user(
      TestingBrowserProcess::GetGlobal()->local_state());
  known_user.SetLastOnlineSignin(saml_login_account1_id_, now);
  known_user.SetLastOnlineSignin(saml_login_account2_id_, now);

  known_user.SetOfflineSigninLimit(saml_login_account1_id_,
                                   kLoginOnlineShortDelay);
  known_user.SetOfflineSigninLimit(saml_login_account2_id_,
                                   kLoginOnlineLongDelay);

  auto* user_manager = GetFakeUserManager();
  user_manager->AddSamlUser(saml_login_account1_id_);
  user_manager->AddUser(saml_login_account2_id_);
  user_online_signin_notifier_ = std::make_unique<UserOnlineSigninNotifier>(
      TestingBrowserProcess::GetGlobal()->local_state(),
      user_manager->GetPersistedUsers());
  user_online_signin_notifier_->AddObserver(
      mock_online_signin_notifier_observer_.get());
  EXPECT_CALL(*mock_online_signin_notifier_observer_,
              OnOnlineSigninEnforced(saml_login_account1_id_))
      .Times(1);
  EXPECT_CALL(*mock_online_signin_notifier_observer_,
              OnOnlineSigninEnforced(saml_login_account2_id_))
      .Times(0);
  user_online_signin_notifier()->CheckForPolicyEnforcedOnlineSignin();
  EXPECT_TRUE(online_login_refresh_timer()->IsRunning());
  task_environment_.FastForwardBy(kLoginOnlineShortDelay + kLoginOnlineOffset);
  EXPECT_TRUE(online_login_refresh_timer()->IsRunning());
}

// Tests unset policy value in local state.
TEST_F(UserOnlineSigninNotifierTest, SamlOnlineAuthSamlPolicyNotSet) {
  const base::Time now = base::DefaultClock::GetInstance()->Now();
  user_manager::KnownUser known_user(
      TestingBrowserProcess::GetGlobal()->local_state());
  known_user.SetLastOnlineSignin(saml_login_account1_id_, now);
  known_user.SetOfflineSigninLimit(saml_login_account1_id_, std::nullopt);

  auto* user_manager = GetFakeUserManager();
  user_manager->AddSamlUser(saml_login_account1_id_);
  user_online_signin_notifier_ = std::make_unique<UserOnlineSigninNotifier>(
      TestingBrowserProcess::GetGlobal()->local_state(),
      user_manager->GetPersistedUsers());
  user_online_signin_notifier_->AddObserver(
      mock_online_signin_notifier_observer_.get());
  EXPECT_CALL(*mock_online_signin_notifier_observer_,
              OnOnlineSigninEnforced(saml_login_account1_id_))
      .Times(0);
  user_online_signin_notifier()->CheckForPolicyEnforcedOnlineSignin();
  EXPECT_FALSE(online_login_refresh_timer()->IsRunning());
}

// Tests login screen update when `GaiaOfflineSigninTimeLimitDays` policy is set
// and the last online sign in has not been set. This is the case for those
// devices that went through the online signin in the first login before the
// introduction of `GaiaOfflineSigninTimeLimitDays` policy logic which didn't
// store the last online sign in.
TEST_F(UserOnlineSigninNotifierTest,
       GaiaOnlineAuthSingleUserNoLastOnlineSignin) {
  const base::Time now = base::DefaultClock::GetInstance()->Now();
  user_manager::KnownUser known_user(
      TestingBrowserProcess::GetGlobal()->local_state());
  known_user.SetOfflineSigninLimit(gaia_login_account1_id_,
                                   kLoginOnlineShortDelay);

  auto* user_manager = GetFakeUserManager();
  user_manager->AddUser(gaia_login_account1_id_);
  user_online_signin_notifier_ = std::make_unique<UserOnlineSigninNotifier>(
      TestingBrowserProcess::GetGlobal()->local_state(),
      user_manager->GetPersistedUsers());
  user_online_signin_notifier_->AddObserver(
      mock_online_signin_notifier_observer_.get());
  EXPECT_CALL(*mock_online_signin_notifier_observer_,
              OnOnlineSigninEnforced(gaia_login_account1_id_))
      .Times(1);
  user_online_signin_notifier()->CheckForPolicyEnforcedOnlineSignin();
  testing::Mock::VerifyAndClearExpectations(
      mock_online_signin_notifier_observer_.get());
  // Since `LastOnlineSignin` value is null and there is a limit, it will
  // enforce the next login to be online. No timer should be running.
  EXPECT_FALSE(online_login_refresh_timer()->IsRunning());

  // User logged in online after enforcement.
  known_user.SetLastOnlineSignin(gaia_login_account1_id_, now);
  // Set the force_online_signin to false along with logged in,
  // which is done often in OfflineSigninLimiter::SignedIn.
  user_manager->SaveForceOnlineSignin(gaia_login_account1_id_, false);

  user_online_signin_notifier_ = std::make_unique<UserOnlineSigninNotifier>(
      TestingBrowserProcess::GetGlobal()->local_state(),
      user_manager->GetPersistedUsers());
  user_online_signin_notifier_->AddObserver(
      mock_online_signin_notifier_observer_.get());
  EXPECT_CALL(*mock_online_signin_notifier_observer_,
              OnOnlineSigninEnforced(gaia_login_account1_id_))
      .Times(0);
  user_online_signin_notifier()->CheckForPolicyEnforcedOnlineSignin();
  EXPECT_TRUE(online_login_refresh_timer()->IsRunning());
  testing::Mock::VerifyAndClearExpectations(
      mock_online_signin_notifier_observer_.get());
  // Check timer again 1s after its expacted expiry.
  EXPECT_CALL(*mock_online_signin_notifier_observer_,
              OnOnlineSigninEnforced(gaia_login_account1_id_))
      .Times(1);
  task_environment_.FastForwardBy(kLoginOnlineShortDelay + kLoginOnlineOffset);
  EXPECT_FALSE(online_login_refresh_timer()->IsRunning());
  testing::Mock::VerifyAndClearExpectations(
      mock_online_signin_notifier_observer_.get());
}

// Tests login screen update when `GaiaOfflineSigninTimeLimitDays` policy is set
// and the last online sign in has been set.
TEST_F(UserOnlineSigninNotifierTest, GaiaOnlineAuthSingleUserLastOnlineSignin) {
  const base::Time now = base::DefaultClock::GetInstance()->Now();
  user_manager::KnownUser known_user(
      TestingBrowserProcess::GetGlobal()->local_state());
  known_user.SetLastOnlineSignin(gaia_login_account1_id_, now);
  known_user.SetOfflineSigninLimit(gaia_login_account1_id_,
                                   kLoginOnlineShortDelay);

  auto* user_manager = GetFakeUserManager();
  user_manager->AddUser(gaia_login_account1_id_);
  user_online_signin_notifier_ = std::make_unique<UserOnlineSigninNotifier>(
      TestingBrowserProcess::GetGlobal()->local_state(),
      user_manager->GetPersistedUsers());
  user_online_signin_notifier_->AddObserver(
      mock_online_signin_notifier_observer_.get());
  EXPECT_CALL(*mock_online_signin_notifier_observer_,
              OnOnlineSigninEnforced(gaia_login_account1_id_))
      .Times(1);
  user_online_signin_notifier()->CheckForPolicyEnforcedOnlineSignin();
  EXPECT_TRUE(online_login_refresh_timer()->IsRunning());
  // Check timer again 1s after its expacted expiry.
  task_environment_.FastForwardBy(kLoginOnlineShortDelay + kLoginOnlineOffset);
  EXPECT_FALSE(online_login_refresh_timer()->IsRunning());
}

// Tests login screen update functionality for 2 Gaia without SAML users.
TEST_F(UserOnlineSigninNotifierTest, GaiaOnlineAuthTwoGaiaUsers) {
  base::Time now = base::DefaultClock::GetInstance()->Now();
  user_manager::KnownUser known_user(
      TestingBrowserProcess::GetGlobal()->local_state());
  known_user.SetLastOnlineSignin(gaia_login_account1_id_, now);
  known_user.SetOfflineSigninLimit(gaia_login_account1_id_,
                                   kLoginOnlineLongDelay);

  task_environment_.FastForwardBy(kLoginOnlineShortDelay);
  now = base::DefaultClock::GetInstance()->Now();
  known_user.SetLastOnlineSignin(gaia_login_account2_id_, now);
  known_user.SetOfflineSigninLimit(gaia_login_account2_id_,
                                   kLoginOnlineVeryLongDelay);

  auto* user_manager = GetFakeUserManager();
  user_manager->AddUser(gaia_login_account1_id_);
  user_manager->AddUser(gaia_login_account2_id_);
  user_online_signin_notifier_ = std::make_unique<UserOnlineSigninNotifier>(
      TestingBrowserProcess::GetGlobal()->local_state(),
      user_manager->GetPersistedUsers());
  user_online_signin_notifier_->AddObserver(
      mock_online_signin_notifier_observer_.get());
  EXPECT_CALL(*mock_online_signin_notifier_observer_,
              OnOnlineSigninEnforced(gaia_login_account1_id_))
      .Times(1);
  EXPECT_CALL(*mock_online_signin_notifier_observer_,
              OnOnlineSigninEnforced(gaia_login_account2_id_))
      .Times(0);
  user_online_signin_notifier()->CheckForPolicyEnforcedOnlineSignin();
  EXPECT_TRUE(online_login_refresh_timer()->IsRunning());
  // The timer should be re-started after
  // (kLoginOnlineLongDelay - kLoginOnlineShortDelay) s.
  task_environment_.FastForwardBy(kLoginOnlineLongDelay -
                                  kLoginOnlineShortDelay + kLoginOnlineOffset);
  EXPECT_TRUE(online_login_refresh_timer()->IsRunning());
}

// Tests unset `GaiaOfflineTimeLimitDays` policy value in local state.
TEST_F(UserOnlineSigninNotifierTest, GaiaOnlineAuthGaiaPolicyNotSet) {
  const base::Time now = base::DefaultClock::GetInstance()->Now();
  user_manager::KnownUser known_user(
      TestingBrowserProcess::GetGlobal()->local_state());
  // No `LastOnlineSignin` value, case where devices didn't store that value in
  // the first Gaia login.
  known_user.SetOfflineSigninLimit(gaia_login_account1_id_, std::nullopt);

  // Case where the user has already stored last online signin.
  known_user.SetLastOnlineSignin(gaia_login_account2_id_, now);
  known_user.SetOfflineSigninLimit(gaia_login_account2_id_, std::nullopt);

  auto* user_manager = GetFakeUserManager();
  user_manager->AddUser(gaia_login_account1_id_);
  user_manager->AddUser(gaia_login_account2_id_);
  user_online_signin_notifier_ = std::make_unique<UserOnlineSigninNotifier>(
      TestingBrowserProcess::GetGlobal()->local_state(),
      user_manager->GetPersistedUsers());
  user_online_signin_notifier_->AddObserver(
      mock_online_signin_notifier_observer_.get());
  EXPECT_CALL(*mock_online_signin_notifier_observer_,
              OnOnlineSigninEnforced(gaia_login_account1_id_))
      .Times(0);
  EXPECT_CALL(*mock_online_signin_notifier_observer_,
              OnOnlineSigninEnforced(gaia_login_account1_id_))
      .Times(0);
  user_online_signin_notifier()->CheckForPolicyEnforcedOnlineSignin();
  EXPECT_FALSE(online_login_refresh_timer()->IsRunning());
}

}  // namespace ash
