Commit afe1fab8 authored by Mohammed Abdon's avatar Mohammed Abdon Committed by Commit Bot

Add browser test for password change detection using extension

Install extension,
Sets the change-password page to an embedded test server,
then simulates submitting a password-change form to that server,
and redirects to URL to simulate what happens in IdPs page.

Bug: 1080626
Change-Id: I18db071dc318d0910f7b41da97c34b79c72c3d86
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2255471
Commit-Queue: Mohammed Abdon <mohammedabdon@chromium.org>
Reviewed-by: default avatarRoman Sorokin [CET] <rsorokin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#781278}
parent be4cd5f2
// Copyright 2020 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 "chrome/browser/chromeos/login/saml/in_session_password_change_manager.h"
#include "chrome/browser/chromeos/login/test/embedded_test_server_mixin.h"
#include "chrome/browser/chromeos/login/test/js_checker.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/common/pref_names.h"
#include "chromeos/constants/chromeos_switches.h"
#include "components/prefs/pref_service.h"
#include "content/public/test/browser_test.h"
#include "net/base/url_util.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
using net::test_server::BasicHttpResponse;
using net::test_server::HttpMethod;
using net::test_server::HttpRequest;
using net::test_server::HttpResponse;
namespace chromeos {
constexpr char kPasswordChangePageTemplate[] =
R"(<html><body onload='document.forms[0].submit();'>
<form action='{0}' method='post'>
Old password: <input name='op' type='password' /><br>
New password: <input name='np' type='password' /><br>
Confirm new password: <input name='cnp' type='password' /><br>
<input type='submit' value='Submit'>
</form></body></html>)";
// Simulates an IdP at where the user can change the password. Redirects the
// user to URLs in the same way as the real IdP would, which we use to detect
// that the password was changed successfully.
// Unlike the real change password page, this one automatically hits submit
// on the change password form as soon as the page loads.
class FakeChangePasswordIdp {
public:
FakeChangePasswordIdp() = default;
FakeChangePasswordIdp& operator=(const FakeChangePasswordIdp&) = delete;
FakeChangePasswordIdp(const FakeChangePasswordIdp&) = delete;
~FakeChangePasswordIdp() = default;
void SetFormSubmitAction(const std::string& url) {
form_submit_action_url_ = url;
}
void RedirectNextPostTo(const std::string& url) {
redirect_next_post_url_ = url;
}
std::unique_ptr<HttpResponse> HandleHttpRequest(const HttpRequest& request);
private:
std::string GetPasswordChangePageContent();
std::string form_submit_action_url_;
std::string redirect_next_post_url_;
};
std::unique_ptr<HttpResponse> FakeChangePasswordIdp::HandleHttpRequest(
const HttpRequest& request) {
auto http_response = std::make_unique<BasicHttpResponse>();
if (request.method == HttpMethod::METHOD_POST) {
if (redirect_next_post_url_ != "") {
http_response->set_code(net::HTTP_TEMPORARY_REDIRECT);
http_response->AddCustomHeader("Location", redirect_next_post_url_);
redirect_next_post_url_.clear();
return http_response;
}
http_response->set_code(net::HTTP_OK);
return http_response;
}
http_response->set_code(net::HTTP_OK);
http_response->set_content(GetPasswordChangePageContent());
return http_response;
}
std::string FakeChangePasswordIdp::GetPasswordChangePageContent() {
std::string result = kPasswordChangePageTemplate;
std::string place_holder = "{0}";
result.replace(result.find(place_holder), place_holder.size(),
form_submit_action_url_);
return result;
}
// Waits for an SAML_IDP_PASSWORD_CHANGED event from the
// InSessionPasswordChangeManager.
class PasswordChangeWaiter : public InSessionPasswordChangeManager::Observer {
public:
PasswordChangeWaiter() {
InSessionPasswordChangeManager::Get()->AddObserver(this);
}
PasswordChangeWaiter& operator=(const PasswordChangeWaiter&) = delete;
PasswordChangeWaiter(const PasswordChangeWaiter&) = delete;
~PasswordChangeWaiter() override {
InSessionPasswordChangeManager::Get()->RemoveObserver(this);
}
void WaitForPasswordChange() {
run_loop_.Run();
ASSERT_TRUE(saml_password_changed_);
}
void OnEvent(InSessionPasswordChangeManager::Event event) override {
if (event ==
InSessionPasswordChangeManager::Event::SAML_IDP_PASSWORD_CHANGED) {
saml_password_changed_ = true;
run_loop_.Quit();
}
}
private:
bool saml_password_changed_ = false;
base::RunLoop run_loop_;
};
// Simulates the redirects that Adfs, Azure, and Ping do in the case of
// password change success, and ensures that we detect each one.
class PasswordChangeExtensionTest : public extensions::ExtensionBrowserTest {
protected:
PasswordChangeExtensionTest() = default;
PasswordChangeExtensionTest& operator=(const PasswordChangeExtensionTest&) =
delete;
PasswordChangeExtensionTest(const PasswordChangeExtensionTest&) = delete;
// We have to essentially replicate what MixinBasedInProcessBrowserTest does
// here because ExtensionBrowserTest doesn't inherit from that class.
void SetUp() override {
embedded_test_server_.RegisterRequestHandler(
base::Bind(&FakeChangePasswordIdp::HandleHttpRequest,
base::Unretained(&fake_idp_)));
mixin_host_.SetUp();
extensions::ExtensionBrowserTest::SetUp();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
mixin_host_.SetUpCommandLine(command_line);
extensions::ExtensionBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitchASCII(switches::kSamlPasswordChangeUrl,
embedded_test_server_.base_url().spec());
}
void SetUpOnMainThread() override {
mixin_host_.SetUpOnMainThread();
extensions::ExtensionBrowserTest::SetUpOnMainThread();
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
Profile* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
profile->GetPrefs()->SetBoolean(prefs::kSamlInSessionPasswordChangeEnabled,
true);
password_change_manager_ =
std::make_unique<InSessionPasswordChangeManager>(profile);
InSessionPasswordChangeManager::SetForTesting(
password_change_manager_.get());
base::FilePath path =
test_data_dir_.AppendASCII("saml_password_change.crx");
extension = extensions::ExtensionBrowserTest::InstallExtension(path, 1);
}
void WaitForPasswordChangeDetected() {
PasswordChangeWaiter password_change_waiter;
password_change_waiter.WaitForPasswordChange();
}
void TearDownOnMainThread() override {
InSessionPasswordChangeManager::ResetForTesting();
mixin_host_.TearDownOnMainThread();
extensions::ExtensionBrowserTest::TearDownOnMainThread();
extensions::ExtensionBrowserTest::UninstallExtension(extension->id());
}
FakeChangePasswordIdp fake_idp_;
private:
net::EmbeddedTestServer embedded_test_server_{
net::EmbeddedTestServer::Type::TYPE_HTTPS};
InProcessBrowserTestMixinHost mixin_host_;
EmbeddedTestServerSetupMixin embedded_test_server_mixin_{
&mixin_host_, &embedded_test_server_};
const extensions::Extension* extension;
std::unique_ptr<InSessionPasswordChangeManager> password_change_manager_;
};
IN_PROC_BROWSER_TEST_F(PasswordChangeExtensionTest, DetectAdfsSuccess) {
fake_idp_.SetFormSubmitAction("/adfs/portal/updatepassword/");
fake_idp_.RedirectNextPostTo("/adfs/portal/updatepassword/?status=0");
InSessionPasswordChangeManager::Get()->StartInSessionPasswordChange();
WaitForPasswordChangeDetected();
}
IN_PROC_BROWSER_TEST_F(PasswordChangeExtensionTest, DetectAzureSuccess) {
fake_idp_.SetFormSubmitAction("/ChangePassword.aspx");
fake_idp_.RedirectNextPostTo("/ChangePassword.aspx?ReturnCode=0");
InSessionPasswordChangeManager::Get()->StartInSessionPasswordChange();
WaitForPasswordChangeDetected();
}
IN_PROC_BROWSER_TEST_F(PasswordChangeExtensionTest, DetectPingSuccess) {
fake_idp_.SetFormSubmitAction(
"/idp/directory/a/12345/password/chg/67890?returnurl=/Selection");
fake_idp_.RedirectNextPostTo("/Selection");
InSessionPasswordChangeManager::Get()->StartInSessionPasswordChange();
WaitForPasswordChangeDetected();
}
} // namespace chromeos
...@@ -2234,6 +2234,7 @@ if (!is_android) { ...@@ -2234,6 +2234,7 @@ if (!is_android) {
"../browser/chromeos/login/proxy_auth_dialog_browsertest.cc", "../browser/chromeos/login/proxy_auth_dialog_browsertest.cc",
"../browser/chromeos/login/quick_unlock/pin_migration_browsertest.cc", "../browser/chromeos/login/quick_unlock/pin_migration_browsertest.cc",
"../browser/chromeos/login/reset_browsertest.cc", "../browser/chromeos/login/reset_browsertest.cc",
"../browser/chromeos/login/saml/password_change_extension_browsertest.cc",
"../browser/chromeos/login/saml/password_change_success_detection_browsertest.cc", "../browser/chromeos/login/saml/password_change_success_detection_browsertest.cc",
"../browser/chromeos/login/saml/saml_browsertest.cc", "../browser/chromeos/login/saml/saml_browsertest.cc",
"../browser/chromeos/login/saml/security_token_saml_browsertest.cc", "../browser/chromeos/login/saml/security_token_saml_browsertest.cc",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment