blob: f04859704b995f099d1f5e616e0b31b102d9aca7 [file] [log] [blame]
// 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/bind.h"
#include "base/callback.h"
#include "base/files/file_util.h"
#include "base/guid.h"
#include "base/json/json_writer.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread_restrictions.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/chromeos/login/helper.h"
#include "chrome/browser/chromeos/login/signin_partition_manager.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/policy/browser_policy_connector_chromeos.h"
#include "chrome/browser/chromeos/policy/device_policy_builder.h"
#include "chrome/browser/chromeos/policy/device_policy_cros_browser_test.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/chromeos/settings/scoped_testing_cros_settings.h"
#include "chrome/browser/chromeos/settings/stub_cros_settings_provider.h"
#include "chrome/browser/policy/test/local_policy_test_server.h"
#include "chrome/browser/ui/login/login_handler.h"
#include "chrome/browser/ui/webui/signin/signin_utils.h"
#include "chromeos/constants/chromeos_switches.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/fake_session_manager_client.h"
#include "components/content_settings/core/common/pref_names.h"
#include "components/guest_view/browser/guest_view_manager.h"
#include "components/onc/onc_constants.h"
#include "components/onc/onc_pref_names.h"
#include "components/policy/core/common/cloud/device_management_service.h"
#include "components/policy/core/common/policy_service.h"
#include "components/policy/core/common/policy_switches.h"
#include "components/policy/policy_constants.h"
#include "components/policy/proto/chrome_device_policy.pb.h"
#include "components/prefs/pref_change_registrar.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "crypto/nss_util_internal.h"
#include "crypto/scoped_test_system_nss_key_slot.h"
#include "media/base/media_switches.h"
#include "net/cookies/canonical_cookie.h"
#include "net/test/cert_test_util.h"
#include "net/test/spawned_test_server/spawned_test_server.h"
#include "net/test/test_data_directory.h"
#include "services/network/public/mojom/cookie_manager.mojom.h"
namespace em = enterprise_management;
namespace chromeos {
namespace {
constexpr char kTestGuid[] = "cccccccc-cccc-4ccc-0ccc-ccccccccccc1";
constexpr char kTestCookieName[] = "TestCookie";
constexpr char kTestCookieValue[] = "present";
constexpr char kTestCookieHost[] = "host1.com";
void InjectCookieDoneCallback(base::OnceClosure done_closure, bool result) {
ASSERT_TRUE(result);
std::move(done_closure).Run();
}
// Injects a cookie into |storage_partition|, so we can test for cookie presence
// later to infer if the StoragePartition has been cleared.
void InjectCookie(content::StoragePartition* storage_partition) {
network::mojom::CookieManagerPtr cookie_manager;
storage_partition->GetNetworkContext()->GetCookieManager(
mojo::MakeRequest(&cookie_manager));
base::RunLoop run_loop;
cookie_manager->SetCanonicalCookie(
net::CanonicalCookie(kTestCookieName, kTestCookieValue, kTestCookieHost,
"/", base::Time(), base::Time(), base::Time(), false,
false, net::CookieSameSite::NO_RESTRICTION,
net::COOKIE_PRIORITY_MEDIUM),
"http", false,
base::Bind(&InjectCookieDoneCallback, run_loop.QuitClosure()));
run_loop.Run();
}
void GetAllCookiesCallback(std::string* cookies_out,
base::OnceClosure done_closure,
const std::vector<net::CanonicalCookie>& cookies) {
*cookies_out = net::CanonicalCookie::BuildCookieLine(cookies);
std::move(done_closure).Run();
}
// Returns all cookies present in |storage_partition| as a HTTP header cookie
// line. Will be an empty string if there are no cookies.
std::string GetAllCookies(content::StoragePartition* storage_partition) {
network::mojom::CookieManagerPtr cookie_manager;
storage_partition->GetNetworkContext()->GetCookieManager(
mojo::MakeRequest(&cookie_manager));
std::string cookies;
base::RunLoop run_loop;
cookie_manager->GetAllCookies(
base::BindOnce(&GetAllCookiesCallback, &cookies, run_loop.QuitClosure()));
run_loop.Run();
return cookies;
}
void PolicyChangedCallback(base::RepeatingClosure callback,
const base::Value* old_value,
const base::Value* new_value) {
callback.Run();
}
// Spins the loop until a notification is received from |prefs| that the value
// of |pref_name| has changed. If the notification is received before Wait()
// has been called, Wait() returns immediately and no loop is spun.
class PrefChangeWatcher {
public:
PrefChangeWatcher(const std::string& pref_name, PrefService* prefs);
void Wait();
private:
void OnPrefChange();
bool pref_changed_ = false;
base::RunLoop run_loop_;
PrefChangeRegistrar registrar_;
DISALLOW_COPY_AND_ASSIGN(PrefChangeWatcher);
};
PrefChangeWatcher::PrefChangeWatcher(const std::string& pref_name,
PrefService* prefs) {
registrar_.Init(prefs);
registrar_.Add(pref_name, base::Bind(&PrefChangeWatcher::OnPrefChange,
base::Unretained(this)));
}
void PrefChangeWatcher::Wait() {
if (!pref_changed_)
run_loop_.Run();
}
void PrefChangeWatcher::OnPrefChange() {
pref_changed_ = true;
run_loop_.Quit();
}
} // namespace
class WebviewLoginTest : public OobeBaseTest {
public:
WebviewLoginTest() = default;
~WebviewLoginTest() override = default;
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitch(switches::kOobeSkipPostLogin);
command_line->AppendSwitch(::switches::kUseFakeDeviceForMediaStream);
OobeBaseTest::SetUpCommandLine(command_line);
}
protected:
void ClickNext() {
ExecuteJsInSigninFrame("document.getElementById('nextButton').click();");
}
void ExpectIdentifierPage() {
// First page: no back button, no close button, refresh button, #identifier
// input field.
test::OobeJS().ExpectTrue("!$('gaia-navigation').backVisible");
test::OobeJS().ExpectTrue("!$('gaia-navigation').closeVisible");
test::OobeJS().ExpectTrue("$('gaia-navigation').refreshVisible");
test::OobeJS().ExpectTrue(
"$('signin-frame').src.indexOf('#identifier') != -1");
}
void ExpectPasswordPage() {
// Second page: back button, close button, no refresh button,
// #challengepassword input field.
test::OobeJS().ExpectTrue("$('gaia-navigation').backVisible");
test::OobeJS().ExpectTrue("$('gaia-navigation').closeVisible");
test::OobeJS().ExpectTrue("!$('gaia-navigation').refreshVisible");
test::OobeJS().ExpectTrue(
"$('signin-frame').src.indexOf('#challengepassword') != -1");
}
bool WebViewVisited(content::BrowserContext* browser_context,
content::StoragePartition* expected_storage_partition,
bool* out_web_view_found,
content::WebContents* guest_contents) {
content::StoragePartition* guest_storage_partition =
content::BrowserContext::GetStoragePartition(
browser_context, guest_contents->GetSiteInstance());
if (guest_storage_partition == expected_storage_partition)
*out_web_view_found = true;
// Returns true if found - this will exit the iteration early.
return *out_web_view_found;
}
// Returns true if a webview which has a WebContents associated with
// |storage_partition| currently exists in the login UI's main WebContents.
bool IsLoginScreenHasWebviewWithStoragePartition(
content::StoragePartition* storage_partition) {
bool web_view_found = false;
content::WebContents* web_contents = GetLoginUI()->GetWebContents();
content::BrowserContext* browser_context =
web_contents->GetBrowserContext();
guest_view::GuestViewManager* guest_view_manager =
guest_view::GuestViewManager::FromBrowserContext(browser_context);
guest_view_manager->ForEachGuest(
web_contents,
base::BindRepeating(&WebviewLoginTest::WebViewVisited,
base::Unretained(this), browser_context,
storage_partition, &web_view_found));
return web_view_found;
}
protected:
chromeos::ScopedTestingCrosSettings scoped_testing_cros_settings_;
FakeGaiaMixin fake_gaia_{&mixin_host_, embedded_test_server()};
private:
DISALLOW_COPY_AND_ASSIGN(WebviewLoginTest);
};
// Basic signin with username and password.
IN_PROC_BROWSER_TEST_F(WebviewLoginTest, Basic) {
WaitForGaiaPageLoadAndPropertyUpdate();
ExpectIdentifierPage();
SetSignFormField("identifier", FakeGaiaMixin::kFakeUserEmail);
ClickNext();
WaitForGaiaPageBackButtonUpdate();
ExpectPasswordPage();
content::WindowedNotificationObserver session_start_waiter(
chrome::NOTIFICATION_SESSION_STARTED,
content::NotificationService::AllSources());
SetSignFormField("services", "[]");
SetSignFormField("password", FakeGaiaMixin::kFakeUserPassword);
ClickNext();
session_start_waiter.Wait();
}
// Fails: http://crbug.com/512648.
IN_PROC_BROWSER_TEST_F(WebviewLoginTest, DISABLED_BackButton) {
WaitForGaiaPageLoadAndPropertyUpdate();
// Start with identifer page.
ExpectIdentifierPage();
// Move to password page.
SetSignFormField("identifier", FakeGaiaMixin::kFakeUserEmail);
ClickNext();
WaitForGaiaPageBackButtonUpdate();
ExpectPasswordPage();
// Click back to identifier page.
test::OobeJS().Evaluate("$('gaia-navigation').$.backButton.click();");
WaitForGaiaPageBackButtonUpdate();
ExpectIdentifierPage();
// Click next to password page, user id is remembered.
ClickNext();
WaitForGaiaPageBackButtonUpdate();
ExpectPasswordPage();
content::WindowedNotificationObserver session_start_waiter(
chrome::NOTIFICATION_SESSION_STARTED,
content::NotificationService::AllSources());
// Finish sign-up.
SetSignFormField("services", "[]");
SetSignFormField("password", FakeGaiaMixin::kFakeUserPassword);
ClickNext();
session_start_waiter.Wait();
}
// Create new account option should be available only if the settings allow it.
IN_PROC_BROWSER_TEST_F(WebviewLoginTest, AllowNewUser) {
WaitForGaiaPageLoad();
std::string frame_url = "$('gaia-signin').gaiaAuthHost_.reloadUrl_";
// New users are allowed.
test::OobeJS().ExpectTrue(frame_url + ".search('flow=nosignup') == -1");
// Disallow new users - we also need to set a whitelist due to weird logic.
scoped_testing_cros_settings_.device_settings()->Set(kAccountsPrefUsers,
base::ListValue());
scoped_testing_cros_settings_.device_settings()->Set(
kAccountsPrefAllowNewUser, base::Value(false));
WaitForGaiaPageReload();
// flow=nosignup indicates that user creation is not allowed.
test::OobeJS().ExpectTrue(frame_url + ".search('flow=nosignup') != -1");
}
IN_PROC_BROWSER_TEST_F(WebviewLoginTest, EmailPrefill) {
WaitForGaiaPageLoad();
test::OobeJS().ExecuteAsync("Oobe.showSigninUI('user@example.com')");
WaitForGaiaPageReload();
EXPECT_EQ(fake_gaia_.fake_gaia()->prefilled_email(), "user@example.com");
}
IN_PROC_BROWSER_TEST_F(WebviewLoginTest, StoragePartitionHandling) {
WaitForGaiaPageLoadAndPropertyUpdate();
// Start with identifer page.
ExpectIdentifierPage();
// WebContents of the embedding frame
content::WebContents* web_contents = GetLoginUI()->GetWebContents();
content::BrowserContext* browser_context = web_contents->GetBrowserContext();
std::string signin_frame_partition_name_1 =
test::OobeJS().GetString("$('signin-frame').partition");
content::StoragePartition* signin_frame_partition_1 =
login::GetSigninPartition();
EXPECT_FALSE(signin_frame_partition_name_1.empty());
EXPECT_EQ(login::SigninPartitionManager::Factory::GetForBrowserContext(
browser_context)
->GetCurrentStoragePartitionName(),
signin_frame_partition_name_1);
EXPECT_TRUE(
IsLoginScreenHasWebviewWithStoragePartition(signin_frame_partition_1));
// Inject a cookie into the currently used StoragePartition, so we can test
// later if it has been cleared.
InjectCookie(signin_frame_partition_1);
// Press the back button at a sign-in screen without pre-existing users to
// start a new sign-in attempt.
test::OobeJS().Evaluate("$('signin-back-button').fire('tap')");
WaitForGaiaPageBackButtonUpdate();
// Expect that we got back to the identifier page, as there are no known users
// so the sign-in screen will not display user pods.
ExpectIdentifierPage();
std::string signin_frame_partition_name_2 =
test::OobeJS().GetString("$('signin-frame').partition");
content::StoragePartition* signin_frame_partition_2 =
login::GetSigninPartition();
EXPECT_FALSE(signin_frame_partition_name_2.empty());
EXPECT_EQ(login::SigninPartitionManager::Factory::GetForBrowserContext(
browser_context)
->GetCurrentStoragePartitionName(),
signin_frame_partition_name_2);
EXPECT_TRUE(
IsLoginScreenHasWebviewWithStoragePartition(signin_frame_partition_2));
InjectCookie(signin_frame_partition_2);
// Make sure that the partitions differ and that the old one is not in use
// anymore.
EXPECT_NE(signin_frame_partition_name_1, signin_frame_partition_name_2);
EXPECT_NE(signin_frame_partition_1, signin_frame_partition_2);
EXPECT_FALSE(
IsLoginScreenHasWebviewWithStoragePartition(signin_frame_partition_1));
// The StoragePartition which is not in use is supposed to have been cleared.
EXPECT_EQ("", GetAllCookies(signin_frame_partition_1));
EXPECT_NE("", GetAllCookies(signin_frame_partition_2));
}
// Tests that requesting webcam access from the login screen works correctly.
// This is needed for taking profile pictures.
IN_PROC_BROWSER_TEST_F(WebviewLoginTest, RequestCamera) {
WaitForGaiaPageLoad();
// Video devices should be allowed from the login screen.
content::WebContents* web_contents = GetLoginUI()->GetWebContents();
bool getUserMediaSuccess = false;
ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
web_contents->GetMainFrame(),
"navigator.getUserMedia("
" {video: true},"
" function() { window.domAutomationController.send(true); },"
" function() { window.domAutomationController.send(false); });",
&getUserMediaSuccess));
EXPECT_TRUE(getUserMediaSuccess);
// Audio devices should be denied from the login screen.
ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
web_contents->GetMainFrame(),
"navigator.getUserMedia("
" {audio: true},"
" function() { window.domAutomationController.send(true); },"
" function() { window.domAutomationController.send(false); });",
&getUserMediaSuccess));
EXPECT_FALSE(getUserMediaSuccess);
}
class WebviewClientCertsLoginTest : public WebviewLoginTest {
public:
WebviewClientCertsLoginTest() {}
// Installs a testing system slot and imports a client certificate into it.
void SetUpClientCertInSystemSlot() {
{
bool system_slot_constructed_successfully = false;
base::RunLoop loop;
base::PostTaskWithTraitsAndReply(
FROM_HERE, {content::BrowserThread::IO},
base::BindOnce(&WebviewClientCertsLoginTest::SetUpTestSystemSlotOnIO,
base::Unretained(this),
&system_slot_constructed_successfully),
loop.QuitClosure());
loop.Run();
ASSERT_TRUE(system_slot_constructed_successfully);
}
// Import a second client cert signed by another CA than client_1 into the
// system wide key slot.
base::ScopedAllowBlockingForTesting allow_io;
client_cert_ = net::ImportClientCertAndKeyFromFile(
net::GetTestCertsDirectory(), "client_1.pem", "client_1.pk8",
test_system_slot_->slot());
ASSERT_TRUE(client_cert_.get());
}
// Sets up the DeviceLoginScreenAutoSelectCertificateForUrls policy.
void SetAutoSelectCertificatePatterns(
const std::vector<std::string>& autoselect_patterns) {
em::ChromeDeviceSettingsProto& proto(
device_policy_test_helper_.device_policy()->payload());
auto* field =
proto.mutable_device_login_screen_auto_select_certificate_for_urls();
for (const std::string& autoselect_pattern : autoselect_patterns)
field->add_login_screen_auto_select_certificate_rules(autoselect_pattern);
device_policy_test_helper_.device_policy()->Build();
fake_session_manager_client_->set_device_policy(
device_policy_test_helper_.device_policy()->GetBlob());
PrefChangeWatcher watcher(prefs::kManagedAutoSelectCertificateForUrls,
ProfileHelper::GetSigninProfile()->GetPrefs());
fake_session_manager_client_->OnPropertyChangeComplete(true);
watcher.Wait();
}
// Adds the certificate from |authority_file_path| (PEM) as untrusted
// authority in device OpenNetworkConfiguration policy.
void SetIntermediateAuthorityInDeviceOncPolicy(
const base::FilePath& authority_file_path) {
std::string x509_contents;
{
base::ScopedAllowBlockingForTesting allow_io;
ASSERT_TRUE(base::ReadFileToString(authority_file_path, &x509_contents));
}
base::DictionaryValue onc_dict =
BuildDeviceOncDictForUntrustedAuthority(x509_contents);
em::ChromeDeviceSettingsProto& proto(
device_policy_test_helper_.device_policy()->payload());
base::JSONWriter::Write(onc_dict,
proto.mutable_open_network_configuration()
->mutable_open_network_configuration());
device_policy_test_helper_.device_policy()->Build();
fake_session_manager_client_->set_device_policy(
device_policy_test_helper_.device_policy()->GetBlob());
PrefChangeWatcher watcher(onc::prefs::kDeviceOpenNetworkConfiguration,
g_browser_process->local_state());
fake_session_manager_client_->OnPropertyChangeComplete(true);
watcher.Wait();
}
// Starts the Test HTTPS server with |ssl_options|.
void StartHttpsServer(const net::SpawnedTestServer::SSLOptions& ssl_options) {
https_server_ = std::make_unique<net::SpawnedTestServer>(
net::SpawnedTestServer::TYPE_HTTPS, ssl_options, base::FilePath());
ASSERT_TRUE(https_server_->Start());
}
// Requests |http_server_|'s client-cert test page in the webview with the id
// |webview_id|. Returns the content of the client-cert test page.
std::string RequestClientCertTestPageInFrame(const std::string& webview_id) {
const GURL url = https_server_->GetURL("client-cert");
content::TestNavigationObserver navigation_observer(url);
navigation_observer.WatchExistingWebContents();
navigation_observer.StartWatchingNewWebContents();
// TODO(https://crbug.com/830337): Remove the logs if flakiness is gone.
// If you see this after April 2019, please ping the owner of the above bug.
LOG(INFO) << "Triggering navigation to " << url.spec() << ".";
test::OobeJS().Evaluate(base::StringPrintf(
"$('%s').src='%s'", webview_id.c_str(), url.spec().c_str()));
navigation_observer.Wait();
LOG(INFO) << "Navigation done.";
content::WebContents* main_web_contents = GetLoginUI()->GetWebContents();
content::WebContents* frame_web_contents =
signin::GetAuthFrameWebContents(main_web_contents, webview_id);
test::JSChecker frame_js_checker(frame_web_contents);
const std::string https_reply_content =
frame_js_checker.GetString("document.body.textContent");
// TODO(https://crbug.com/830337): Remove this is if flakiness does not
// reproduce.
// If you see this after April 2019, please ping the owner of the above bug.
if (https_reply_content.empty()) {
base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(1000));
const std::string https_reply_content_after_sleep =
frame_js_checker.GetString("document.body.textContent");
if (!https_reply_content_after_sleep.empty())
LOG(INFO) << "Magic - textContent appeared after sleep.";
}
return https_reply_content;
}
void ShowEulaScreen() {
LoginDisplayHost::default_host()->StartWizard(OobeScreen::SCREEN_OOBE_EULA);
OobeScreenWaiter(OobeScreen::SCREEN_OOBE_EULA).Wait();
}
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitch(
switches::kDisableSigninFrameClientCertUserSelection);
WebviewLoginTest::SetUpCommandLine(command_line);
}
void SetUpInProcessBrowserTestFixture() override {
auto fake_session_manager_client =
std::make_unique<FakeSessionManagerClient>();
fake_session_manager_client_ = fake_session_manager_client.get();
DBusThreadManager::GetSetterForTesting()->SetSessionManagerClient(
std::move(fake_session_manager_client));
device_policy_test_helper_.InstallOwnerKey();
device_policy_test_helper_.MarkAsEnterpriseOwned();
fake_session_manager_client_->set_device_policy(
device_policy_test_helper_.device_policy()->GetBlob());
WebviewLoginTest::SetUpInProcessBrowserTestFixture();
}
void TearDownOnMainThread() override { TearDownTestSystemSlot(); }
private:
void SetUpTestSystemSlotOnIO(bool* out_system_slot_constructed_successfully) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
test_system_slot_ = std::make_unique<crypto::ScopedTestSystemNSSKeySlot>();
*out_system_slot_constructed_successfully =
test_system_slot_->ConstructedSuccessfully();
}
void TearDownTestSystemSlot() {
if (!test_system_slot_)
return;
base::RunLoop loop;
base::PostTaskWithTraitsAndReply(
FROM_HERE, {content::BrowserThread::IO},
base::BindOnce(&WebviewClientCertsLoginTest::TearDownTestSystemSlotOnIO,
base::Unretained(this)),
loop.QuitClosure());
loop.Run();
}
void TearDownTestSystemSlotOnIO() { test_system_slot_.reset(); }
// Builds a device ONC dictionary defining a single untrusted authority
// certificate.
base::DictionaryValue BuildDeviceOncDictForUntrustedAuthority(
const std::string& x509_authority_cert) {
base::DictionaryValue onc_certificate;
onc_certificate.SetKey(onc::certificate::kGUID, base::Value(kTestGuid));
onc_certificate.SetKey(onc::certificate::kType,
base::Value(onc::certificate::kAuthority));
onc_certificate.SetKey(onc::certificate::kX509,
base::Value(x509_authority_cert));
base::ListValue onc_certificates;
onc_certificates.GetList().emplace_back(std::move(onc_certificate));
base::DictionaryValue onc_dict;
onc_dict.SetKey(onc::toplevel_config::kCertificates,
std::move(onc_certificates));
onc_dict.SetKey(
onc::toplevel_config::kType,
base::Value(onc::toplevel_config::kUnencryptedConfiguration));
return onc_dict;
}
policy::DevicePolicyCrosTestHelper device_policy_test_helper_;
// Unowned pointer - owned by DBusThreadManager.
FakeSessionManagerClient* fake_session_manager_client_;
std::unique_ptr<crypto::ScopedTestSystemNSSKeySlot> test_system_slot_;
scoped_refptr<net::X509Certificate> client_cert_;
std::unique_ptr<net::SpawnedTestServer> https_server_;
DISALLOW_COPY_AND_ASSIGN(WebviewClientCertsLoginTest);
};
// Test that client certificate authentication using certificates from the
// system slot is enabled in the sign-in frame. The server does not request
// certificates signed by a specific authority.
IN_PROC_BROWSER_TEST_F(WebviewClientCertsLoginTest,
SigninFrameNoAuthorityGiven) {
ASSERT_NO_FATAL_FAILURE(SetUpClientCertInSystemSlot());
net::SpawnedTestServer::SSLOptions ssl_options;
ssl_options.request_client_certificate = true;
ASSERT_NO_FATAL_FAILURE(StartHttpsServer(ssl_options));
const std::vector<std::string> autoselect_patterns = {
R"({"pattern": "*", "filter": {"ISSUER": {"CN": "B CA"}}})"};
SetAutoSelectCertificatePatterns(autoselect_patterns);
WaitForGaiaPageLoadAndPropertyUpdate();
const std::string https_reply_content =
RequestClientCertTestPageInFrame(gaia_frame_parent_);
EXPECT_EQ(
"got client cert with fingerprint: "
"c66145f49caca4d1325db96ace0f12f615ba4981",
https_reply_content);
}
// Test that client certificate autoselect selects the right certificate even
// with multiple filters for the same pattern.
//
// Disabled due to flaky timeouts: https://crbug.com/830337.
IN_PROC_BROWSER_TEST_F(WebviewClientCertsLoginTest,
DISABLED_SigninFrameCertMultipleFiltersAutoSelected) {
ASSERT_NO_FATAL_FAILURE(SetUpClientCertInSystemSlot());
net::SpawnedTestServer::SSLOptions ssl_options;
ssl_options.request_client_certificate = true;
ASSERT_NO_FATAL_FAILURE(StartHttpsServer(ssl_options));
const std::vector<std::string> autoselect_patterns = {
R"({"pattern": "*", "filter": {"ISSUER": {"CN": "B CA"}}})",
R"({"pattern": "*", "filter": {"ISSUER": {"CN": "foo baz bar"}}})"};
SetAutoSelectCertificatePatterns(autoselect_patterns);
WaitForGaiaPageLoadAndPropertyUpdate();
const std::string https_reply_content =
RequestClientCertTestPageInFrame(gaia_frame_parent_);
EXPECT_EQ(
"got client cert with fingerprint: "
"c66145f49caca4d1325db96ace0f12f615ba4981",
https_reply_content);
}
// Test that if no client certificate is auto-selected using policy on the
// sign-in frame, the client does not send up any client certificate.
IN_PROC_BROWSER_TEST_F(WebviewClientCertsLoginTest,
SigninFrameCertNotAutoSelected) {
ASSERT_NO_FATAL_FAILURE(SetUpClientCertInSystemSlot());
net::SpawnedTestServer::SSLOptions ssl_options;
ssl_options.request_client_certificate = true;
ASSERT_NO_FATAL_FAILURE(StartHttpsServer(ssl_options));
WaitForGaiaPageLoadAndPropertyUpdate();
const std::string https_reply_content =
RequestClientCertTestPageInFrame(gaia_frame_parent_);
EXPECT_EQ("got no client cert", https_reply_content);
}
// Test that client certificate authentication using certificates from the
// system slot is enabled in the sign-in frame. The server requests
// a certificate signed by a specific authority.
IN_PROC_BROWSER_TEST_F(WebviewClientCertsLoginTest, SigninFrameAuthorityGiven) {
ASSERT_NO_FATAL_FAILURE(SetUpClientCertInSystemSlot());
net::SpawnedTestServer::SSLOptions ssl_options;
ssl_options.request_client_certificate = true;
base::FilePath ca_path =
net::GetTestCertsDirectory().Append(FILE_PATH_LITERAL("client_1_ca.pem"));
ssl_options.client_authorities.push_back(ca_path);
ASSERT_NO_FATAL_FAILURE(StartHttpsServer(ssl_options));
const std::vector<std::string> autoselect_patterns = {
R"({"pattern": "*", "filter": {"ISSUER": {"CN": "B CA"}}})"};
SetAutoSelectCertificatePatterns(autoselect_patterns);
WaitForGaiaPageLoadAndPropertyUpdate();
const std::string https_reply_content =
RequestClientCertTestPageInFrame(gaia_frame_parent_);
EXPECT_EQ(
"got client cert with fingerprint: "
"c66145f49caca4d1325db96ace0f12f615ba4981",
https_reply_content);
}
// Test that client certificate authentication using certificates from the
// system slot is enabled in the sign-in frame. The server requests
// a certificate signed by a specific authority. The client doesn't have a
// matching certificate.
IN_PROC_BROWSER_TEST_F(WebviewClientCertsLoginTest,
SigninFrameAuthorityGivenNoMatchingCert) {
ASSERT_NO_FATAL_FAILURE(SetUpClientCertInSystemSlot());
net::SpawnedTestServer::SSLOptions ssl_options;
ssl_options.request_client_certificate = true;
base::FilePath ca_path =
net::GetTestCertsDirectory().Append(FILE_PATH_LITERAL("client_2_ca.pem"));
ssl_options.client_authorities.push_back(ca_path);
ASSERT_NO_FATAL_FAILURE(StartHttpsServer(ssl_options));
const std::vector<std::string> autoselect_patterns = {
R"({"pattern": "*", "filter": {"ISSUER": {"CN": "B CA"}}})"};
SetAutoSelectCertificatePatterns(autoselect_patterns);
WaitForGaiaPageLoadAndPropertyUpdate();
const std::string https_reply_content =
RequestClientCertTestPageInFrame(gaia_frame_parent_);
EXPECT_EQ("got no client cert", https_reply_content);
}
// Test that client certificate will not be discovered if the server requests
// certificates signed by a root authority, the installed certificate has been
// issued by an intermediate authority, and the intermediate authority is not
// known on the device (it has not been made available through device ONC
// policy).
IN_PROC_BROWSER_TEST_F(WebviewClientCertsLoginTest,
SigninFrameIntermediateAuthorityUnknown) {
ASSERT_NO_FATAL_FAILURE(SetUpClientCertInSystemSlot());
net::SpawnedTestServer::SSLOptions ssl_options;
ssl_options.request_client_certificate = true;
base::FilePath ca_path = net::GetTestCertsDirectory().Append(
FILE_PATH_LITERAL("client_root_ca.pem"));
ssl_options.client_authorities.push_back(ca_path);
ASSERT_NO_FATAL_FAILURE(StartHttpsServer(ssl_options));
const std::vector<std::string> autoselect_patterns = {
R"({"pattern": "*", "filter": {"ISSUER": {"CN": "B CA"}}})"};
SetAutoSelectCertificatePatterns(autoselect_patterns);
WaitForGaiaPageLoadAndPropertyUpdate();
const std::string https_reply_content =
RequestClientCertTestPageInFrame(gaia_frame_parent_);
EXPECT_EQ("got no client cert", https_reply_content);
}
// Test that client certificate will be discovered if the server requests
// certificates signed by a root authority, the installed certificate has been
// issued by an intermediate authority, and the intermediate authority is
// known on the device (it has been made available through device ONC policy).
IN_PROC_BROWSER_TEST_F(WebviewClientCertsLoginTest,
SigninFrameIntermediateAuthorityKnown) {
ASSERT_NO_FATAL_FAILURE(SetUpClientCertInSystemSlot());
net::SpawnedTestServer::SSLOptions ssl_options;
ssl_options.request_client_certificate = true;
base::FilePath ca_path = net::GetTestCertsDirectory().Append(
FILE_PATH_LITERAL("client_root_ca.pem"));
ssl_options.client_authorities.push_back(ca_path);
ASSERT_NO_FATAL_FAILURE(StartHttpsServer(ssl_options));
const std::vector<std::string> autoselect_patterns = {
R"({"pattern": "*", "filter": {"ISSUER": {"CN": "B CA"}}})"};
SetAutoSelectCertificatePatterns(autoselect_patterns);
base::FilePath intermediate_ca_path =
net::GetTestCertsDirectory().Append(FILE_PATH_LITERAL("client_1_ca.pem"));
ASSERT_NO_FATAL_FAILURE(
SetIntermediateAuthorityInDeviceOncPolicy(intermediate_ca_path));
WaitForGaiaPageLoadAndPropertyUpdate();
const std::string https_reply_content =
RequestClientCertTestPageInFrame(gaia_frame_parent_);
EXPECT_EQ(
"got client cert with fingerprint: "
"c66145f49caca4d1325db96ace0f12f615ba4981",
https_reply_content);
}
// Tests that client certificate authentication is not enabled in a webview on
// the sign-in screen which is not the sign-in frame. In this case, the EULA
// webview is used.
// TODO(pmarko): This is DISABLED because the eula UI it depends on has been
// deprecated and removed. https://crbug.com/849710.
IN_PROC_BROWSER_TEST_F(WebviewClientCertsLoginTest,
DISABLED_ClientCertRequestedInOtherWebView) {
ASSERT_NO_FATAL_FAILURE(SetUpClientCertInSystemSlot());
net::SpawnedTestServer::SSLOptions ssl_options;
ssl_options.request_client_certificate = true;
ASSERT_NO_FATAL_FAILURE(StartHttpsServer(ssl_options));
const std::vector<std::string> autoselect_patterns = {
R"({"pattern": "*", "filter": {"ISSUER": {"CN": "B CA"}}})"};
SetAutoSelectCertificatePatterns(autoselect_patterns);
ShowEulaScreen();
// Use |watch_new_webcontents| because the EULA webview has not navigated yet.
const std::string https_reply_content =
RequestClientCertTestPageInFrame("cros-eula-frame");
EXPECT_EQ("got no client cert", https_reply_content);
}
class WebviewProxyAuthLoginTest : public WebviewLoginTest {
public:
WebviewProxyAuthLoginTest()
: auth_proxy_server_(std::make_unique<net::SpawnedTestServer>(
net::SpawnedTestServer::TYPE_BASIC_AUTH_PROXY,
base::FilePath())) {}
protected:
void SetUp() override {
// Start proxy server
auth_proxy_server_->set_redirect_connect_to_localhost(true);
ASSERT_TRUE(auth_proxy_server_->Start());
// Prepare device policy which will be used for two purposes:
// - given to |fake_session_manager_client_|, so the device appears to have
// registered for policy.
// - the payload is given to |policy_test_server_|, so we can download fresh
// policy.
device_policy_test_helper_.device_policy()
->policy_data()
.set_public_key_version(1);
device_policy_test_helper_.device_policy()->Build();
// Start policy server. Use the DMToken and DeviceId from PolicyBuilder.
// These also used in |device_policy_test_helper_| and was passed to
// |fake_session_manager_client_| above, so the device will request policy
// with these identifiers.
policy_test_server_.RegisterClient(policy::PolicyBuilder::kFakeToken,
policy::PolicyBuilder::kFakeDeviceId,
{} /* state_keys */);
UpdateServedPolicyFromDevicePolicyTestHelper();
ASSERT_TRUE(policy_test_server_.Start());
WebviewLoginTest::SetUp();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitchASCII(
::switches::kProxyServer,
auth_proxy_server_->host_port_pair().ToString());
command_line->AppendSwitchASCII(policy::switches::kDeviceManagementUrl,
policy_test_server_.GetServiceURL().spec());
WebviewLoginTest::SetUpCommandLine(command_line);
}
void SetUpInProcessBrowserTestFixture() override {
WebviewLoginTest::SetUpInProcessBrowserTestFixture();
// Use a fake SessionManagerClient to be able to pretend that the device has
// been enrolled and registered for policy (and has a device DMToken).
auto fake_session_manager_client =
std::make_unique<FakeSessionManagerClient>();
fake_session_manager_client_ = fake_session_manager_client.get();
DBusThreadManager::GetSetterForTesting()->SetSessionManagerClient(
std::move(fake_session_manager_client));
device_policy_test_helper_.InstallOwnerKey();
device_policy_test_helper_.MarkAsEnterpriseOwned();
fake_session_manager_client_->set_device_policy(
device_policy_builder()->GetBlob());
// Set some fake state keys to make sure they are not empty.
std::vector<std::string> state_keys;
state_keys.push_back("1");
fake_session_manager_client_->set_server_backed_state_keys(state_keys);
}
void SetUpOnMainThread() override {
// Setup the observer reacting on NOTIFICATION_AUTH_NEEDED before the test
// runs because there is no action we actively trigger to request proxy
// authentication. Instead, the sign-in screen automatically shows the gaia
// webview, which will request the gaia URL, which leads to a login prompt.
auth_needed_wait_loop_ = std::make_unique<base::RunLoop>();
auth_needed_observer_ =
std::make_unique<content::WindowedNotificationObserver>(
chrome::NOTIFICATION_AUTH_NEEDED,
base::BindRepeating(&WebviewProxyAuthLoginTest::OnAuthRequested,
base::Unretained(this)));
WebviewLoginTest::SetUpOnMainThread();
}
void TearDownOnMainThread() override {
WebviewLoginTest::TearDownOnMainThread();
auth_needed_observer_.reset();
auth_needed_wait_loop_.reset();
}
bool OnAuthRequested(const content::NotificationSource& source,
const content::NotificationDetails& details) {
// Only care for notifications originating from the frame which is
// displaying gaia.
content::WebContents* main_web_contents = GetLoginUI()->GetWebContents();
content::WebContents* gaia_frame_web_contents =
signin::GetAuthFrameWebContents(main_web_contents, gaia_frame_parent_);
LoginHandler* login_handler =
content::Details<LoginNotificationDetails>(details)->handler();
if (login_handler->web_contents() != gaia_frame_web_contents)
return false;
gaia_frame_login_handler_ = login_handler;
auth_needed_wait_loop_->Quit();
return true;
}
// Waits until proxy authentication has been requested by the frame displaying
// gaia. Returns the LoginHandler handling this authentication request.
LoginHandler* WaitForAuthRequested() {
auth_needed_wait_loop_->Run();
return gaia_frame_login_handler_;
}
void UpdateServedPolicyFromDevicePolicyTestHelper() {
policy_test_server_.UpdatePolicy(
policy::dm_protocol::kChromeDevicePolicyType,
std::string() /* entity_id */,
device_policy_builder()->payload().SerializeAsString());
}
policy::DevicePolicyBuilder* device_policy_builder() {
return device_policy_test_helper_.device_policy();
}
content::WindowedNotificationObserver* auth_needed_observer() {
return auth_needed_observer_.get();
}
private:
std::unique_ptr<content::WindowedNotificationObserver> auth_needed_observer_;
std::unique_ptr<base::RunLoop> auth_needed_wait_loop_;
// Unowned pointer - set to the LoginHandler of the frame displaying gaia.
LoginHandler* gaia_frame_login_handler_ = nullptr;
// A proxy server which requires authentication using the 'Basic'
// authentication method.
std::unique_ptr<net::SpawnedTestServer> auth_proxy_server_;
policy::LocalPolicyTestServer policy_test_server_;
policy::DevicePolicyCrosTestHelper device_policy_test_helper_;
// FakeDBusThreadManager uses FakeSessionManagerClient.
std::unique_ptr<chromeos::DBusThreadManagerSetter> dbus_setter_;
// Unowned pointer - owned by DBusThreadManager.
chromeos::FakeSessionManagerClient* fake_session_manager_client_;
DISALLOW_COPY_AND_ASSIGN(WebviewProxyAuthLoginTest);
};
// Disabled fails on msan and also non-msan bots: https://crbug.com/849128.
IN_PROC_BROWSER_TEST_F(WebviewProxyAuthLoginTest, DISABLED_ProxyAuthTransfer) {
WaitForSigninScreen();
LoginHandler* login_handler = WaitForAuthRequested();
// Before entering auth data, make |policy_test_server_| serve a policy that
// we can use to detect if policies have been fetched.
em::ChromeDeviceSettingsProto& device_policy =
device_policy_builder()->payload();
device_policy.mutable_device_login_screen_auto_select_certificate_for_urls()
->add_login_screen_auto_select_certificate_rules("test_pattern");
UpdateServedPolicyFromDevicePolicyTestHelper();
policy::PolicyChangeRegistrar policy_change_registrar(
g_browser_process->platform_part()
->browser_policy_connector_chromeos()
->GetPolicyService(),
policy::PolicyNamespace(policy::POLICY_DOMAIN_CHROME,
std::string() /* component_id */));
// Now enter auth data
login_handler->SetAuth(base::ASCIIToUTF16("foo"), base::ASCIIToUTF16("bar"));
WaitForGaiaPageLoad();
base::RunLoop run_loop;
policy_change_registrar.Observe(
policy::key::kDeviceLoginScreenAutoSelectCertificateForUrls,
base::BindRepeating(&PolicyChangedCallback, run_loop.QuitClosure()));
run_loop.Run();
// Press the back button at a sign-in screen without pre-existing users to
// start a new sign-in attempt.
// This will re-load gaia, rotating the StoragePartition. The new
// StoragePartition must also have the proxy auth details.
test::OobeJS().Evaluate("$('signin-back-button').fire('tap')");
WaitForGaiaPageBackButtonUpdate();
// Expect that we got back to the identifier page, as there are no known users
// so the sign-in screen will not display user pods.
ExpectIdentifierPage();
}
} // namespace chromeos