Commit 34ee95d5 authored by yilkal's avatar yilkal Committed by Commit Bot

Prevent multiple requests on the same tab.

This CL prevents users from making multiple requests
from the same tab for the same host address.

The requests can be repeated by opening a new tab.
The requests can be repeated in the next session.

Bug: 1045155
Change-Id: If0a7549fe7509ff6ece8c1701e449294a85c56aa
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2020543Reviewed-by: default avatarAga Wronska <agawronska@chromium.org>
Commit-Queue: Yilkal Abe <yilkal@chromium.org>
Cr-Commit-Position: refs/heads/master@{#736221}
parent 892f7d50
......@@ -42,11 +42,11 @@
</div>
</div>
<div class="button-container">
<button id="request-access-button" class="ask-permission-button">
<button id="request-access-button" class="ask-permission-button" hidden>
$i18n{requestAccessButton}
</button>
<div id="details-button-container">
<button id="show-details-link" class="details-button small-link">
<button id="show-details-link" class="details-button small-link" hidden>
$i18n{showDetailsLink}
</button>
<button id="hide-details-link" class="details-button small-link" hidden>
......
......@@ -38,14 +38,6 @@ function makeImageSet(url1x, url2x) {
function initialize() {
var allowAccessRequests = loadTimeData.getBoolean('allowAccessRequests');
if (allowAccessRequests) {
$('request-access-button').onclick = function(event) {
$('request-access-button').hidden = true;
sendCommand('request');
};
} else {
$('request-access-button').hidden = true;
}
var avatarURL1x = loadTimeData.getString('avatarURL1x');
var avatarURL2x = loadTimeData.getString('avatarURL2x');
var custodianName = loadTimeData.getString('custodianName');
......@@ -72,10 +64,30 @@ function initialize() {
'secondCustodianEmail');
}
}
var already_requested_access = loadTimeData.getBoolean('alreadySentRequest');
if (already_requested_access) {
var is_main_frame = loadTimeData.getBoolean('isMainFrame');
requestCreated(true, is_main_frame);
return;
}
if (allowAccessRequests) {
$('request-access-button').hidden = false;
$('request-access-button').onclick = function(event) {
$('request-access-button').hidden = true;
sendCommand('request');
};
} else {
$('request-access-button').hidden = true;
}
$('back-button').onclick = function(event) {
sendCommand('back');
};
if (loadTimeData.getBoolean('showFeedbackLink')) {
$('show-details-link').hidden = false;
$('show-details-link').onclick = function(event) {
showDetails = true;
$('show-details-link').hidden = true;
......@@ -105,6 +117,16 @@ function initialize() {
*/
function setRequestStatus(isSuccessful, isMainFrame) {
console.log('setRequestStatus(' + isSuccessful +')');
requestCreated(isSuccessful, isMainFrame)
}
/**
* Updates the interstitial to show that the request failed or was sent.
* @param {boolean} isSuccessful Whether the request was successful or not.
* @param {boolean} isMainFrame Whether the interstitial is being shown in main
* frame.
*/
function requestCreated(isSuccessful, isMainFrame) {
$('block-page-header').hidden = true;
$('block-page-message').hidden = true;
$('hide-details-link').hidden = true;
......
......@@ -81,6 +81,26 @@ std::string BuildHtml(bool allow_access_requests,
bool is_deprecated,
FilteringBehaviorReason reason,
const std::string& app_locale) {
return BuildHtml(allow_access_requests, profile_image_url, profile_image_url2,
custodian, custodian_email, second_custodian,
second_custodian_email, is_child_account, is_deprecated,
reason, app_locale, /* already_sent_request */ false,
/* is_main_frame */ true);
}
std::string BuildHtml(bool allow_access_requests,
const std::string& profile_image_url,
const std::string& profile_image_url2,
const std::string& custodian,
const std::string& custodian_email,
const std::string& second_custodian,
const std::string& second_custodian_email,
bool is_child_account,
bool is_deprecated,
FilteringBehaviorReason reason,
const std::string& app_locale,
bool already_sent_request,
bool is_main_frame) {
base::DictionaryValue strings;
strings.SetString("blockPageTitle",
l10n_util::GetStringUTF16(IDS_BLOCK_INTERSTITIAL_TITLE));
......@@ -97,6 +117,8 @@ std::string BuildHtml(bool allow_access_requests,
strings.SetString("custodianEmail", custodian_email);
strings.SetString("secondCustodianName", second_custodian);
strings.SetString("secondCustodianEmail", second_custodian_email);
strings.SetBoolean("alreadySentRequest", already_sent_request);
strings.SetBoolean("isMainFrame", is_main_frame);
base::string16 custodian16 = base::UTF8ToUTF16(custodian);
base::string16 block_header;
......
......@@ -26,6 +26,9 @@ int GetBlockMessageID(
bool is_child_account,
bool single_parent);
// This function assumes that the error page will be displayed in the main
// frame. It also assumes that there was no request already sent to access the
// blocked url.
std::string BuildHtml(bool allow_access_requests,
const std::string& profile_image_url,
const std::string& profile_image_url2,
......@@ -38,6 +41,20 @@ std::string BuildHtml(bool allow_access_requests,
FilteringBehaviorReason reason,
const std::string& app_locale);
std::string BuildHtml(bool allow_access_requests,
const std::string& profile_image_url,
const std::string& profile_image_url2,
const std::string& custodian,
const std::string& custodian_email,
const std::string& second_custodian,
const std::string& second_custodian_email,
bool is_child_account,
bool is_deprecated,
FilteringBehaviorReason reason,
const std::string& app_locale,
bool already_sent_request,
bool is_main_frame);
} // namespace supervised_user_error_page
#endif // CHROME_BROWSER_SUPERVISED_USER_SUPERVISED_USER_ERROR_PAGE_SUPERVISED_USER_ERROR_PAGE_H_
......@@ -180,7 +180,9 @@ SupervisedUserInterstitial::~SupervisedUserInterstitial() {}
// static
std::string SupervisedUserInterstitial::GetHTMLContents(
Profile* profile,
supervised_user_error_page::FilteringBehaviorReason reason) {
supervised_user_error_page::FilteringBehaviorReason reason,
bool already_sent_request,
bool is_main_frame) {
bool is_child_account = profile->IsChild();
bool is_deprecated = !is_child_account;
......@@ -206,7 +208,8 @@ std::string SupervisedUserInterstitial::GetHTMLContents(
allow_access_requests, profile_image_url, profile_image_url2, custodian,
custodian_email, second_custodian, second_custodian_email,
is_child_account, is_deprecated, reason,
g_browser_process->GetApplicationLocale());
g_browser_process->GetApplicationLocale(), already_sent_request,
is_main_frame);
}
void SupervisedUserInterstitial::GoBack() {
......
......@@ -37,7 +37,9 @@ class SupervisedUserInterstitial {
static std::string GetHTMLContents(
Profile* profile,
supervised_user_error_page::FilteringBehaviorReason reason);
supervised_user_error_page::FilteringBehaviorReason reason,
bool already_sent_request,
bool is_main_frame);
void GoBack();
void RequestPermission(base::OnceCallback<void(bool)> callback);
......
......@@ -77,15 +77,15 @@ void SupervisedUserNavigationObserver::OnRequestBlocked(
supervised_user_error_page::FilteringBehaviorReason reason,
int64_t navigation_id,
int frame_id,
const base::Callback<
void(SupervisedUserNavigationThrottle::CallbackActions)>& callback) {
const OnInterstitialResultCallback& callback) {
SupervisedUserNavigationObserver* navigation_observer =
SupervisedUserNavigationObserver::FromWebContents(web_contents);
// Cancel the navigation if there is no navigation observer.
if (!navigation_observer) {
callback.Run(
SupervisedUserNavigationThrottle::CallbackActions::kCancelNavigation);
SupervisedUserNavigationThrottle::CallbackActions::kCancelNavigation,
/* already_requested_permission */ false, /* is_main_frame */ false);
return;
}
......@@ -172,6 +172,8 @@ void SupervisedUserNavigationObserver::OnURLFilterChanged() {
web_contents()->GetLastCommittedURL(),
main_frame_process_id, routing_id));
MaybeUpdateRequestedHosts();
Profile* profile =
Profile::FromBrowserContext(web_contents()->GetBrowserContext());
SupervisedUserService* service =
......@@ -194,8 +196,7 @@ void SupervisedUserNavigationObserver::OnRequestBlockedInternal(
supervised_user_error_page::FilteringBehaviorReason reason,
int64_t navigation_id,
int frame_id,
const base::Callback<
void(SupervisedUserNavigationThrottle::CallbackActions)>& callback) {
const OnInterstitialResultCallback& callback) {
// TODO(bauerb): Use SaneTime when available.
base::Time timestamp = base::Time::Now();
// Create a history entry for the attempt and mark it as such. This history
......@@ -273,16 +274,20 @@ void SupervisedUserNavigationObserver::MaybeShowInterstitial(
bool initial_page_load,
int64_t navigation_id,
int frame_id,
const base::Callback<
void(SupervisedUserNavigationThrottle::CallbackActions)>& callback) {
const OnInterstitialResultCallback& callback) {
std::unique_ptr<SupervisedUserInterstitial> interstitial =
SupervisedUserInterstitial::Create(web_contents(), url, reason, frame_id,
navigation_id);
supervised_user_interstitials_[frame_id] = std::move(interstitial);
bool already_requested = base::Contains(requested_hosts_, url.host());
bool is_main_frame =
frame_id == web_contents()->GetMainFrame()->GetFrameTreeNodeId();
callback.Run(SupervisedUserNavigationThrottle::CallbackActions::
kCancelWithInterstitial);
kCancelWithInterstitial,
already_requested, is_main_frame);
}
void SupervisedUserNavigationObserver::FilterRenderFrame(
......@@ -321,8 +326,15 @@ void SupervisedUserNavigationObserver::RequestPermission(
auto* render_frame_host = receiver_.GetCurrentTargetFrame();
int id = render_frame_host->GetFrameTreeNodeId();
if (base::Contains(supervised_user_interstitials_, id))
supervised_user_interstitials_[id]->RequestPermission(std::move(callback));
if (base::Contains(supervised_user_interstitials_, id)) {
SupervisedUserInterstitial* interstitial =
supervised_user_interstitials_[id].get();
interstitial->RequestPermission(
base::BindOnce(&SupervisedUserNavigationObserver::RequestCreated,
weak_ptr_factory_.GetWeakPtr(), std::move(callback),
interstitial->url().host()));
}
}
void SupervisedUserNavigationObserver::Feedback() {
......@@ -333,4 +345,29 @@ void SupervisedUserNavigationObserver::Feedback() {
supervised_user_interstitials_[id]->ShowFeedback();
}
void SupervisedUserNavigationObserver::RequestCreated(
RequestPermissionCallback callback,
const std::string& host,
bool successfully_created_request) {
if (successfully_created_request)
requested_hosts_.insert(host);
std::move(callback).Run(successfully_created_request);
}
void SupervisedUserNavigationObserver::MaybeUpdateRequestedHosts() {
SupervisedUserURLFilter::FilteringBehavior filtering_behavior;
for (auto iter = requested_hosts_.begin(); iter != requested_hosts_.end();) {
bool is_manual = url_filter_->GetManualFilteringBehaviorForURL(
GURL(*iter), &filtering_behavior);
if (is_manual && filtering_behavior ==
SupervisedUserURLFilter::FilteringBehavior::ALLOW) {
iter = requested_hosts_.erase(iter);
} else {
iter++;
}
}
}
WEB_CONTENTS_USER_DATA_KEY_IMPL(SupervisedUserNavigationObserver)
......@@ -5,7 +5,9 @@
#ifndef CHROME_BROWSER_SUPERVISED_USER_SUPERVISED_USER_NAVIGATION_OBSERVER_H_
#define CHROME_BROWSER_SUPERVISED_USER_SUPERVISED_USER_NAVIGATION_OBSERVER_H_
#include <map>
#include <memory>
#include <set>
#include <vector>
#include "base/macros.h"
......@@ -29,6 +31,9 @@ class RenderFrameHost;
class WebContents;
} // namespace content
using OnInterstitialResultCallback = base::RepeatingCallback<
void(SupervisedUserNavigationThrottle::CallbackActions, bool, bool)>;
class SupervisedUserNavigationObserver
: public content::WebContentsUserData<SupervisedUserNavigationObserver>,
public content::WebContentsObserver,
......@@ -53,8 +58,7 @@ class SupervisedUserNavigationObserver
supervised_user_error_page::FilteringBehaviorReason reason,
int64_t navigation_id,
int frame_id,
const base::Callback<
void(SupervisedUserNavigationThrottle::CallbackActions)>& callback);
const OnInterstitialResultCallback& callback);
void UpdateMainFrameFilteringStatus(
SupervisedUserURLFilter::FilteringBehavior behavior,
......@@ -85,10 +89,14 @@ class SupervisedUserNavigationObserver
void OnInterstitialDone(int frame_id);
const std::map<int, std::unique_ptr<SupervisedUserInterstitial>>&
interstitials_for_test() {
interstitials_for_test() const {
return supervised_user_interstitials_;
}
const std::set<std::string>& requested_hosts_for_test() const {
return requested_hosts_;
}
private:
friend class content::WebContentsUserData<SupervisedUserNavigationObserver>;
......@@ -99,8 +107,7 @@ class SupervisedUserNavigationObserver
supervised_user_error_page::FilteringBehaviorReason reason,
int64_t navigation_id,
int frame_id,
const base::Callback<
void(SupervisedUserNavigationThrottle::CallbackActions)>& callback);
const OnInterstitialResultCallback& callback);
void URLFilterCheckCallback(
const GURL& url,
......@@ -116,8 +123,7 @@ class SupervisedUserNavigationObserver
bool initial_page_load,
int64_t navigation_id,
int frame_id,
const base::Callback<
void(SupervisedUserNavigationThrottle::CallbackActions)>& callback);
const OnInterstitialResultCallback& callback);
// Filters the render frame host if render frame is live.
void FilterRenderFrame(content::RenderFrameHost* render_frame_host);
......@@ -129,6 +135,16 @@ class SupervisedUserNavigationObserver
void RequestPermission(RequestPermissionCallback callback) override;
void Feedback() override;
// When a request is successfully created, this method is called
// asynchronously.
void RequestCreated(RequestPermissionCallback callback,
const std::string& host,
bool successfully_created_request);
// Called when the url filter changes i.e. whitelist or blacklist change to
// clear up entries in |requested_hosts_| which have been whitelisted.
void MaybeUpdateRequestedHosts();
// Owned by SupervisedUserService.
const SupervisedUserURLFilter* url_filter_;
......@@ -140,6 +156,8 @@ class SupervisedUserNavigationObserver
std::map<int, std::unique_ptr<SupervisedUserInterstitial>>
supervised_user_interstitials_;
std::set<std::string> requested_hosts_;
SupervisedUserURLFilter::FilteringBehavior main_frame_filtering_behavior_ =
SupervisedUserURLFilter::FilteringBehavior::ALLOW;
supervised_user_error_page::FilteringBehaviorReason
......
......@@ -251,7 +251,9 @@ void SupervisedUserNavigationThrottle::OnCheckDone(
}
void SupervisedUserNavigationThrottle::OnInterstitialResult(
CallbackActions action) {
CallbackActions action,
bool already_sent_request,
bool is_main_frame) {
switch (action) {
case kCancelNavigation: {
CancelDeferredNavigation(CANCEL);
......@@ -262,7 +264,7 @@ void SupervisedUserNavigationThrottle::OnInterstitialResult(
SupervisedUserInterstitial::GetHTMLContents(
Profile::FromBrowserContext(
navigation_handle()->GetWebContents()->GetBrowserContext()),
reason_);
reason_, already_sent_request, is_main_frame);
CancelDeferredNavigation(content::NavigationThrottle::ThrottleCheckResult(
CANCEL, net::ERR_BLOCKED_BY_CLIENT, interstitial_html));
}
......
......@@ -52,7 +52,9 @@ class SupervisedUserNavigationThrottle : public content::NavigationThrottle {
supervised_user_error_page::FilteringBehaviorReason reason,
bool uncertain);
void OnInterstitialResult(CallbackActions continue_request);
void OnInterstitialResult(CallbackActions continue_request,
bool already_requested_permission,
bool is_main_frame);
const SupervisedUserURLFilter* url_filter_;
bool deferred_;
......
......@@ -285,6 +285,7 @@ class SupervisedUserIframeFilterTest
std::vector<int> GetBlockedFrames();
const GURL& GetBlockedFrameURL(int frame_id);
bool IsInterstitialBeingShownInFrame(int frame_id);
bool IsAskPermissionButtonBeingShown(int frame_id);
void RequestPermissionFromFrame(int frame_id);
void WaitForNavigationFinished(int frame_id, const GURL& url);
......@@ -295,6 +296,9 @@ class SupervisedUserIframeFilterTest
RenderFrameTracker* tracker() { return tracker_.get(); }
private:
bool RunCommandAndGetBooleanFromFrame(int frame_id,
const std::string& command);
std::unique_ptr<RenderFrameTracker> tracker_;
PermissionRequestCreatorMock* permission_creator_;
......@@ -354,30 +358,20 @@ const GURL& SupervisedUserIframeFilterTest::GetBlockedFrameURL(int frame_id) {
bool SupervisedUserIframeFilterTest::IsInterstitialBeingShownInFrame(
int frame_id) {
// First check that SupervisedUserNavigationObserver believes that there is
// an error page in the frame hosted by |rfh|.
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
auto* navigation_observer =
SupervisedUserNavigationObserver::FromWebContents(tab);
auto& interstitials = navigation_observer->interstitials_for_test();
if (!base::Contains(interstitials, frame_id))
return false;
// Then check that an error page has actually been loaded in the frame.
std::string command =
"domAutomationController.send("
"(document.getElementsByClassName('supervised-user-block') != null) "
"? (true) : (false));";
return RunCommandAndGetBooleanFromFrame(frame_id, command);
}
auto* render_frame_host = tracker()->GetHost(frame_id);
DCHECK(render_frame_host->IsRenderFrameLive());
bool value = false;
auto target = content::ToRenderFrameHost(render_frame_host);
EXPECT_TRUE(content::ExecuteScriptWithoutUserGestureAndExtractBool(
target, command, &value));
return value;
bool SupervisedUserIframeFilterTest::IsAskPermissionButtonBeingShown(
int frame_id) {
std::string command =
"domAutomationController.send("
"(document.getElementById('request-access-button').hidden"
"? (false) : (true)));";
return RunCommandAndGetBooleanFromFrame(frame_id, command);
}
void SupervisedUserIframeFilterTest::RequestPermissionFromFrame(int frame_id) {
......@@ -397,6 +391,29 @@ void SupervisedUserIframeFilterTest::WaitForNavigationFinished(
waiter.Wait();
}
bool SupervisedUserIframeFilterTest::RunCommandAndGetBooleanFromFrame(
int frame_id,
const std::string& command) {
// First check that SupervisedUserNavigationObserver believes that there is
// an error page in the frame with frame tree node id |frame_id|.
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
auto* navigation_observer =
SupervisedUserNavigationObserver::FromWebContents(tab);
auto& interstitials = navigation_observer->interstitials_for_test();
if (!base::Contains(interstitials, frame_id))
return false;
auto* render_frame_host = tracker()->GetHost(frame_id);
DCHECK(render_frame_host->IsRenderFrameLive());
bool value = false;
auto target = content::ToRenderFrameHost(render_frame_host);
EXPECT_TRUE(content::ExecuteScriptWithoutUserGestureAndExtractBool(
target, command, &value));
return value;
}
IN_PROC_BROWSER_TEST_F(SupervisedUserIframeFilterTest, BlockSubFrame) {
BlockHost(kIframeHost2);
GURL allowed_url_with_iframes = embedded_test_server()->GetURL(
......@@ -548,6 +565,64 @@ IN_PROC_BROWSER_TEST_F(SupervisedUserIframeFilterTest,
EXPECT_EQ(kIframeHost1, GetBlockedFrameURL(blocked[0]).host());
}
IN_PROC_BROWSER_TEST_F(SupervisedUserIframeFilterTest,
RememberAlreadyRequestedHosts) {
BlockHost(kExampleHost);
GURL blocked_url = embedded_test_server()->GetURL(
kExampleHost, "/supervised_user/with_frames.html");
ui_test_utils::NavigateToURL(browser(), blocked_url);
EXPECT_TRUE(IsInterstitialBeingShownInMainFrame(browser()));
auto blocked_frames = GetBlockedFrames();
EXPECT_EQ(blocked_frames.size(), 1u);
// Expect that request permission button is shown.
EXPECT_TRUE(IsAskPermissionButtonBeingShown(blocked_frames[0]));
// Delay approval/denial by parent.
permission_creator()->SetPermissionResult(true);
permission_creator()->DelayHandlingForNextRequests();
// Request permission.
RequestPermissionFromFrame(blocked_frames[0]);
// Navigate to another allowed url.
GURL allowed_url = embedded_test_server()->GetURL(
kExampleHost2, "/supervised_user/with_frames.html");
ui_test_utils::NavigateToURL(browser(), allowed_url);
EXPECT_FALSE(IsInterstitialBeingShownInMainFrame(browser()));
ui_test_utils::NavigateToURL(browser(), blocked_url);
EXPECT_TRUE(IsInterstitialBeingShownInMainFrame(browser()));
// Navigate back to the blocked url.
EXPECT_TRUE(IsInterstitialBeingShownInMainFrame(browser()));
// Error page is being shown, but "Ask Permission" button is not being shown.
EXPECT_FALSE(IsAskPermissionButtonBeingShown(blocked_frames[0]));
content::WebContents* active_contents =
browser()->tab_strip_model()->GetActiveWebContents();
SupervisedUserNavigationObserver* navigation_observer =
SupervisedUserNavigationObserver::FromWebContents(active_contents);
ASSERT_NE(navigation_observer, nullptr);
EXPECT_TRUE(base::Contains(navigation_observer->requested_hosts_for_test(),
kExampleHost));
NavigationFinishedWaiter waiter(
active_contents, active_contents->GetMainFrame()->GetFrameTreeNodeId(),
blocked_url);
permission_creator()->HandleDelayedRequests();
waiter.Wait();
EXPECT_FALSE(base::Contains(navigation_observer->requested_hosts_for_test(),
kExampleHost));
EXPECT_FALSE(IsInterstitialBeingShownInMainFrame(browser()));
}
class SupervisedUserNavigationThrottleNotSupervisedTest
: public SupervisedUserNavigationThrottleTest {
protected:
......
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