// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/passwords/model/well_known_change_password_tab_helper.h"
#import <Foundation/Foundation.h>
#import "base/logging.h"
#import "components/affiliations/core/browser/affiliation_service.h"
#import "components/password_manager/core/common/password_manager_features.h"
#import "components/ukm/ios/ukm_url_recorder.h"
#import "ios/chrome/browser/affiliations/model/ios_chrome_affiliation_service_factory.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/web/public/navigation/navigation_context.h"
#import "net/base/apple/url_conversions.h"
#import "services/metrics/public/cpp/ukm_builders.h"
#import "services/metrics/public/cpp/ukm_recorder.h"
#import "services/network/public/cpp/shared_url_loader_factory.h"
#import "ui/base/page_transition_types.h"
using password_manager::WellKnownChangePasswordTabHelper;
WellKnownChangePasswordTabHelper::WellKnownChangePasswordTabHelper(
web::WebState* web_state)
: web::WebStatePolicyDecider(web_state), web_state_(web_state) {
affiliation_service_ = IOSChromeAffiliationServiceFactory::GetForBrowserState(
web_state->GetBrowserState());
web_state->AddObserver(this);
}
WellKnownChangePasswordTabHelper::~WellKnownChangePasswordTabHelper() = default;
void WellKnownChangePasswordTabHelper::DidStartNavigation(
web::WebState* web_state,
web::NavigationContext* navigation_context) {
// `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;
}
}
void WellKnownChangePasswordTabHelper::ShouldAllowRequest(
NSURLRequest* request,
web::WebStatePolicyDecider::RequestInfo request_info,
web::WebStatePolicyDecider::PolicyDecisionCallback callback) {
GURL request_url = net::GURLWithNSURL(request.URL);
// 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) {
if (request_info.target_frame_is_main &&
ui::PageTransitionCoreTypeIs(request_info.transition_type,
ui::PAGE_TRANSITION_TYPED) &&
web_state_->GetLastCommittedURL().is_empty() && // empty tab history
IsWellKnownChangePasswordUrl(request_url)) {
request_url_ = request_url;
if (affiliation_service_->GetChangePasswordURL(request_url_).is_empty()) {
well_known_change_password_state_.PrefetchChangePasswordURLs(
affiliation_service_, {request_url_});
}
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;
}
}
std::move(callback).Run(web::WebStatePolicyDecider::PolicyDecision::Allow());
}
void WellKnownChangePasswordTabHelper::ShouldAllowResponse(
NSURLResponse* response,
web::WebStatePolicyDecider::ResponseInfo response_info,
web::WebStatePolicyDecider::PolicyDecisionCallback callback) {
GURL url = net::GURLWithNSURL(response.URL);
// True if the TabHelper expects the response from .well-known/change-password
// and only that navigation was started.
if (processing_state_ == kWaitingForResponse &&
[response isKindOfClass:[NSHTTPURLResponse class]]) {
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(
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(
web::WebState* web_state) {
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;
}
GURL redirect_url = affiliation_service_->GetChangePasswordURL(request_url_);
if (is_supported || redirect_url == request_url_) {
std::move(response_policy_callback_)
.Run(web::WebStatePolicyDecider::PolicyDecision::Allow());
RecordMetric(WellKnownChangePasswordResult::kUsedWellKnownChangePassword);
} else {
std::move(response_policy_callback_)
.Run(web::WebStatePolicyDecider::PolicyDecision::Cancel());
if (redirect_url.is_valid()) {
RecordMetric(WellKnownChangePasswordResult::kFallbackToOverrideUrl);
Redirect(redirect_url);
} else {
RecordMetric(WellKnownChangePasswordResult::kFallbackToOriginUrl);
Redirect(request_url_.DeprecatedGetOriginAsURL());
}
}
}
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);
}
}
void WellKnownChangePasswordTabHelper::RecordMetric(
WellKnownChangePasswordResult result) {
ukm::SourceId source_id = ukm::GetSourceIdForWebStateDocument(web_state_);
ukm::builders::PasswordManager_WellKnownChangePasswordResult(source_id)
.SetWellKnownChangePasswordResult(static_cast<int64_t>(result))
.Record(ukm::UkmRecorder::Get());
}
WEB_STATE_USER_DATA_KEY_IMPL(WellKnownChangePasswordTabHelper)