// Copyright 2014 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/profile_auth_data.h"

#include <memory>
#include <string>
#include <utility>

#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/time/time.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_utils.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/base/network_anonymization_key.h"
#include "net/cookies/canonical_cookie.h"
#include "net/cookies/cookie_constants.h"
#include "net/http/http_auth.h"
#include "net/http/http_auth_cache.h"
#include "net/http/http_network_session.h"
#include "net/http/http_transaction_factory.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"
#include "services/cert_verifier/public/mojom/cert_verifier_service_factory.mojom.h"
#include "services/network/network_context.h"
#include "services/network/network_service.h"
#include "services/network/public/mojom/cookie_manager.mojom.h"
#include "services/network/public/mojom/network_context.mojom-forward.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/scheme_host_port.h"

namespace ash {
namespace {

const char kProxyAuthURL[] = "https://example.com/";
const char kProxyAuthRealm[] = "realm";
const char kProxyAuthChallenge[] = "challenge";
const char16_t kProxyAuthPassword1[] = u"password 1";
const char16_t kProxyAuthPassword2[] = u"password 2";

const char kGAIACookieURL[] = "https://google.com/";
const char kSAMLIdPCookieURL[] = "https://example.com/";
const char kCookieName[] = "cookie";
const char kCookieValue1[] = "value 1";
const char kCookieValue2[] = "value 2";
const char kGAIACookieDomain[] = "google.com";
const char kSAMLIdPCookieDomain[] = "example.com";
const char kSAMLIdPCookieDomainWithWildcard[] = ".example.com";

std::unique_ptr<network::NetworkContext>
CreateNetworkContextForDefaultStoragePartition(
    network::NetworkService* network_service,
    content::BrowserContext* browser_context) {
  mojo::PendingRemote<network::mojom::NetworkContext> network_context_remote;
  auto params = network::mojom::NetworkContextParams::New();
  params->cert_verifier_params = content::GetCertVerifierParams(
      cert_verifier::mojom::CertVerifierCreationParams::New());
  auto network_context = std::make_unique<network::NetworkContext>(
      network_service, network_context_remote.InitWithNewPipeAndPassReceiver(),
      std::move(params));
  browser_context->GetDefaultStoragePartition()->SetNetworkContextForTesting(
      std::move(network_context_remote));
  return network_context;
}

network::NetworkService* GetNetworkService() {
  content::GetNetworkService();
  // Wait for the Network Service to initialize on the IO thread.
  content::RunAllPendingInMessageLoop(content::BrowserThread::IO);
  return network::NetworkService::GetNetworkServiceForTesting();
}

}  // namespace

class ProfileAuthDataTest : public testing::Test {
 public:
  ProfileAuthDataTest();

  // testing::Test:
  void SetUp() override;

  void PopulateUserBrowserContext();

  void Transfer(bool transfer_auth_cookies_on_first_login,
                bool transfer_saml_auth_cookies_on_subsequent_login);

  net::CookieList GetUserCookies();

  void VerifyTransferredUserProxyAuthEntry();
  void VerifyUserCookies(const std::string& expected_gaia_cookie_value,
                         const std::string& expected_saml_idp_cookie_value);

 private:
  void PopulateBrowserContext(TestingProfile* browser_context,
                              network::NetworkContext* network_context,
                              const std::u16string& proxy_auth_password,
                              const std::string& cookie_value);

  net::HttpAuthCache* GetAuthCache(network::NetworkContext* network_context);
  network::mojom::CookieManager* GetCookies(
      content::BrowserContext* browser_context);

  content::BrowserTaskEnvironment task_environment_;

  raw_ptr<network::NetworkService> network_service_;
  TestingProfile login_browser_context_;
  TestingProfile user_browser_context_;
  std::unique_ptr<network::NetworkContext> login_network_context_;
  std::unique_ptr<network::NetworkContext> user_network_context_;
};

ProfileAuthDataTest::ProfileAuthDataTest()
    : network_service_(GetNetworkService()) {
  login_network_context_ = CreateNetworkContextForDefaultStoragePartition(
      network_service_, &login_browser_context_);
  user_network_context_ = CreateNetworkContextForDefaultStoragePartition(
      network_service_, &user_browser_context_);
}

void ProfileAuthDataTest::SetUp() {
  PopulateBrowserContext(&login_browser_context_, login_network_context_.get(),
                         kProxyAuthPassword1, kCookieValue1);
}

void ProfileAuthDataTest::PopulateUserBrowserContext() {
  PopulateBrowserContext(&user_browser_context_, user_network_context_.get(),
                         kProxyAuthPassword2, kCookieValue2);
}

void ProfileAuthDataTest::Transfer(
    bool transfer_auth_cookies_on_first_login,
    bool transfer_saml_auth_cookies_on_subsequent_login) {
  base::RunLoop run_loop;
  ProfileAuthData::Transfer(
      login_browser_context_.GetDefaultStoragePartition(),
      &user_browser_context_, transfer_auth_cookies_on_first_login,
      transfer_saml_auth_cookies_on_subsequent_login, run_loop.QuitClosure());
  run_loop.Run();
}

net::CookieList ProfileAuthDataTest::GetUserCookies() {
  base::RunLoop run_loop;
  net::CookieList result;
  GetCookies(&user_browser_context_)
      ->GetAllCookies(
          base::BindLambdaForTesting([&](const net::CookieList& cookie_list) {
            result = cookie_list;
            run_loop.Quit();
          }));
  run_loop.Run();
  return result;
}

void ProfileAuthDataTest::VerifyTransferredUserProxyAuthEntry() {
  net::HttpAuthCache::Entry* entry =
      GetAuthCache(user_network_context_.get())
          ->Lookup(url::SchemeHostPort(GURL(kProxyAuthURL)),
                   net::HttpAuth::AUTH_PROXY, kProxyAuthRealm,
                   net::HttpAuth::AUTH_SCHEME_BASIC,
                   net::NetworkAnonymizationKey());
  ASSERT_TRUE(entry);
  EXPECT_EQ(kProxyAuthPassword1, entry->credentials().password());
}

void ProfileAuthDataTest::VerifyUserCookies(
    const std::string& expected_gaia_cookie_value,
    const std::string& expected_saml_idp_cookie_value) {
  net::CookieList user_cookies = GetUserCookies();
  ASSERT_EQ(3u, user_cookies.size());

  // Cookies are returned chronoligically, in the order they were set.
  net::CanonicalCookie* cookie = &user_cookies[0];
  EXPECT_EQ(kCookieName, cookie->Name());
  EXPECT_EQ(expected_saml_idp_cookie_value, cookie->Value());
  EXPECT_EQ(kSAMLIdPCookieDomainWithWildcard, cookie->Domain());

  cookie = &user_cookies[1];
  EXPECT_EQ(kCookieName, cookie->Name());
  EXPECT_EQ(expected_saml_idp_cookie_value, cookie->Value());
  EXPECT_EQ(kSAMLIdPCookieDomain, cookie->Domain());

  cookie = &user_cookies[2];
  EXPECT_EQ(kCookieName, cookie->Name());
  EXPECT_EQ(expected_gaia_cookie_value, cookie->Value());
  EXPECT_EQ(kGAIACookieDomain, cookie->Domain());
}

void ProfileAuthDataTest::PopulateBrowserContext(
    TestingProfile* browser_context,
    network::NetworkContext* network_context,
    const std::u16string& proxy_auth_password,
    const std::string& cookie_value) {
  GetAuthCache(network_context)
      ->Add(url::SchemeHostPort(GURL(kProxyAuthURL)), net::HttpAuth::AUTH_PROXY,
            kProxyAuthRealm, net::HttpAuth::AUTH_SCHEME_BASIC,
            net::NetworkAnonymizationKey(), kProxyAuthChallenge,
            net::AuthCredentials(std::u16string(), proxy_auth_password),
            std::string());

  network::mojom::CookieManager* cookies = GetCookies(browser_context);
  // Ensure `cookies` is fully initialized.
  base::RunLoop run_loop;
  cookies->GetAllCookies(base::BindLambdaForTesting(
      [&](const net::CookieList& cookies) { run_loop.Quit(); }));
  run_loop.Run();

  net::CookieOptions options;
  options.set_include_httponly();
  cookies->SetCanonicalCookie(
      *net::CanonicalCookie::CreateSanitizedCookie(
          GURL(kSAMLIdPCookieURL), kCookieName, cookie_value,
          kSAMLIdPCookieDomainWithWildcard, std::string(), base::Time(),
          base::Time(), base::Time(), true, false,
          net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_DEFAULT,
          std::nullopt, /*status=*/nullptr),
      GURL(kSAMLIdPCookieURL), options, base::DoNothing());

  cookies->SetCanonicalCookie(
      *net::CanonicalCookie::CreateSanitizedCookie(
          GURL(kSAMLIdPCookieURL), kCookieName, cookie_value, std::string(),
          std::string(), base::Time(), base::Time(), base::Time(), true, false,
          net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_DEFAULT,
          std::nullopt, /*status=*/nullptr),
      GURL(kSAMLIdPCookieURL), options, base::DoNothing());

  cookies->SetCanonicalCookie(
      *net::CanonicalCookie::CreateSanitizedCookie(
          GURL(kGAIACookieURL), kCookieName, cookie_value, std::string(),
          std::string(), base::Time(), base::Time(), base::Time(), true, false,
          net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_DEFAULT,
          std::nullopt, /*status=*/nullptr),
      GURL(kGAIACookieURL), options, base::DoNothing());
}

net::HttpAuthCache* ProfileAuthDataTest::GetAuthCache(
    network::NetworkContext* network_context) {
  return network_context->url_request_context()
      ->http_transaction_factory()
      ->GetSession()
      ->http_auth_cache();
}

network::mojom::CookieManager* ProfileAuthDataTest::GetCookies(
    content::BrowserContext* browser_context) {
  return browser_context->GetDefaultStoragePartition()
      ->GetCookieManagerForBrowserProcess();
}

// Verifies that when no transfer of auth cookies is requested, only the proxy
// auth state is transferred.
TEST_F(ProfileAuthDataTest, DoNotTransfer) {
  Transfer(false, false);

  VerifyTransferredUserProxyAuthEntry();
  EXPECT_TRUE(GetUserCookies().empty());
}

// Verifies that when the transfer of auth cookies on first login is requested,
// they do get transferred along with the proxy auth state on first login.
TEST_F(ProfileAuthDataTest, TransferOnFirstLoginWithNewProfile) {
  Transfer(true, false);

  VerifyTransferredUserProxyAuthEntry();
  VerifyUserCookies(kCookieValue1, kCookieValue1);
}

// Verifies that even if the transfer of auth cookies on first login is
// requested, only the proxy auth state is transferred on subsequent login.
TEST_F(ProfileAuthDataTest, TransferOnFirstLoginWithExistingProfile) {
  PopulateUserBrowserContext();

  Transfer(true, false);

  VerifyTransferredUserProxyAuthEntry();
  VerifyUserCookies(kCookieValue2, kCookieValue2);
}

// Verifies that when the transfer of auth cookies set by a SAML IdP on
// subsequent login is requested, they do get transferred along with the proxy
// auth state on subsequent login.
TEST_F(ProfileAuthDataTest, TransferOnSubsequentLogin) {
  PopulateUserBrowserContext();

  Transfer(false, true);

  VerifyTransferredUserProxyAuthEntry();
  VerifyUserCookies(kCookieValue2, kCookieValue1);
}

}  // namespace ash
