blob: 3835679c09d745e3b0238dd6b8d5cc5236429342 [file] [log] [blame]
// Copyright 2017 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 <string>
#include "base/command_line.h"
#include "base/location.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/chromeos/login/active_directory_test_helper.h"
#include "chrome/browser/chromeos/login/login_manager_test.h"
#include "chrome/browser/chromeos/login/login_shelf_test_helper.h"
#include "chrome/browser/chromeos/login/startup_utils.h"
#include "chrome/browser/chromeos/login/test/js_checker.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/login/wizard_controller.h"
#include "chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "chromeos/constants/chromeos_switches.h"
#include "chromeos/dbus/auth_policy/fake_auth_policy_client.h"
#include "chromeos/dbus/cryptohome/fake_cryptohome_client.h"
#include "chromeos/dbus/cryptohome/tpm_util.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/login/auth/authpolicy_login_helper.h"
#include "components/account_id/account_id.h"
#include "components/user_manager/user_names.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/geometry/test/rect_test_util.h"
using ::gfx::test::RectContains;
namespace chromeos {
namespace {
const char kPassword[] = "password";
constexpr char kGaiaSigninId[] = "signin-frame";
constexpr char kAdOfflineAuthId[] = "offline-ad-auth";
constexpr char kTestActiveDirectoryUser[] = "test-user";
constexpr char kTestUserRealm[] = "user.realm";
constexpr char kAdMachineInput[] = "machineNameInput";
constexpr char kAdMoreOptionsButton[] = "moreOptionsBtn";
constexpr char kAdUserInput[] = "userInput";
constexpr char kAdPasswordInput[] = "passwordInput";
constexpr char kAdCredsButton[] = "nextButton";
constexpr char kAdAutocompleteRealm[] = "$.userInput.querySelector('span')";
constexpr char kAdPasswordChangeId[] = "active-directory-password-change";
constexpr char kAdAnimatedPages[] = "animatedPages";
constexpr char kAdOldPasswordInput[] = "oldPassword";
constexpr char kAdNewPassword1Input[] = "newPassword1";
constexpr char kAdNewPassword2Input[] = "newPassword2";
constexpr char kAdPasswordChangeFormId[] = "inputForm";
constexpr char kFormButtonId[] = "button";
constexpr char kNewPassword[] = "new_password";
constexpr char kDifferentNewPassword[] = "different_new_password";
constexpr char kNavigationId[] = "navigation";
constexpr char kCloseButtonId[] = "closeButton";
class ActiveDirectoryLoginTest : public LoginManagerTest {
public:
ActiveDirectoryLoginTest()
: LoginManagerTest(true, true),
// Using the same realm as supervised user domain. Should be treated as
// normal realm.
test_realm_(user_manager::kSupervisedUserDomain),
test_user_(kTestActiveDirectoryUser + ("@" + test_realm_)) {}
~ActiveDirectoryLoginTest() override = default;
void SetUpInProcessBrowserTestFixture() override {
LoginManagerTest::SetUpInProcessBrowserTestFixture();
// This is called before ChromeBrowserMain initializes the fake dbus
// clients, and DisableOperationDelayForTesting() needs to be called before
// other ChromeBrowserMain initialization occurs.
AuthPolicyClient::InitializeFake();
FakeAuthPolicyClient::Get()->DisableOperationDelayForTesting();
// Note: FakeCryptohomeClient needs paths to be set to load install attribs.
active_directory_test_helper::OverridePaths();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
LoginManagerTest::SetUpCommandLine(command_line);
command_line->AppendSwitch(switches::kOobeSkipPostLogin);
}
void SetUpOnMainThread() override {
// Set the threshold to a max value to disable the offline message screen
// on slow configurations like MSAN, where it otherwise triggers on every
// run.
LoginDisplayHost::default_host()
->GetOobeUI()
->signin_screen_handler()
->SetOfflineTimeoutForTesting(base::TimeDelta::Max());
LoginManagerTest::SetUpOnMainThread();
}
void MarkAsActiveDirectoryEnterprise() {
StartupUtils::MarkOobeCompleted();
active_directory_test_helper::PrepareLogin(test_user_);
}
void TriggerPasswordChangeScreen() {
OobeScreenWaiter screen_waiter(
OobeScreen::SCREEN_ACTIVE_DIRECTORY_PASSWORD_CHANGE);
fake_auth_policy_client()->set_auth_error(
authpolicy::ERROR_PASSWORD_EXPIRED);
SubmitActiveDirectoryCredentials(test_user_, kPassword);
screen_waiter.Wait();
TestAdPasswordChangeError(std::string());
}
void ClosePasswordChangeScreen() {
test::OobeJS().TapOnPath(
{kAdPasswordChangeId, kNavigationId, kCloseButtonId});
}
void ExpectValid(const std::string& parent_id,
const std::string& child_id,
bool valid) {
std::string js =
test::GetOobeElementPath({parent_id, child_id}) + ".invalid";
if (valid)
test::OobeJS().ExpectFalse(js);
else
test::OobeJS().ExpectTrue(js);
}
// Checks if Active Directory login is visible.
void TestLoginVisible() {
OobeScreenWaiter screen_waiter(OobeScreen::SCREEN_GAIA_SIGNIN);
screen_waiter.Wait();
// Checks if Gaia signin is hidden.
test::OobeJS().ExpectHidden(kGaiaSigninId);
// Checks if Active Directory signin is visible.
test::OobeJS().ExpectVisible(kAdOfflineAuthId);
test::OobeJS().ExpectHiddenPath({kAdOfflineAuthId, kAdMachineInput});
test::OobeJS().ExpectHiddenPath({kAdOfflineAuthId, kAdMoreOptionsButton});
test::OobeJS().ExpectVisiblePath({kAdOfflineAuthId, kAdUserInput});
test::OobeJS().ExpectVisiblePath({kAdOfflineAuthId, kAdPasswordInput});
std::string autocomplete_domain_ui;
base::TrimString(
test::OobeJS().GetString(
JSElement(kAdOfflineAuthId, kAdAutocompleteRealm) + ".innerText"),
base::kWhitespaceASCII, &autocomplete_domain_ui);
// Checks if realm is set to autocomplete username.
EXPECT_EQ(autocomplete_realm_, autocomplete_domain_ui);
EXPECT_TRUE(LoginShelfTestHelper().IsLoginShelfShown());
}
// Checks if Active Directory password change screen is shown.
void TestPasswordChangeVisible() {
// Checks if Gaia signin is hidden.
test::OobeJS().ExpectHidden(kGaiaSigninId);
// Checks if Active Directory signin is visible.
test::OobeJS().ExpectVisible(kAdPasswordChangeId);
test::OobeJS().ExpectTrue(
test::GetOobeElementPath({kAdPasswordChangeId, kAdAnimatedPages}) +
".selected == 0");
test::OobeJS().ExpectVisiblePath(
{kAdPasswordChangeId, kNavigationId, kCloseButtonId});
}
// Checks if user input is marked as invalid.
void TestUserError() {
TestLoginVisible();
ExpectValid(kAdOfflineAuthId, kAdUserInput, false);
}
void SetUserInput(const std::string& value) {
test::OobeJS().TypeIntoPath(value, {kAdOfflineAuthId, kAdUserInput});
}
void TestUserInput(const std::string& value) {
test::OobeJS().ExpectEQ(
test::GetOobeElementPath({kAdOfflineAuthId, kAdUserInput}) + ".value",
value);
}
// Checks if password input is marked as invalid.
void TestPasswordError() {
TestLoginVisible();
ExpectValid(kAdOfflineAuthId, kAdPasswordInput, false);
}
// Checks that machine, password and user inputs are valid.
void TestNoError() {
TestLoginVisible();
ExpectValid(kAdOfflineAuthId, kAdMachineInput, true);
ExpectValid(kAdOfflineAuthId, kAdUserInput, true);
ExpectValid(kAdOfflineAuthId, kAdPasswordInput, true);
}
// Checks if autocomplete domain is visible for the user input.
void TestDomainVisible() {
test::OobeJS().ExpectTrue(
"!" + JSElement(kAdOfflineAuthId, kAdAutocompleteRealm) + ".hidden");
}
// Checks if autocomplete domain is hidden for the user input.
void TestDomainHidden() {
test::OobeJS().ExpectTrue(
JSElement(kAdOfflineAuthId, kAdAutocompleteRealm) + ".hidden");
}
// Checks if Active Directory password change screen is shown. Also checks if
// |invalid_element| is invalidated and all the other elements are valid.
void TestAdPasswordChangeError(const std::string& invalid_element) {
TestPasswordChangeVisible();
for (const char* element :
{kAdOldPasswordInput, kAdNewPassword1Input, kAdNewPassword2Input}) {
std::string js_assertion =
test::GetOobeElementPath({kAdPasswordChangeId, element}) +
".isInvalid";
if (element != invalid_element)
js_assertion = "!" + js_assertion;
test::OobeJS().ExpectTrue(js_assertion);
}
}
// Sets username and password for the Active Directory login and submits it.
void SubmitActiveDirectoryCredentials(const std::string& username,
const std::string& password) {
test::OobeJS().TypeIntoPath(username, {kAdOfflineAuthId, kAdUserInput});
test::OobeJS().TypeIntoPath(password, {kAdOfflineAuthId, kAdPasswordInput});
test::OobeJS().TapOnPath({kAdOfflineAuthId, kAdCredsButton});
}
// Sets username and password for the Active Directory login and submits it.
void SubmitActiveDirectoryPasswordChangeCredentials(
const std::string& old_password,
const std::string& new_password1,
const std::string& new_password2) {
test::OobeJS().TypeIntoPath(old_password,
{kAdPasswordChangeId, kAdOldPasswordInput});
test::OobeJS().TypeIntoPath(new_password1,
{kAdPasswordChangeId, kAdNewPassword1Input});
test::OobeJS().TypeIntoPath(new_password2,
{kAdPasswordChangeId, kAdNewPassword2Input});
test::OobeJS().TapOnPath(
{kAdPasswordChangeId, kAdPasswordChangeFormId, kFormButtonId});
}
void SetupActiveDirectoryJSNotifications() {
test::OobeJS().Evaluate(
"var testInvalidateAd = login.GaiaSigninScreen.invalidateAd;"
"login.GaiaSigninScreen.invalidateAd = function(user, errorState) {"
" testInvalidateAd(user, errorState);"
" window.domAutomationController.send('ShowAuthError');"
"}");
}
void WaitForMessage(content::DOMMessageQueue* message_queue,
const std::string& expected_message) {
std::string message;
do {
ASSERT_TRUE(message_queue->WaitForMessage(&message));
} while (message != expected_message);
}
protected:
// Returns string representing element with id=|element_id| inside Active
// Directory login element.
std::string JSElement(const std::string& parent_id,
const std::string& selector) {
return "document.querySelector('#" + parent_id + "')." + selector;
}
FakeAuthPolicyClient* fake_auth_policy_client() {
return FakeAuthPolicyClient::Get();
}
const std::string test_realm_;
const std::string test_user_;
std::string autocomplete_realm_;
private:
DISALLOW_COPY_AND_ASSIGN(ActiveDirectoryLoginTest);
};
class ActiveDirectoryLoginAutocompleteTest : public ActiveDirectoryLoginTest {
public:
ActiveDirectoryLoginAutocompleteTest() = default;
void SetUpInProcessBrowserTestFixture() override {
ActiveDirectoryLoginTest::SetUpInProcessBrowserTestFixture();
enterprise_management::ChromeDeviceSettingsProto device_settings;
device_settings.mutable_login_screen_domain_auto_complete()
->set_login_screen_domain_auto_complete(kTestUserRealm);
fake_auth_policy_client()->set_device_policy(device_settings);
autocomplete_realm_ = "@" + std::string(kTestUserRealm);
}
private:
DISALLOW_COPY_AND_ASSIGN(ActiveDirectoryLoginAutocompleteTest);
};
} // namespace
// Declares a PRE_ test that calls MarkAsActiveDirectoryEnterprise() and the
// test itself.
#define IN_PROC_BROWSER_TEST_F_WITH_PRE(class_name, test_name) \
IN_PROC_BROWSER_TEST_F(class_name, PRE_##test_name) { \
MarkAsActiveDirectoryEnterprise(); \
} \
IN_PROC_BROWSER_TEST_F(class_name, test_name)
// Test successful Active Directory login.
IN_PROC_BROWSER_TEST_F_WITH_PRE(ActiveDirectoryLoginTest, LoginSuccess) {
TestNoError();
TestDomainHidden();
content::WindowedNotificationObserver session_start_waiter(
chrome::NOTIFICATION_SESSION_STARTED,
content::NotificationService::AllSources());
SubmitActiveDirectoryCredentials(test_user_, kPassword);
session_start_waiter.Wait();
}
// Test different UI errors for Active Directory login.
IN_PROC_BROWSER_TEST_F_WITH_PRE(ActiveDirectoryLoginTest, LoginErrors) {
SetupActiveDirectoryJSNotifications();
TestNoError();
TestDomainHidden();
content::DOMMessageQueue message_queue;
SubmitActiveDirectoryCredentials("", "");
TestUserError();
TestDomainHidden();
SubmitActiveDirectoryCredentials(test_user_, "");
TestPasswordError();
TestDomainHidden();
SubmitActiveDirectoryCredentials(std::string(kTestActiveDirectoryUser) + "@",
kPassword);
WaitForMessage(&message_queue, "\"ShowAuthError\"");
TestUserError();
TestDomainHidden();
fake_auth_policy_client()->set_auth_error(authpolicy::ERROR_BAD_USER_NAME);
SubmitActiveDirectoryCredentials(test_user_, kPassword);
WaitForMessage(&message_queue, "\"ShowAuthError\"");
TestUserError();
TestDomainHidden();
fake_auth_policy_client()->set_auth_error(authpolicy::ERROR_BAD_PASSWORD);
SubmitActiveDirectoryCredentials(test_user_, kPassword);
WaitForMessage(&message_queue, "\"ShowAuthError\"");
TestPasswordError();
TestDomainHidden();
fake_auth_policy_client()->set_auth_error(authpolicy::ERROR_UNKNOWN);
SubmitActiveDirectoryCredentials(test_user_, kPassword);
WaitForMessage(&message_queue, "\"ShowAuthError\"");
// Inputs are not invalidated for the unknown error.
TestNoError();
TestDomainHidden();
}
// Test successful Active Directory login from the password change screen.
IN_PROC_BROWSER_TEST_F_WITH_PRE(ActiveDirectoryLoginTest,
PasswordChange_LoginSuccess) {
TestLoginVisible();
TestDomainHidden();
TriggerPasswordChangeScreen();
// Password accepted by AuthPolicyClient.
fake_auth_policy_client()->set_auth_error(authpolicy::ERROR_NONE);
content::WindowedNotificationObserver session_start_waiter(
chrome::NOTIFICATION_SESSION_STARTED,
content::NotificationService::AllSources());
SubmitActiveDirectoryPasswordChangeCredentials(kPassword, kNewPassword,
kNewPassword);
session_start_waiter.Wait();
}
// Test different UI errors for Active Directory password change screen.
IN_PROC_BROWSER_TEST_F_WITH_PRE(ActiveDirectoryLoginTest,
PasswordChange_UIErrors) {
TestLoginVisible();
TestDomainHidden();
TriggerPasswordChangeScreen();
// Password rejected by UX.
// Empty passwords.
SubmitActiveDirectoryPasswordChangeCredentials("", "", "");
TestAdPasswordChangeError(kAdOldPasswordInput);
// Empty new password.
SubmitActiveDirectoryPasswordChangeCredentials(kPassword, "", "");
TestAdPasswordChangeError(kAdNewPassword1Input);
// Empty confirmation of the new password.
SubmitActiveDirectoryPasswordChangeCredentials(kPassword, kNewPassword, "");
TestAdPasswordChangeError(kAdNewPassword2Input);
// Confirmation of password is different from new password.
SubmitActiveDirectoryPasswordChangeCredentials(kPassword, kNewPassword,
kDifferentNewPassword);
TestAdPasswordChangeError(kAdNewPassword2Input);
// Password rejected by AuthPolicyClient.
fake_auth_policy_client()->set_auth_error(authpolicy::ERROR_BAD_PASSWORD);
SubmitActiveDirectoryPasswordChangeCredentials(kPassword, kNewPassword,
kNewPassword);
TestAdPasswordChangeError(kAdOldPasswordInput);
}
// Test reopening Active Directory password change screen clears errors.
IN_PROC_BROWSER_TEST_F_WITH_PRE(ActiveDirectoryLoginTest,
PasswordChange_ReopenClearErrors) {
TestLoginVisible();
TestDomainHidden();
TriggerPasswordChangeScreen();
// Empty new password.
SubmitActiveDirectoryPasswordChangeCredentials("", "", "");
TestAdPasswordChangeError(kAdOldPasswordInput);
ClosePasswordChangeScreen();
TestLoginVisible();
TriggerPasswordChangeScreen();
}
// Tests that autocomplete works. Submits username without domain.
IN_PROC_BROWSER_TEST_F_WITH_PRE(ActiveDirectoryLoginAutocompleteTest,
LoginSuccess) {
TestNoError();
TestDomainVisible();
content::WindowedNotificationObserver session_start_waiter(
chrome::NOTIFICATION_SESSION_STARTED,
content::NotificationService::AllSources());
SubmitActiveDirectoryCredentials(kTestActiveDirectoryUser, kPassword);
session_start_waiter.Wait();
}
// Tests that user could override autocomplete domain.
IN_PROC_BROWSER_TEST_F_WITH_PRE(ActiveDirectoryLoginAutocompleteTest,
TestAutocomplete) {
SetupActiveDirectoryJSNotifications();
TestLoginVisible();
TestDomainVisible();
fake_auth_policy_client()->set_auth_error(authpolicy::ERROR_BAD_PASSWORD);
content::DOMMessageQueue message_queue;
// Submit with a different domain.
SetUserInput(test_user_);
TestDomainHidden();
TestUserInput(test_user_);
SubmitActiveDirectoryCredentials(test_user_, "password");
WaitForMessage(&message_queue, "\"ShowAuthError\"");
TestLoginVisible();
TestDomainHidden();
TestUserInput(test_user_);
// Set userinput with the autocomplete domain. JS will remove the autocomplete
// domain.
SetUserInput(kTestActiveDirectoryUser + autocomplete_realm_);
TestDomainVisible();
TestUserInput(kTestActiveDirectoryUser);
SubmitActiveDirectoryCredentials(
kTestActiveDirectoryUser + autocomplete_realm_, "password");
WaitForMessage(&message_queue, "\"ShowAuthError\"");
TestLoginVisible();
TestDomainVisible();
TestUserInput(kTestActiveDirectoryUser);
}
#undef IN_PROC_BROWSER_TEST_F_WITH_PRE
} // namespace chromeos