// Copyright 2019 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/ssl/chrome_security_blocking_page_factory.h"

#include "base/functional/bind.h"
#include "base/notreached.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/interstitials/chrome_settings_page_helper.h"
#include "chrome/browser/net/secure_dns_config.h"
#include "chrome/browser/net/stub_resolver_config_reader.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/safe_browsing/advanced_protection_status_manager.h"
#include "chrome/browser/safe_browsing/advanced_protection_status_manager_factory.h"
#include "chrome/browser/safe_browsing/safe_browsing_metrics_collector_factory.h"
#include "chrome/browser/ssl/https_only_mode_controller_client.h"
#include "chrome/browser/ssl/https_upgrades_util.h"
#include "chrome/browser/ssl/insecure_form/insecure_form_controller_client.h"
#include "chrome/browser/ssl/ssl_error_controller_client.h"
#include "chrome/browser/ui/browser_window/public/create_browser_window.h"
#include "chrome/browser/ui/navigator/browser_navigator.h"
#include "chrome/common/channel_info.h"
#include "chrome/common/chrome_features.h"
#include "components/safe_browsing/core/browser/safe_browsing_metrics_collector.h"
#include "components/security_interstitials/content/content_metrics_helper.h"
#include "components/security_interstitials/content/settings_page_helper.h"
#include "components/security_interstitials/content/ssl_blocking_page.h"
#include "components/security_interstitials/core/controller_client.h"
#include "components/security_interstitials/core/metrics_helper.h"
#include "content/public/browser/web_contents.h"

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
#include "base/enterprise_util.h"
#elif BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
#include "chrome/browser/browser_process_platform_part.h"
#endif

#if BUILDFLAG(IS_ANDROID)
#include "base/android/jni_android.h"
#include "components/security_interstitials/content/captive_portal_helper_android.h"
#include "content/public/common/referrer.h"
#include "net/android/network_library.h"
#include "ui/base/window_open_disposition.h"
#endif

#if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION)
#include "chrome/browser/captive_portal/captive_portal_service_factory.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/public/global_browser_collection.h"
#include "chrome/browser/ui/browser_window/public/profile_browser_collection.h"
#include "chrome/browser/ui/tab_contents/tab_contents_iterator.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "components/captive_portal/content/captive_portal_tab_helper.h"
#include "components/tabs/public/tab_interface.h"
#include "net/base/net_errors.h"
#include "net/dns/public/secure_dns_mode.h"
#endif

namespace {

enum EnterpriseManaged {
  ENTERPRISE_MANAGED_STATUS_NOT_SET,
  ENTERPRISE_MANAGED_STATUS_TRUE,
  ENTERPRISE_MANAGED_STATUS_FALSE
};

EnterpriseManaged g_is_enterprise_managed_for_testing =
    ENTERPRISE_MANAGED_STATUS_NOT_SET;

// Opens the login page for a captive portal. Passed in to
// CaptivePortalBlockingPage to be invoked when the user has pressed the
// connect button.
void OpenLoginPage(content::WebContents* web_contents) {
#if !BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION)
  // OpenLoginTabForWebContents() is not available on Android (the only
  // platform on which captive portal detection is not enabled). Simply open
  // the platform's portal detection URL in a new tab.
  const std::string url = security_interstitials::GetCaptivePortalServerUrl(
      base::android::AttachCurrentThread());
  content::OpenURLParams params(GURL(url), content::Referrer(),
                                WindowOpenDisposition::NEW_FOREGROUND_TAB,
                                ui::PAGE_TRANSITION_LINK, false);
  web_contents->OpenURL(params, /*navigation_handle_callback=*/{});
#else
  ChromeSecurityBlockingPageFactory::OpenLoginTabForWebContents(web_contents,
                                                                true);
#endif  // !BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION)
}

std::unique_ptr<ContentMetricsHelper> CreateMetricsHelperAndStartRecording(
    content::WebContents* web_contents,
    const GURL& request_url,
    const std::string& metric_prefix,
    bool overridable) {
  security_interstitials::MetricsHelper::ReportDetails reporting_info;
  reporting_info.metric_prefix = metric_prefix;
  std::unique_ptr<ContentMetricsHelper> metrics_helper =
      std::make_unique<ContentMetricsHelper>(
          HistoryServiceFactory::GetForProfile(
              Profile::FromBrowserContext(web_contents->GetBrowserContext()),
              ServiceAccessType::EXPLICIT_ACCESS),
          request_url, reporting_info);
#if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION)
  metrics_helper.get()->StartRecordingCaptivePortalMetrics(
      CaptivePortalServiceFactory::GetForProfile(
          Profile::FromBrowserContext(web_contents->GetBrowserContext())),
      overridable);
#endif
  return metrics_helper;
}

std::unique_ptr<security_interstitials::SettingsPageHelper>
CreateSettingsPageHelper() {
  return security_interstitials::ChromeSettingsPageHelper::
      CreateChromeSettingsPageHelper();
}

void LogSafeBrowsingSecuritySensitiveAction(
    safe_browsing::SafeBrowsingMetricsCollector* metrics_collector) {
  if (metrics_collector) {
    metrics_collector->AddSafeBrowsingEventToPref(
        safe_browsing::SafeBrowsingMetricsCollector::EventType::
            SECURITY_SENSITIVE_SSL_INTERSTITIAL);
  }
}

}  // namespace

std::unique_ptr<SSLBlockingPage>
ChromeSecurityBlockingPageFactory::CreateSSLPage(
    content::WebContents* web_contents,
    net::Error cert_error,
    const net::SSLInfo& ssl_info,
    const GURL& request_url,
    int options_mask,
    const base::Time& time_triggered,
    const GURL& support_url) {
  bool overridable = SSLBlockingPage::IsOverridable(options_mask);
  std::unique_ptr<ContentMetricsHelper> metrics_helper(
      CreateMetricsHelperAndStartRecording(
          web_contents, request_url,
          overridable ? "ssl_overridable" : "ssl_nonoverridable", overridable));

  LogSafeBrowsingSecuritySensitiveAction(
      safe_browsing::SafeBrowsingMetricsCollectorFactory::GetForProfile(
          Profile::FromBrowserContext(web_contents->GetBrowserContext())));

  auto controller_client = std::make_unique<SSLErrorControllerClient>(
      web_contents, ssl_info, cert_error, request_url,
      std::move(metrics_helper), CreateSettingsPageHelper());

  std::unique_ptr<SSLBlockingPage> page;

  page = std::make_unique<SSLBlockingPage>(
      web_contents, cert_error, ssl_info, request_url, options_mask,
      time_triggered, support_url, overridable,
      /*can_show_enhanced_protection_message=*/true,
      std::move(controller_client));

  return page;
}

std::unique_ptr<LocalSelfSignedBlockingPage>
ChromeSecurityBlockingPageFactory::CreateLocalSelfSignedBlockingPage(
    content::WebContents* web_contents,
    net::Error cert_error,
    const net::SSLInfo& ssl_info,
    const GURL& request_url,
    int options_mask,
    const base::Time& time_triggered,
    const GURL& support_url) {
  bool overridable = SSLBlockingPage::IsOverridable(options_mask);
  std::unique_ptr<ContentMetricsHelper> metrics_helper(
      CreateMetricsHelperAndStartRecording(
          web_contents, request_url,
          overridable ? "ssl_overridable" : "ssl_nonoverridable", overridable));

  LogSafeBrowsingSecuritySensitiveAction(
      safe_browsing::SafeBrowsingMetricsCollectorFactory::GetForProfile(
          Profile::FromBrowserContext(web_contents->GetBrowserContext())));

  auto controller_client = std::make_unique<SSLErrorControllerClient>(
      web_contents, ssl_info, cert_error, request_url,
      std::move(metrics_helper), CreateSettingsPageHelper());

  auto page = std::make_unique<LocalSelfSignedBlockingPage>(
      web_contents, cert_error, ssl_info, request_url, options_mask,
      time_triggered, support_url, overridable,
      /*can_show_enhanced_protection_message=*/true,
      std::move(controller_client));

  return page;
}

std::unique_ptr<CaptivePortalBlockingPage>
ChromeSecurityBlockingPageFactory::CreateCaptivePortalBlockingPage(
    content::WebContents* web_contents,
    const GURL& request_url,
    const GURL& login_url,
    const net::SSLInfo& ssl_info,
    net::Error cert_error) {
  auto page = std::make_unique<CaptivePortalBlockingPage>(
      web_contents, request_url, login_url,
      /*can_show_enhanced_protection_message=*/true, ssl_info,
      std::make_unique<SSLErrorControllerClient>(
          web_contents, ssl_info, cert_error, request_url,
          CreateMetricsHelperAndStartRecording(web_contents, request_url,
                                               "captive_portal", false),
          CreateSettingsPageHelper()),
      base::BindRepeating(&OpenLoginPage));

  return page;
}

std::unique_ptr<BadClockBlockingPage>
ChromeSecurityBlockingPageFactory::CreateBadClockBlockingPage(
    content::WebContents* web_contents,
    net::Error cert_error,
    const net::SSLInfo& ssl_info,
    const GURL& request_url,
    const base::Time& time_triggered,
    ssl_errors::ClockState clock_state) {
  auto page = std::make_unique<BadClockBlockingPage>(
      web_contents, cert_error, ssl_info, request_url, time_triggered,
      /*can_show_enhanced_protection_message=*/true, clock_state,
      std::make_unique<SSLErrorControllerClient>(
          web_contents, ssl_info, cert_error, request_url,
          CreateMetricsHelperAndStartRecording(web_contents, request_url,
                                               "bad_clock", false),
          CreateSettingsPageHelper()));

  return page;
}

std::unique_ptr<MITMSoftwareBlockingPage>
ChromeSecurityBlockingPageFactory::CreateMITMSoftwareBlockingPage(
    content::WebContents* web_contents,
    net::Error cert_error,
    const GURL& request_url,
    const net::SSLInfo& ssl_info,
    const std::string& mitm_software_name) {
  Profile* profile =
      Profile::FromBrowserContext(web_contents->GetBrowserContext());
  LogSafeBrowsingSecuritySensitiveAction(
      safe_browsing::SafeBrowsingMetricsCollectorFactory::GetForProfile(
          profile));

  auto page = std::make_unique<MITMSoftwareBlockingPage>(
      web_contents, cert_error, request_url,
      /*can_show_enhanced_protection_message=*/true, ssl_info,
      mitm_software_name, IsEnterpriseManaged(profile),
      std::make_unique<SSLErrorControllerClient>(
          web_contents, ssl_info, cert_error, request_url,
          CreateMetricsHelperAndStartRecording(web_contents, request_url,
                                               "mitm_software", false),
          CreateSettingsPageHelper()));

  return page;
}

std::unique_ptr<BlockedInterceptionBlockingPage>
ChromeSecurityBlockingPageFactory::CreateBlockedInterceptionBlockingPage(
    content::WebContents* web_contents,
    net::Error cert_error,
    const GURL& request_url,
    const net::SSLInfo& ssl_info) {
  LogSafeBrowsingSecuritySensitiveAction(
      safe_browsing::SafeBrowsingMetricsCollectorFactory::GetForProfile(
          Profile::FromBrowserContext(web_contents->GetBrowserContext())));

  auto page = std::make_unique<BlockedInterceptionBlockingPage>(
      web_contents, cert_error, request_url,
      /*can_show_enhanced_protection_message=*/true, ssl_info,
      std::make_unique<SSLErrorControllerClient>(
          web_contents, ssl_info, cert_error, request_url,
          CreateMetricsHelperAndStartRecording(web_contents, request_url,
                                               "blocked_interception", false),
          CreateSettingsPageHelper()));

  return page;
}

std::unique_ptr<security_interstitials::InsecureFormBlockingPage>
ChromeSecurityBlockingPageFactory::CreateInsecureFormBlockingPage(
    content::WebContents* web_contents,
    const GURL& request_url) {
  std::unique_ptr<InsecureFormControllerClient> client =
      std::make_unique<InsecureFormControllerClient>(web_contents, request_url);
  auto page =
      std::make_unique<security_interstitials::InsecureFormBlockingPage>(
          web_contents, request_url, std::move(client));
  return page;
}

std::unique_ptr<security_interstitials::HttpsOnlyModeBlockingPage>
ChromeSecurityBlockingPageFactory::CreateHttpsOnlyModeBlockingPage(
    content::WebContents* web_contents,
    const GURL& request_url,
    security_interstitials::https_only_mode::HttpInterstitialState
        interstitial_state,
    std::optional<std::string> url_type_param,
    security_interstitials::HttpsOnlyModeBlockingPage::MetricsCallback
        metrics_callback) {
  std::unique_ptr<HttpsOnlyModeControllerClient> client =
      std::make_unique<HttpsOnlyModeControllerClient>(
          web_contents, request_url, CreateSettingsPageHelper());
  Profile* profile =
      Profile::FromBrowserContext(web_contents->GetBrowserContext());

  if (url_type_param) {
    if (*url_type_param == "advanced_protection") {
      interstitial_state.enabled_by_advanced_protection = true;
    } else if (*url_type_param == "site_engagement") {
      interstitial_state.enabled_by_engagement_heuristic = true;
    } else if (*url_type_param == "typically_secure") {
      interstitial_state.enabled_by_typically_secure_browsing = true;
    } else if (*url_type_param == "incognito") {
      interstitial_state.enabled_by_incognito = true;
    }
  } else {
    interstitial_state.enabled_by_advanced_protection =
        profile &&
        safe_browsing::AdvancedProtectionStatusManagerFactory::GetForProfile(
            profile)
            ->IsUnderAdvancedProtection();
    // HFM interstitial with Site Engagement heuristic is only shown if the
    // feature flag is enabled, so update the relevant flag here.
    interstitial_state.enabled_by_engagement_heuristic =
        interstitial_state.enabled_by_engagement_heuristic &&
        base::FeatureList::IsEnabled(
            features::kHttpsFirstModeV2ForEngagedSites);
  }

  auto page =
      std::make_unique<security_interstitials::HttpsOnlyModeBlockingPage>(
          web_contents, request_url, std::move(client), interstitial_state,
          metrics_callback);
  return page;
}

#if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION)

// Open a login tab or popup for the captive portal login page.
void OpenLoginTab(BrowserWindowInterface* browser,
                  captive_portal::CaptivePortalWindowType portal_type) {
  // We only end up here when a captive portal result was received, so it's safe
  // to assume profile has a captive_portal::CaptivePortalService.
  NavigateParams params(
      browser,
      CaptivePortalServiceFactory::GetForProfile(browser->GetProfile())
          ->test_url(),
      ui::PAGE_TRANSITION_TYPED);
  WindowOpenDisposition disposition;
  switch (portal_type) {
    case captive_portal::CaptivePortalWindowType::kPopup:
      disposition = WindowOpenDisposition::NEW_POPUP;
      break;
    case captive_portal::CaptivePortalWindowType::kTab:
      disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
      break;
    default:
      NOTREACHED() << "Invalid captive portal window type";
  }
  params.captive_portal_window_type = portal_type;
  params.disposition = disposition;
  Navigate(&params);

  content::WebContents* new_contents = params.navigated_or_inserted_contents;
  captive_portal::CaptivePortalTabHelper* captive_portal_tab_helper =
      captive_portal::CaptivePortalTabHelper::FromWebContents(new_contents);
  captive_portal_tab_helper->SetIsLoginTab();
}

// static
void ChromeSecurityBlockingPageFactory::OpenLoginPageForBrowser(
    base::FunctionRef<BrowserWindowInterface*()> get_browser,
    bool focus_tab) {
  SecureDnsConfig secure_dns_config =
      SystemNetworkContextManager::GetStubResolverConfigReader()
          ->GetSecureDnsConfiguration(
              false /* force_check_parental_controls */);

  // If the DNS mode is SECURE, captive portal login tabs should be opened in
  // new popup windows where secure DNS will be disabled.
  if (secure_dns_config.mode() == net::SecureDnsMode::kSecure) {
    // If there is already a captive portal popup window, do not create another.
    bool found_login_tab = false;
    tabs::ForEachTabInterface([&found_login_tab](tabs::TabInterface* tab) {
      content::WebContents* const contents = tab->GetContents();
      captive_portal::CaptivePortalTabHelper* const captive_portal_tab_helper =
          captive_portal::CaptivePortalTabHelper::FromWebContents(contents);
      if (captive_portal_tab_helper->IsLoginTab()) {
        BrowserWindowInterface* const browser_with_login_tab =
            tab->GetBrowserWindowInterface();
        browser_with_login_tab->GetWindow()->Show();
        TabStripModel* const tab_strip_model =
            browser_with_login_tab->GetTabStripModel();
        tab_strip_model->ActivateTabAt(tab_strip_model->GetIndexOfTab(tab));
        found_login_tab = true;
      }
      return !found_login_tab;
    });
    if (found_login_tab) {
      return;
    }
  }

  BrowserWindowInterface* browser = get_browser();
  // If the Profile doesn't have a tabbed browser window open, do nothing.
  if (!browser) {
    return;
  }
  // Create a captive portal popup window.
  if (secure_dns_config.mode() == net::SecureDnsMode::kSecure) {
    OpenLoginTab(browser, captive_portal::CaptivePortalWindowType::kPopup);
    return;
  }

  // Check if the Profile's topmost browser window already has a login tab.
  // If so, do nothing.
  // TODO(mmenke):  Consider focusing that tab, at least if this is the tab
  //                helper for the currently active tab for the profile.
  for (int i = 0; i < browser->GetTabStripModel()->count(); ++i) {
    content::WebContents* contents =
        browser->GetTabStripModel()->GetWebContentsAt(i);
    captive_portal::CaptivePortalTabHelper* captive_portal_tab_helper =
        captive_portal::CaptivePortalTabHelper::FromWebContents(contents);
    if (captive_portal_tab_helper->IsLoginTab()) {
      if (focus_tab) {
        browser->GetTabStripModel()->ActivateTabAt(i);
      }
      return;
    }
  }

  // Otherwise, open a login tab.
  OpenLoginTab(browser, captive_portal::CaptivePortalWindowType::kTab);
}

// static
void ChromeSecurityBlockingPageFactory::OpenLoginTabForWebContents(
    content::WebContents* web_contents,
    bool focus_tab) {
  OpenLoginPageForBrowser(
      [&web_contents]() {
        return GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
            web_contents);
      },
      focus_tab);
}

// static
void ChromeSecurityBlockingPageFactory::
    OpenLoginPageInAnyTabbedBrowserOrCreateOne(Profile* profile,
                                               bool focus_tab) {
  auto lambda = [&profile]() -> BrowserWindowInterface* {
    BrowserWindowInterface* browser =
        ProfileBrowserCollection::GetForProfile(profile)->FindTabbedBrowser();
    // Create browser if not exists.
    if (!browser && GetBrowserWindowCreationStatusForProfile(*profile) ==
                        BrowserWindowInterface::CreationStatus::kOk) {
      BrowserWindowCreateParams params(profile, /*from_user_gesture=*/true);
      browser = CreateBrowserWindow(std::move(params));
    }

    if (browser && browser->GetWindow()) {
      browser->GetWindow()->Activate();
      return browser;
    } else {
      return nullptr;
    }
  };

  OpenLoginPageForBrowser(lambda, focus_tab);
}

#endif  // BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION)

// static
bool ChromeSecurityBlockingPageFactory::IsEnterpriseManaged(Profile* profile) {
  // Return the value of the testing flag if it's set.
  if (g_is_enterprise_managed_for_testing == ENTERPRISE_MANAGED_STATUS_TRUE) {
    return true;
  }
  if (g_is_enterprise_managed_for_testing == ENTERPRISE_MANAGED_STATUS_FALSE) {
    return false;
  }

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
  if (base::IsManagedOrEnterpriseDevice()) {
    return true;
  }
#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)

#if BUILDFLAG(IS_CHROMEOS)
  auto* connector =
      g_browser_process->platform_part()->browser_policy_connector_ash();
  if (connector && connector->IsDeviceEnterpriseManaged()) {
    return true;
  }
#endif  // BUILDFLAG(IS_CHROMEOS)

  if (profile && profile->GetProfilePolicyConnector() &&
      profile->GetProfilePolicyConnector()->IsManaged()) {
    return true;
  }
  return false;
}

// static
void ChromeSecurityBlockingPageFactory::SetEnterpriseManagedForTesting(
    bool enterprise_managed) {
  if (enterprise_managed) {
    g_is_enterprise_managed_for_testing = ENTERPRISE_MANAGED_STATUS_TRUE;
  } else {
    g_is_enterprise_managed_for_testing = ENTERPRISE_MANAGED_STATUS_FALSE;
  }
}
