Commit be65655c authored by Jonathan Mengedoht's avatar Jonathan Mengedoht Committed by Commit Bot

Add processing logic to WellKnownChangePasswordTabHelper

Bug: 927473
Change-Id: Icf7010f41392f761117915dac92f4f390b41f2a0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2335289
Commit-Queue: Jonathan Mengedoht <mengedoht@google.com>
Reviewed-by: default avatarVasilii Sukhanov <vasilii@chromium.org>
Reviewed-by: default avatarEugene But <eugenebut@chromium.org>
Reviewed-by: default avatarAli Juma <ajuma@chromium.org>
Reviewed-by: default avatarGauthier Ambard <gambard@chromium.org>
Cr-Commit-Position: refs/heads/master@{#798659}
parent 5bd2ea8d
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#ifndef IOS_CHROME_BROWSER_PASSWORDS_WELL_KNOWN_CHANGE_PASSWORD_TAB_HELPER_H_ #ifndef IOS_CHROME_BROWSER_PASSWORDS_WELL_KNOWN_CHANGE_PASSWORD_TAB_HELPER_H_
#define IOS_CHROME_BROWSER_PASSWORDS_WELL_KNOWN_CHANGE_PASSWORD_TAB_HELPER_H_ #define IOS_CHROME_BROWSER_PASSWORDS_WELL_KNOWN_CHANGE_PASSWORD_TAB_HELPER_H_
#include "components/password_manager/core/browser/well_known_change_password_state.h"
#include "ios/web/public/navigation/web_state_policy_decider.h" #include "ios/web/public/navigation/web_state_policy_decider.h"
#include "ios/web/public/web_state_observer.h" #include "ios/web/public/web_state_observer.h"
#import "ios/web/public/web_state_user_data.h" #import "ios/web/public/web_state_user_data.h"
...@@ -17,30 +18,65 @@ namespace password_manager { ...@@ -17,30 +18,65 @@ namespace password_manager {
// the change-password url. If the site does not support the change password // the change-password url. If the site does not support the change password
// url, the user gets redirected to the base path '/'. If the sites supports the // url, the user gets redirected to the base path '/'. If the sites supports the
// standard, the request is allowed and the navigation is not changed. // standard, the request is allowed and the navigation is not changed.
// The TabHelper only intercepts the navigation if .well-known/change-password
// is opened in a new tab.
class WellKnownChangePasswordTabHelper class WellKnownChangePasswordTabHelper
: public web::WebStatePolicyDecider, : public password_manager::WellKnownChangePasswordStateDelegate,
public web::WebStateObserver, public web::WebStateObserver,
public web::WebStatePolicyDecider,
public web::WebStateUserData<WellKnownChangePasswordTabHelper> { public web::WebStateUserData<WellKnownChangePasswordTabHelper> {
public: public:
~WellKnownChangePasswordTabHelper() override; ~WellKnownChangePasswordTabHelper() override;
// web::WebStatePolicyDecider:
PolicyDecision ShouldAllowRequest(NSURLRequest* request, PolicyDecision ShouldAllowRequest(NSURLRequest* request,
const RequestInfo& request_info) override; const RequestInfo& request_info) override;
void ShouldAllowResponse( void ShouldAllowResponse(
NSURLResponse* response, NSURLResponse* response,
bool for_main_frame, bool for_main_frame,
web::WebStatePolicyDecider::PolicyDecisionCallback callback) override; web::WebStatePolicyDecider::PolicyDecisionCallback callback) override;
void DidRedirectNavigation(
web::WebState* web_state,
web::NavigationContext* navigation_context) override;
void WebStateDestroyed() override; void WebStateDestroyed() override;
// web::WebStateObserver:
void DidStartNavigation(web::WebState* web_state,
web::NavigationContext* navigation_context) override;
void RenderProcessGone(web::WebState* web_state) override;
void WebStateDestroyed(web::WebState* web_state) override; void WebStateDestroyed(web::WebState* web_state) override;
private: private:
friend class web::WebStateUserData<WellKnownChangePasswordTabHelper>;
// Describes the progress and state of the .well-known processing.
enum ProcessingState {
// The TabHelper only checks the first request. This state signals if the
// current request is the first.
kWaitingForFirstRequest = 0,
// When the first request is for .well-known/change-password, the TabHelper
// is waiting for the response.
kWaitingForResponse,
// When the Response arrived the TabHelper waits for the
// well_known_change_password_state_ to notify is the .well-known is
// supported.
kResponesReceived,
// When the first request is not to .well-known/change-password, or another
// navigation is started the TabHelper stops any custom processing.
kInactive,
};
explicit WellKnownChangePasswordTabHelper(web::WebState* web_state); explicit WellKnownChangePasswordTabHelper(web::WebState* web_state);
// WellKnownChangePasswordStateDelegate:
void OnProcessingFinished(bool is_supported) override;
// Redirects to a given URL in the same tab.
void Redirect(const GURL& url);
friend class web::WebStateUserData<WellKnownChangePasswordTabHelper>;
web::WebState* web_state_ = nullptr; web::WebState* web_state_ = nullptr;
ProcessingState processing_state_ = kWaitingForFirstRequest;
// Stores the request_url if the first navigation was to
// .well-known/change-password. It is later used to redirect to the origin.
GURL request_url_;
// Stored callback from ShouldAllowResponse when the response is from
// .well-known/change-password.
web::WebStatePolicyDecider::PolicyDecisionCallback response_policy_callback_;
password_manager::WellKnownChangePasswordState
well_known_change_password_state_{this};
WEB_STATE_USER_DATA_KEY_DECL(); WEB_STATE_USER_DATA_KEY_DECL();
}; };
......
...@@ -12,14 +12,13 @@ ...@@ -12,14 +12,13 @@
#include "ios/chrome/browser/browser_state/chrome_browser_state.h" #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
#import "ios/web/public/navigation/navigation_context.h" #import "ios/web/public/navigation/navigation_context.h"
#import "net/base/mac/url_conversions.h" #import "net/base/mac/url_conversions.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#if !defined(__has_feature) || !__has_feature(objc_arc) #if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support." #error "This file requires ARC support."
#endif #endif
namespace {
using password_manager::WellKnownChangePasswordTabHelper; using password_manager::WellKnownChangePasswordTabHelper;
}
WellKnownChangePasswordTabHelper::WellKnownChangePasswordTabHelper( WellKnownChangePasswordTabHelper::WellKnownChangePasswordTabHelper(
web::WebState* web_state) web::WebState* web_state)
...@@ -29,26 +28,46 @@ WellKnownChangePasswordTabHelper::WellKnownChangePasswordTabHelper( ...@@ -29,26 +28,46 @@ WellKnownChangePasswordTabHelper::WellKnownChangePasswordTabHelper(
WellKnownChangePasswordTabHelper::~WellKnownChangePasswordTabHelper() = default; WellKnownChangePasswordTabHelper::~WellKnownChangePasswordTabHelper() = default;
void WellKnownChangePasswordTabHelper::DidRedirectNavigation( void WellKnownChangePasswordTabHelper::DidStartNavigation(
web::WebState* web_state, web::WebState* web_state,
web::NavigationContext* navigation_context) { web::NavigationContext* navigation_context) {
// TODO(crbug.com/927473): handle redirects // |request_url_| is set when the first request goes to
// .well-known-change-password. If the navigation url and |request_url_| are
// equal, DidStartNavigation() is called for the .well-known/change-password
// navigation. Otherwise a different navigation was started.
if (!(request_url_.is_valid() &&
request_url_ == navigation_context->GetUrl())) {
processing_state_ = kInactive;
}
} }
web::WebStatePolicyDecider::PolicyDecision web::WebStatePolicyDecider::PolicyDecision
WellKnownChangePasswordTabHelper::ShouldAllowRequest( WellKnownChangePasswordTabHelper::ShouldAllowRequest(
NSURLRequest* request, NSURLRequest* request,
const RequestInfo& request_info) { const RequestInfo& request_info) {
const GURL& request_url = net::GURLWithNSURL(request.URL); GURL request_url = net::GURLWithNSURL(request.URL);
// Boolean order important. First url then feature flag. Otherwise it messes // The custom behaviour is only used if the .well-known/change-password
// request if the request is the main frame opened in a new tab.
if (processing_state_ == kWaitingForFirstRequest) {
// Boolean order important, feature flag last. Otherwise it messes
// with usage statistics of control and experimental group (UMA). // with usage statistics of control and experimental group (UMA).
if (request_info.target_frame_is_main && if (request_info.target_frame_is_main &&
web_state_->GetLastCommittedURL().is_empty() && // empty tab history
IsWellKnownChangePasswordUrl(request_url) && IsWellKnownChangePasswordUrl(request_url) &&
base::FeatureList::IsEnabled( base::FeatureList::IsEnabled(
password_manager::features::kWellKnownChangePassword)) { password_manager::features::kWellKnownChangePassword)) {
// TODO(crbug.com/927473): Make request to non existing resource and check request_url_ = request_url;
// if well-known/change-password is supported.
auto url_loader_factory =
web_state_->GetBrowserState()->GetSharedURLLoaderFactory();
well_known_change_password_state_.FetchNonExistingResource(
url_loader_factory.get(), request_url);
processing_state_ = kWaitingForResponse;
} else {
processing_state_ = kInactive;
} }
}
return web::WebStatePolicyDecider::PolicyDecision::Allow(); return web::WebStatePolicyDecider::PolicyDecision::Allow();
} }
...@@ -56,21 +75,31 @@ void WellKnownChangePasswordTabHelper::ShouldAllowResponse( ...@@ -56,21 +75,31 @@ void WellKnownChangePasswordTabHelper::ShouldAllowResponse(
NSURLResponse* response, NSURLResponse* response,
bool for_main_frame, bool for_main_frame,
web::WebStatePolicyDecider::PolicyDecisionCallback callback) { web::WebStatePolicyDecider::PolicyDecisionCallback callback) {
const GURL& url = net::GURLWithNSURL(response.URL); GURL url = net::GURLWithNSURL(response.URL);
// Boolean order important, feature flag check last. Otherwise it messes // True if the TabHelper expects the response from .well-known/change-password
// with usage statistics. // and only that navigation was started.
// We only want to handle main_frame requests to keep consistency with the if (processing_state_ == kWaitingForResponse &&
// NavigationThrottle implementation. [response isKindOfClass:[NSHTTPURLResponse class]] &&
if (!for_main_frame || !IsWellKnownChangePasswordUrl(url) || base::FeatureList::IsEnabled(
!base::FeatureList::IsEnabled(
password_manager::features::kWellKnownChangePassword)) { password_manager::features::kWellKnownChangePassword)) {
// TODO(crbug.com/927473): Handle .well-known/change-passord response. processing_state_ = kResponesReceived;
DCHECK(url.SchemeIsHTTPOrHTTPS());
response_policy_callback_ = std::move(callback);
well_known_change_password_state_.SetChangePasswordResponseCode(
static_cast<NSHTTPURLResponse*>(response).statusCode);
} else {
std::move(callback).Run( std::move(callback).Run(
web::WebStatePolicyDecider::PolicyDecision::Allow()); web::WebStatePolicyDecider::PolicyDecision::Allow());
return;
} }
// TODO(crbug.com/927473): Handle Response }
std::move(callback).Run(web::WebStatePolicyDecider::PolicyDecision::Allow());
void WellKnownChangePasswordTabHelper::RenderProcessGone(
web::WebState* web_state) {
if (!response_policy_callback_)
return;
std::move(response_policy_callback_)
.Run(web::WebStatePolicyDecider::PolicyDecision::Cancel());
} }
void WellKnownChangePasswordTabHelper::WebStateDestroyed() {} void WellKnownChangePasswordTabHelper::WebStateDestroyed() {}
...@@ -78,6 +107,33 @@ void WellKnownChangePasswordTabHelper::WebStateDestroyed() {} ...@@ -78,6 +107,33 @@ void WellKnownChangePasswordTabHelper::WebStateDestroyed() {}
void WellKnownChangePasswordTabHelper::WebStateDestroyed( void WellKnownChangePasswordTabHelper::WebStateDestroyed(
web::WebState* web_state) { web::WebState* web_state) {
web_state->RemoveObserver(this); web_state->RemoveObserver(this);
if (!response_policy_callback_)
return;
std::move(response_policy_callback_)
.Run(web::WebStatePolicyDecider::PolicyDecision::Cancel());
}
void WellKnownChangePasswordTabHelper::OnProcessingFinished(bool is_supported) {
if (!response_policy_callback_)
return;
if (is_supported) {
std::move(response_policy_callback_)
.Run(web::WebStatePolicyDecider::PolicyDecision::Allow());
} else {
std::move(response_policy_callback_)
.Run(web::WebStatePolicyDecider::PolicyDecision::Cancel());
Redirect(request_url_.GetOrigin());
}
}
void WellKnownChangePasswordTabHelper::Redirect(const GURL& url) {
// Making sure there was no other navigation started we could override.
if (processing_state_ == kResponesReceived) {
web::WebState::OpenURLParams params(url, web::Referrer(),
WindowOpenDisposition::CURRENT_TAB,
ui::PAGE_TRANSITION_LINK, false);
web_state_->OpenURL(params);
}
} }
WEB_STATE_USER_DATA_KEY_IMPL(WellKnownChangePasswordTabHelper) WEB_STATE_USER_DATA_KEY_IMPL(WellKnownChangePasswordTabHelper)
// Copyright 2020 The Chromium Authors. All rights reserved. // Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#import "ios/chrome/browser/passwords/well_known_change_password_tab_helper.h" #import "ios/chrome/browser/passwords/well_known_change_password_tab_helper.h"
#import <Foundation/Foundation.h>
#include "base/run_loop.h"
#import "base/test/ios/wait_util.h"
#include "base/test/scoped_feature_list.h"
#include "components/password_manager/core/browser/well_known_change_password_util.h"
#include "components/password_manager/core/common/password_manager_features.h"
#import "ios/web/public/navigation/navigation_manager.h"
#import "ios/web/public/test/fakes/test_web_client.h"
#import "ios/web/public/test/fakes/test_web_state_delegate.h"
#import "ios/web/public/test/navigation_test_util.h"
#include "ios/web/public/test/web_task_environment.h"
#include "ios/web/public/test/web_test.h"
#include "ios/web/public/test/web_test_with_web_state.h"
#import "ios/web/public/test/web_view_content_test_util.h"
#include "net/cert/x509_certificate.h"
#include "net/http/http_status_code.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"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.h"
#if !defined(__has_feature) || !__has_feature(objc_arc) #if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support." #error "This file requires ARC support."
#endif #endif
namespace {
using base::test::ios::WaitUntilConditionOrTimeout;
using net::test_server::BasicHttpResponse;
using net::test_server::EmbeddedTestServer;
using net::test_server::EmbeddedTestServerHandle;
using net::test_server::HttpRequest;
using net::test_server::HttpResponse;
using password_manager::kWellKnownChangePasswordPath;
using password_manager::kWellKnownNotExistingResourcePath;
// ServerResponse describes how a server should respond to a given path.
struct ServerResponse {
net::HttpStatusCode status_code;
std::vector<std::pair<std::string, std::string>> headers;
};
} // namespace
// This test uses a mockserver to simulate different response. To handle the
// url_loader requests we also mock the response for the url_loader_factory.
class WellKnownChangePasswordTabHelperTest : public web::TestWebClient,
public web::WebTestWithWebState {
public:
WellKnownChangePasswordTabHelperTest() {
feature_list_.InitAndEnableFeature(
password_manager::features::kWellKnownChangePassword);
test_server_->RegisterRequestHandler(base::BindRepeating(
&WellKnownChangePasswordTabHelperTest::HandleRequest,
base::Unretained(this)));
}
void SetUp() override {
web::WebTestWithWebState::SetUp();
EXPECT_TRUE(test_server_->InitializeAndListen());
test_server_->StartAcceptingConnections();
web_state()->SetDelegate(&delegate_);
password_manager::WellKnownChangePasswordTabHelper::CreateForWebState(
web_state());
SetSharedURLLoaderFactory(
base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
&test_url_loader_factory_));
}
// Sets a response for the |test_url_loader_factory_| with the |test_server_|
// as the host.
void SetUrlLoaderResponse(const std::string& path,
net::HttpStatusCode status_code) {
test_url_loader_factory_.AddResponse(test_server_->GetURL(path).spec(), "",
status_code);
}
// Waits until the navigation is complete and waits for backgroundtasks to
// complete. Returns false when timed out.
bool WaitUntilLoaded();
// Returns the url after the navigation is complete.
GURL GetNavigatedUrl() const;
// Maps a path to a ServerResponse config object.
base::flat_map<std::string, ServerResponse> path_response_map_;
std::unique_ptr<EmbeddedTestServer> test_server_ =
std::make_unique<EmbeddedTestServer>();
private:
// Returns a response for the given request. Uses |path_response_map_| to
// construct the response. Returns nullptr when the path is not defined in
// |path_response_map_|.
std::unique_ptr<HttpResponse> HandleRequest(const HttpRequest& request);
base::test::ScopedFeatureList feature_list_;
network::TestURLLoaderFactory test_url_loader_factory_;
web::TestWebStateDelegate delegate_;
};
GURL WellKnownChangePasswordTabHelperTest::GetNavigatedUrl() const {
web::URLVerificationTrustLevel trust_level =
web::URLVerificationTrustLevel::kAbsolute;
GURL url = web_state()->GetCurrentURL(&trust_level);
// When redirecting with WebState::OpenURL() |web_state_| is not
// updated, we only see the registered request in
// TestWebStateDelegate::last_open_url_request().
if (delegate_.last_open_url_request()) {
url = delegate_.last_open_url_request()->params.url;
}
return url;
}
bool WellKnownChangePasswordTabHelperTest::WaitUntilLoaded() {
return WaitUntilConditionOrTimeout(base::test::ios::kWaitForPageLoadTimeout,
^{
WaitForBackgroundTasks();
return !web_state()->IsLoading();
});
}
std::unique_ptr<HttpResponse>
WellKnownChangePasswordTabHelperTest::HandleRequest(
const HttpRequest& request) {
GURL absolute_url = test_server_->GetURL(request.relative_url);
std::string path = absolute_url.path();
auto it = path_response_map_.find(absolute_url.path_piece());
if (it == path_response_map_.end())
return nullptr;
const ServerResponse& config = it->second;
auto http_response = std::make_unique<BasicHttpResponse>();
http_response->set_code(config.status_code);
http_response->set_content_type("text/plain");
for (auto header_pair : config.headers) {
http_response->AddCustomHeader(header_pair.first, header_pair.second);
}
return http_response;
}
TEST_F(WellKnownChangePasswordTabHelperTest, SupportForChangePassword) {
path_response_map_[kWellKnownChangePasswordPath] = {net::HTTP_OK, {}};
SetUrlLoaderResponse(kWellKnownNotExistingResourcePath, net::HTTP_NOT_FOUND);
web::test::LoadUrl(web_state(),
test_server_->GetURL(kWellKnownChangePasswordPath));
ASSERT_TRUE(WaitUntilLoaded());
EXPECT_EQ(GetNavigatedUrl().path(), kWellKnownChangePasswordPath);
}
TEST_F(WellKnownChangePasswordTabHelperTest,
SupportForChangePassword_WithRedirect) {
path_response_map_[kWellKnownChangePasswordPath] = {
net::HTTP_PERMANENT_REDIRECT,
{std::make_pair("Location", "/change-password")}};
path_response_map_["/change-password"] = {net::HTTP_OK, {}};
SetUrlLoaderResponse(kWellKnownNotExistingResourcePath, net::HTTP_NOT_FOUND);
web::test::LoadUrl(web_state(),
test_server_->GetURL(kWellKnownChangePasswordPath));
ASSERT_TRUE(WaitUntilLoaded());
EXPECT_EQ(GetNavigatedUrl().path(), "/change-password");
}
TEST_F(WellKnownChangePasswordTabHelperTest,
NoSupportForChangePassword_NotFound) {
path_response_map_[kWellKnownChangePasswordPath] = {net::HTTP_NOT_FOUND, {}};
path_response_map_["/"] = {net::HTTP_OK, {}};
SetUrlLoaderResponse(kWellKnownNotExistingResourcePath, net::HTTP_NOT_FOUND);
web::test::LoadUrl(web_state(),
test_server_->GetURL(kWellKnownChangePasswordPath));
ASSERT_TRUE(WaitUntilLoaded());
EXPECT_EQ(GetNavigatedUrl().path(), "/");
}
TEST_F(WellKnownChangePasswordTabHelperTest, NoSupportForChangePassword_Ok) {
path_response_map_[kWellKnownChangePasswordPath] = {net::HTTP_OK, {}};
path_response_map_["/"] = {net::HTTP_OK, {}};
SetUrlLoaderResponse(kWellKnownNotExistingResourcePath, net::HTTP_OK);
web::test::LoadUrl(web_state(),
test_server_->GetURL(kWellKnownChangePasswordPath));
ASSERT_TRUE(WaitUntilLoaded());
EXPECT_EQ(GetNavigatedUrl().path(), "/");
}
TEST_F(WellKnownChangePasswordTabHelperTest,
NoSupportForChangePassword_WithRedirect) {
path_response_map_[kWellKnownChangePasswordPath] = {
net::HTTP_PERMANENT_REDIRECT, {std::make_pair("Location", "/not-found")}};
path_response_map_["/not-found"] = {net::HTTP_NOT_FOUND, {}};
SetUrlLoaderResponse(kWellKnownNotExistingResourcePath, net::HTTP_OK);
web::test::LoadUrl(web_state(),
test_server_->GetURL(kWellKnownChangePasswordPath));
ASSERT_TRUE(WaitUntilLoaded());
EXPECT_EQ(GetNavigatedUrl().path(), "/");
}
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