Commit 32944cdc authored by Jonathan Mengedoht's avatar Jonathan Mengedoht Committed by Commit Bot

Add WellKnownChangePasswordState

The State processes the logic if .well-known/change-password is
supported.

Bug: 927473
Change-Id: Ib0276bb716d600aa458c38f8319b0978a3993159
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2346347
Commit-Queue: Jonathan Mengedoht <mengedoht@google.com>
Reviewed-by: default avatarVasilii Sukhanov <vasilii@chromium.org>
Reviewed-by: default avatarChristian Dullweber <dullweber@chromium.org>
Cr-Commit-Position: refs/heads/master@{#798558}
parent 6d6d84b2
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include "base/logging.h" #include "base/logging.h"
#include "chrome/browser/password_manager/change_password_url_service_factory.h" #include "chrome/browser/password_manager/change_password_url_service_factory.h"
#include "components/password_manager/core/browser/change_password_url_service.h" #include "components/password_manager/core/browser/change_password_url_service.h"
#include "components/password_manager/core/browser/well_known_change_password_state.h"
#include "components/password_manager/core/browser/well_known_change_password_util.h" #include "components/password_manager/core/browser/well_known_change_password_util.h"
#include "components/password_manager/core/common/password_manager_features.h" #include "components/password_manager/core/common/password_manager_features.h"
#include "content/public/browser/browser_context.h" #include "content/public/browser/browser_context.h"
...@@ -116,44 +117,12 @@ const char* WellKnownChangePasswordNavigationThrottle::GetNameForLogging() { ...@@ -116,44 +117,12 @@ const char* WellKnownChangePasswordNavigationThrottle::GetNameForLogging() {
void WellKnownChangePasswordNavigationThrottle::FetchNonExistingResource( void WellKnownChangePasswordNavigationThrottle::FetchNonExistingResource(
NavigationHandle* handle) { NavigationHandle* handle) {
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url =
CreateWellKnownNonExistingResourceURL(handle->GetURL());
resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
resource_request->load_flags = net::LOAD_DISABLE_CACHE;
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation(
"well_known_path_that_should_not_exist",
R"(
semantics {
sender: "Password Manager"
description:
"Check whether the site supports .well-known 'special' URLs."
"If the website does not support the spec we navigate to the "
"fallback url. See also "
"https://wicg.github.io/change-password-url/response-code-reliability.html#iana"
trigger:
"When the user clicks 'Change password' on "
"chrome://settings/passwords, or when they visit the "
"[ORIGIN]/.well-known/change-password special URL, Chrome makes "
"this additional request. Chrome Password manager shows a button "
"with the link in the password checkup for compromised passwords "
"view (chrome://settings/passwords/check) and in a dialog when the "
"user signs in using compromised credentials."
data:
"The request body is empty. No user data is included."
destination: WEBSITE
}
policy {
cookies_allowed: NO
setting: "This feature cannot be disabled."
policy_exception_justification: "Essential for navigation."
})");
auto url_loader_factory = content::BrowserContext::GetDefaultStoragePartition( auto url_loader_factory = content::BrowserContext::GetDefaultStoragePartition(
handle->GetWebContents()->GetBrowserContext()) handle->GetWebContents()->GetBrowserContext())
->GetURLLoaderFactoryForBrowserProcess(); ->GetURLLoaderFactoryForBrowserProcess();
url_loader_ = network::SimpleURLLoader::Create(std::move(resource_request), url_loader_ =
traffic_annotation); password_manager::CreateResourceRequestToWellKnownNonExistingResourceFor(
handle->GetURL());
// Binding the callback to |this| is safe, because the navigationthrottle // Binding the callback to |this| is safe, because the navigationthrottle
// defers if the request is not received yet. Thereby the throttle still exist // defers if the request is not received yet. Thereby the throttle still exist
// when the response arrives. // when the response arrives.
......
...@@ -243,6 +243,8 @@ static_library("browser") { ...@@ -243,6 +243,8 @@ static_library("browser") {
"ui/saved_passwords_presenter.h", "ui/saved_passwords_presenter.h",
"votes_uploader.cc", "votes_uploader.cc",
"votes_uploader.h", "votes_uploader.h",
"well_known_change_password_state.cc",
"well_known_change_password_state.h",
"well_known_change_password_util.cc", "well_known_change_password_util.cc",
"well_known_change_password_util.h", "well_known_change_password_util.h",
] ]
...@@ -631,6 +633,7 @@ source_set("unit_tests") { ...@@ -631,6 +633,7 @@ source_set("unit_tests") {
"ui/saved_passwords_presenter_unittest.cc", "ui/saved_passwords_presenter_unittest.cc",
"vote_uploads_test_matchers.h", "vote_uploads_test_matchers.h",
"votes_uploader_unittest.cc", "votes_uploader_unittest.cc",
"well_known_change_password_state_unittest.cc",
"well_known_change_password_util_unittest.cc", "well_known_change_password_util_unittest.cc",
] ]
if (is_android) { if (is_android) {
......
// 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 "components/password_manager/core/browser/well_known_change_password_state.h"
#include "components/password_manager/core/browser/well_known_change_password_util.h"
#include "net/base/load_flags.h"
#include "net/http/http_status_code.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
using password_manager::WellKnownChangePasswordState;
using password_manager::WellKnownChangePasswordStateDelegate;
namespace password_manager {
std::unique_ptr<network::SimpleURLLoader>
CreateResourceRequestToWellKnownNonExistingResourceFor(const GURL& url) {
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url = CreateWellKnownNonExistingResourceURL(url);
resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
resource_request->load_flags = net::LOAD_DISABLE_CACHE;
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation(
"well_known_path_that_should_not_exist",
R"(
semantics {
sender: "Password Manager"
description:
"Check whether the site supports .well-known 'special' URLs."
"If the website does not support the spec we navigate to the "
"fallback url. See also "
"https://wicg.github.io/change-password-url/response-code-reliability.html#iana"
trigger:
"When the user clicks 'Change password' on "
"chrome://settings/passwords, or when they visit the "
"[ORIGIN]/.well-known/change-password special URL, Chrome makes "
"this additional request. Chrome Password manager shows a button "
"with the link in the password checkup for compromised passwords "
"view (chrome://settings/passwords/check) and in a dialog when the "
"user signs in using compromised credentials."
data:
"The request body is empty. No user data is included."
destination: WEBSITE
}
policy {
cookies_allowed: NO
setting: "This feature cannot be disabled."
policy_exception_justification: "Essential for navigation."
})");
return network::SimpleURLLoader::Create(std::move(resource_request),
traffic_annotation);
}
WellKnownChangePasswordState::WellKnownChangePasswordState(
WellKnownChangePasswordStateDelegate* delegate)
: delegate_(delegate) {}
WellKnownChangePasswordState::~WellKnownChangePasswordState() = default;
void WellKnownChangePasswordState::FetchNonExistingResource(
network::SharedURLLoaderFactory* url_loader_factory,
const GURL& url) {
url_loader_ = CreateResourceRequestToWellKnownNonExistingResourceFor(url);
// Binding the callback to |this| is safe, because the State exists until
// OnProcessingFinished is called which can only be called after the response
// arrives.
url_loader_->DownloadHeadersOnly(
url_loader_factory,
base::BindOnce(
&WellKnownChangePasswordState::FetchNonExistingResourceCallback,
base::Unretained(this)));
}
void WellKnownChangePasswordState::SetChangePasswordResponseCode(
int status_code) {
change_password_response_code_ = status_code;
ContinueProcessing();
}
void WellKnownChangePasswordState::FetchNonExistingResourceCallback(
scoped_refptr<net::HttpResponseHeaders> headers) {
non_existing_resource_response_code_ =
headers ? headers->response_code() : -1;
ContinueProcessing();
}
void WellKnownChangePasswordState::ContinueProcessing() {
if (!BothRequestsFinished())
return;
delegate_->OnProcessingFinished(SupportsChangePasswordUrl());
}
bool WellKnownChangePasswordState::BothRequestsFinished() const {
return non_existing_resource_response_code_ != 0 &&
change_password_response_code_ != 0;
}
bool WellKnownChangePasswordState::SupportsChangePasswordUrl() const {
DCHECK(BothRequestsFinished());
return 200 <= change_password_response_code_ &&
change_password_response_code_ < 300 &&
non_existing_resource_response_code_ == net::HTTP_NOT_FOUND;
}
} // namespace password_manager
\ No newline at end of file
// 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.
#ifndef COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_WELL_KNOWN_CHANGE_PASSWORD_STATE_H_
#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_WELL_KNOWN_CHANGE_PASSWORD_STATE_H_
#include <memory>
#include "base/memory/scoped_refptr.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "url/gurl.h"
namespace network {
class SharedURLLoaderFactory;
}
namespace password_manager {
// Creates a SimpleURLLoader for a request to the non existing resource path for
// a given |origin|.
// TODO(crbug.com/927473): move method to anonymous namespace when State is
// integrated in NavigationThrottle.
std::unique_ptr<network::SimpleURLLoader>
CreateResourceRequestToWellKnownNonExistingResourceFor(const GURL& url);
// A delegate that is notified when the processing is done and its known if
// .well-known/change-password is supported.
class WellKnownChangePasswordStateDelegate {
public:
virtual ~WellKnownChangePasswordStateDelegate() = default;
virtual void OnProcessingFinished(bool is_supported) = 0;
};
// Processes if .well-known/change-password is supported by a site.
class WellKnownChangePasswordState {
public:
explicit WellKnownChangePasswordState(
password_manager::WellKnownChangePasswordStateDelegate* delegate);
~WellKnownChangePasswordState();
// Request the status code from a path that is expected to return 404.
void FetchNonExistingResource(
network::SharedURLLoaderFactory* url_loader_factory,
const GURL& origin);
// The request to .well-known/change-password is not made by this State. To
// get the response code for the request the owner of the state has to call
// this method to tell the state.
void SetChangePasswordResponseCode(int status_code);
private:
// Callback for the request to the "not exist" path.
void FetchNonExistingResourceCallback(
scoped_refptr<net::HttpResponseHeaders> headers);
// Function is called when both requests are finished. Decides to continue or
// redirect to homepage.
void ContinueProcessing();
// Checks if both requests are finished.
bool BothRequestsFinished() const;
// Checks the status codes and returns if change password is supported.
bool SupportsChangePasswordUrl() const;
WellKnownChangePasswordStateDelegate* delegate_ = nullptr;
int non_existing_resource_response_code_ = 0;
int change_password_response_code_ = 0;
std::unique_ptr<network::SimpleURLLoader> url_loader_;
};
} // namespace password_manager
#endif // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_WELL_KNOWN_CHANGE_PASSWORD_STATE_H_
// 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 "components/password_manager/core/browser/well_known_change_password_state.h"
#include "base/task/post_task.h"
#include "base/test/task_environment.h"
#include "components/password_manager/core/browser/well_known_change_password_util.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.h"
#include "services/network/test/test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace password_manager {
// To simulate different arrivals of the response codes, a delay for the
// response is added.
struct ResponseDelayParams {
int change_password_delay;
int not_exist_delay;
};
constexpr char kOrigin[] = "foo.bar";
class MockWellKnownChangePasswordStateDelegate
: public WellKnownChangePasswordStateDelegate {
public:
MockWellKnownChangePasswordStateDelegate() = default;
~MockWellKnownChangePasswordStateDelegate() override = default;
MOCK_METHOD(void, OnProcessingFinished, (bool), (override));
};
class WellKnownChangePasswordStateTest
: public testing::Test,
public testing::WithParamInterface<ResponseDelayParams> {
public:
WellKnownChangePasswordStateTest() {
state_.FetchNonExistingResource(test_shared_loader_factory_.get(),
GURL(kOrigin));
}
// Mocking and sending the response for the non_existing request with status
// code |status| after a time delay |delay|.
void RespondeToNonExistingRequest(net::HttpStatusCode status, int delay);
// Mocking and setting the response for the change_password request with
// status code |status| after a time delay |delay|.
void RespondeToChangePasswordRequest(net::HttpStatusCode status, int delay);
MockWellKnownChangePasswordStateDelegate* delegate() { return &delegate_; }
// Wait until all PostTasks are processed.
void FastForwardPostTasks() {
task_environment_.FastForwardUntilNoTasksRemain();
}
private:
base::test::SingleThreadTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
MockWellKnownChangePasswordStateDelegate delegate_;
WellKnownChangePasswordState state_{&delegate_};
network::TestURLLoaderFactory test_url_loader_factory_;
scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_ =
base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
&test_url_loader_factory_);
};
void WellKnownChangePasswordStateTest::RespondeToNonExistingRequest(
net::HttpStatusCode status,
int delay) {
EXPECT_EQ(test_url_loader_factory_.NumPending(), 1);
base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(
[](net::HttpStatusCode status,
network::TestURLLoaderFactory* factory) {
factory->SimulateResponseForPendingRequest(
CreateWellKnownNonExistingResourceURL(GURL(kOrigin)),
network::URLLoaderCompletionStatus(net::OK),
network::CreateURLResponseHead(status), "");
},
status, &test_url_loader_factory_),
base::TimeDelta::FromMilliseconds(delay));
}
void WellKnownChangePasswordStateTest::RespondeToChangePasswordRequest(
net::HttpStatusCode status,
int delay) {
base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(
&WellKnownChangePasswordState::SetChangePasswordResponseCode,
base::Unretained(&state_), status),
base::TimeDelta::FromMilliseconds(delay));
}
TEST_P(WellKnownChangePasswordStateTest, Support_Ok) {
ResponseDelayParams params = GetParam();
EXPECT_CALL(*delegate(), OnProcessingFinished(true));
RespondeToChangePasswordRequest(net::HTTP_OK, params.change_password_delay);
RespondeToNonExistingRequest(net::HTTP_NOT_FOUND, params.not_exist_delay);
FastForwardPostTasks();
}
TEST_P(WellKnownChangePasswordStateTest, Support_PartialContent) {
ResponseDelayParams params = GetParam();
EXPECT_CALL(*delegate(), OnProcessingFinished(true));
RespondeToChangePasswordRequest(net::HTTP_PARTIAL_CONTENT,
params.change_password_delay);
RespondeToNonExistingRequest(net::HTTP_NOT_FOUND, params.not_exist_delay);
FastForwardPostTasks();
}
TEST_P(WellKnownChangePasswordStateTest, NoSupport_NotFound) {
ResponseDelayParams params = GetParam();
EXPECT_CALL(*delegate(), OnProcessingFinished(false));
RespondeToChangePasswordRequest(net::HTTP_NOT_FOUND,
params.change_password_delay);
RespondeToNonExistingRequest(net::HTTP_NOT_FOUND, params.not_exist_delay);
FastForwardPostTasks();
}
TEST_P(WellKnownChangePasswordStateTest, NoSupport_Ok) {
ResponseDelayParams params = GetParam();
EXPECT_CALL(*delegate(), OnProcessingFinished(false));
RespondeToChangePasswordRequest(net::HTTP_OK, params.change_password_delay);
RespondeToNonExistingRequest(net::HTTP_OK, params.not_exist_delay);
FastForwardPostTasks();
}
// Expect no support because the State should not handle redirects.
TEST_P(WellKnownChangePasswordStateTest, NoSupport_Redirect) {
ResponseDelayParams params = GetParam();
EXPECT_CALL(*delegate(), OnProcessingFinished(false));
RespondeToChangePasswordRequest(net::HTTP_PERMANENT_REDIRECT,
params.change_password_delay);
RespondeToNonExistingRequest(net::HTTP_NOT_FOUND, params.not_exist_delay);
FastForwardPostTasks();
}
constexpr ResponseDelayParams kDelayParams[] = {{0, 1}, {1, 0}};
INSTANTIATE_TEST_SUITE_P(All,
WellKnownChangePasswordStateTest,
::testing::ValuesIn(kDelayParams));
} // namespace password_manager
...@@ -353,7 +353,7 @@ Refer to README.md for content description and update process. ...@@ -353,7 +353,7 @@ Refer to README.md for content description and update process.
<item id="webstore_install_helper" hash_code="25921771" type="0" content_hash_code="10206361" os_list="linux,windows" file_path="chrome/browser/extensions/webstore_install_helper.cc"/> <item id="webstore_install_helper" hash_code="25921771" type="0" content_hash_code="10206361" os_list="linux,windows" file_path="chrome/browser/extensions/webstore_install_helper.cc"/>
<item id="webstore_installer" hash_code="18764319" type="0" content_hash_code="70871152" os_list="linux,windows" file_path="chrome/browser/extensions/webstore_installer.cc"/> <item id="webstore_installer" hash_code="18764319" type="0" content_hash_code="70871152" os_list="linux,windows" file_path="chrome/browser/extensions/webstore_installer.cc"/>
<item id="webui_content_scripts_download" hash_code="100545943" type="0" content_hash_code="119898059" os_list="linux,windows" file_path="extensions/browser/guest_view/web_view/web_ui/web_ui_url_fetcher.cc"/> <item id="webui_content_scripts_download" hash_code="100545943" type="0" content_hash_code="119898059" os_list="linux,windows" file_path="extensions/browser/guest_view/web_view/web_ui/web_ui_url_fetcher.cc"/>
<item id="well_known_path_that_should_not_exist" hash_code="134618785" type="0" content_hash_code="55913167" os_list="linux,windows" file_path="chrome/browser/ui/passwords/well_known_change_password_navigation_throttle.cc"/> <item id="well_known_path_that_should_not_exist" hash_code="134618785" type="0" content_hash_code="55913167" os_list="linux,windows" file_path="components/password_manager/core/browser/well_known_change_password_state.cc"/>
<item id="worker_script_load" hash_code="72087791" type="0" content_hash_code="24889169" os_list="linux,windows" file_path="content/browser/worker_host/worker_script_fetcher.cc"/> <item id="worker_script_load" hash_code="72087791" type="0" content_hash_code="24889169" os_list="linux,windows" file_path="content/browser/worker_host/worker_script_fetcher.cc"/>
<item id="xmpp_signal_strategy" hash_code="88906454" type="0" deprecated="2019-07-16" content_hash_code="88958321" file_path=""/> <item id="xmpp_signal_strategy" hash_code="88906454" type="0" deprecated="2019-07-16" content_hash_code="88958321" file_path=""/>
</annotations> </annotations>
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