blob: f0311183729ba4f23f41c67c22fc01f56d1aebec [file] [log] [blame]
// Copyright 2014 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/supervised_user/supervised_user_navigation_throttle.h"
#include "base/bind.h"
#include "base/feature_list.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/supervised_user/supervised_user_interstitial.h"
#include "chrome/browser/supervised_user/supervised_user_navigation_observer.h"
#include "chrome/browser/supervised_user/supervised_user_service.h"
#include "chrome/browser/supervised_user/supervised_user_service_factory.h"
#include "chrome/browser/supervised_user/supervised_user_url_filter.h"
#include "chrome/common/chrome_features.h"
#include "content/public/browser/navigation_handle.h"
#include "ui/base/page_transition_types.h"
#include "url/gurl.h"
namespace {
// These values corresponds to SupervisedUserSafetyFilterResult in
// tools/metrics/histograms/histograms.xml. If you change anything here, make
// sure to also update histograms.xml accordingly.
enum {
FILTERING_BEHAVIOR_ALLOW = 1,
FILTERING_BEHAVIOR_ALLOW_UNCERTAIN,
FILTERING_BEHAVIOR_BLOCK_BLACKLIST,
FILTERING_BEHAVIOR_BLOCK_SAFESITES,
FILTERING_BEHAVIOR_BLOCK_MANUAL,
FILTERING_BEHAVIOR_BLOCK_DEFAULT,
FILTERING_BEHAVIOR_ALLOW_WHITELIST,
FILTERING_BEHAVIOR_MAX = FILTERING_BEHAVIOR_ALLOW_WHITELIST
};
const int kHistogramFilteringBehaviorSpacing = 100;
const int kHistogramPageTransitionMaxKnownValue =
static_cast<int>(ui::PAGE_TRANSITION_KEYWORD_GENERATED);
const int kHistogramPageTransitionFallbackValue =
kHistogramFilteringBehaviorSpacing - 1;
const int kHistogramMax = 800;
static_assert(kHistogramPageTransitionMaxKnownValue <
kHistogramPageTransitionFallbackValue,
"HistogramPageTransition MaxKnownValue must be < FallbackValue");
static_assert(FILTERING_BEHAVIOR_MAX * kHistogramFilteringBehaviorSpacing +
kHistogramPageTransitionFallbackValue <
kHistogramMax,
"Invalid HistogramMax value");
int GetHistogramValueForFilteringBehavior(
SupervisedUserURLFilter::FilteringBehavior behavior,
supervised_user_error_page::FilteringBehaviorReason reason,
bool uncertain) {
switch (behavior) {
case SupervisedUserURLFilter::ALLOW:
case SupervisedUserURLFilter::WARN:
if (reason == supervised_user_error_page::WHITELIST)
return FILTERING_BEHAVIOR_ALLOW_WHITELIST;
return uncertain ? FILTERING_BEHAVIOR_ALLOW_UNCERTAIN
: FILTERING_BEHAVIOR_ALLOW;
case SupervisedUserURLFilter::BLOCK:
switch (reason) {
case supervised_user_error_page::BLACKLIST:
return FILTERING_BEHAVIOR_BLOCK_BLACKLIST;
case supervised_user_error_page::ASYNC_CHECKER:
return FILTERING_BEHAVIOR_BLOCK_SAFESITES;
case supervised_user_error_page::WHITELIST:
NOTREACHED();
break;
case supervised_user_error_page::MANUAL:
return FILTERING_BEHAVIOR_BLOCK_MANUAL;
case supervised_user_error_page::DEFAULT:
return FILTERING_BEHAVIOR_BLOCK_DEFAULT;
case supervised_user_error_page::NOT_SIGNED_IN:
// Should never happen, only used for requests from Webview
NOTREACHED();
}
FALLTHROUGH;
case SupervisedUserURLFilter::INVALID:
NOTREACHED();
}
return 0;
}
int GetHistogramValueForTransitionType(ui::PageTransition transition_type) {
int value =
static_cast<int>(ui::PageTransitionStripQualifier(transition_type));
if (0 <= value && value <= kHistogramPageTransitionMaxKnownValue)
return value;
NOTREACHED();
return kHistogramPageTransitionFallbackValue;
}
void RecordFilterResultEvent(
bool safesites_histogram,
SupervisedUserURLFilter::FilteringBehavior behavior,
supervised_user_error_page::FilteringBehaviorReason reason,
bool uncertain,
ui::PageTransition transition_type) {
int value =
GetHistogramValueForFilteringBehavior(behavior, reason, uncertain) *
kHistogramFilteringBehaviorSpacing +
GetHistogramValueForTransitionType(transition_type);
DCHECK_LT(value, kHistogramMax);
// Note: We can't pass in the histogram name as a parameter to this function
// because of how the macro works (look up the histogram on the first
// invocation and cache it in a static variable).
if (safesites_histogram)
base::UmaHistogramSparse("ManagedUsers.SafetyFilter", value);
else
base::UmaHistogramSparse("ManagedUsers.FilteringResult", value);
}
} // namespace
// static
std::unique_ptr<SupervisedUserNavigationThrottle>
SupervisedUserNavigationThrottle::MaybeCreateThrottleFor(
content::NavigationHandle* navigation_handle) {
if (!navigation_handle->IsInMainFrame())
return nullptr;
Profile* profile = Profile::FromBrowserContext(
navigation_handle->GetWebContents()->GetBrowserContext());
if (!profile->IsSupervised())
return nullptr;
// Can't use std::make_unique because the constructor is private.
return base::WrapUnique(
new SupervisedUserNavigationThrottle(navigation_handle));
}
SupervisedUserNavigationThrottle::SupervisedUserNavigationThrottle(
content::NavigationHandle* navigation_handle)
: NavigationThrottle(navigation_handle),
url_filter_(
SupervisedUserServiceFactory::GetForProfile(
Profile::FromBrowserContext(
navigation_handle->GetWebContents()->GetBrowserContext()))
->GetURLFilter()),
deferred_(false),
behavior_(SupervisedUserURLFilter::INVALID),
weak_ptr_factory_(this) {}
SupervisedUserNavigationThrottle::~SupervisedUserNavigationThrottle() {}
content::NavigationThrottle::ThrottleCheckResult
SupervisedUserNavigationThrottle::CheckURL() {
deferred_ = false;
DCHECK_EQ(SupervisedUserURLFilter::INVALID, behavior_);
GURL url = navigation_handle()->GetURL();
bool got_result = url_filter_->GetFilteringBehaviorForURLWithAsyncChecks(
url, base::Bind(&SupervisedUserNavigationThrottle::OnCheckDone,
weak_ptr_factory_.GetWeakPtr(), url));
DCHECK_EQ(got_result, behavior_ != SupervisedUserURLFilter::INVALID);
// If we got a "not blocked" result synchronously, don't defer.
deferred_ = !got_result || (behavior_ == SupervisedUserURLFilter::BLOCK);
if (got_result)
behavior_ = SupervisedUserURLFilter::INVALID;
if (deferred_)
return NavigationThrottle::DEFER;
return NavigationThrottle::PROCEED;
}
void SupervisedUserNavigationThrottle::ShowInterstitial(
const GURL& url,
supervised_user_error_page::FilteringBehaviorReason reason) {
// Don't show interstitial synchronously - it doesn't seem like a good idea to
// show an interstitial right in the middle of a call into a
// NavigationThrottle. This also lets OnInterstitialResult to be invoked
// synchronously, once a callback is passed into the
// SupervisedUserNavigationObserver.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::Bind(&SupervisedUserNavigationThrottle::ShowInterstitialAsync,
weak_ptr_factory_.GetWeakPtr(), reason));
}
void SupervisedUserNavigationThrottle::ShowInterstitialAsync(
supervised_user_error_page::FilteringBehaviorReason reason) {
// May not yet have been set when ShowInterstitial was called, but should have
// been set by the time this is invoked.
DCHECK(deferred_);
SupervisedUserNavigationObserver::OnRequestBlocked(
navigation_handle()->GetWebContents(), navigation_handle()->GetURL(),
reason, navigation_handle()->GetNavigationId(),
base::Bind(&SupervisedUserNavigationThrottle::OnInterstitialResult,
weak_ptr_factory_.GetWeakPtr()));
}
content::NavigationThrottle::ThrottleCheckResult
SupervisedUserNavigationThrottle::WillStartRequest() {
return CheckURL();
}
content::NavigationThrottle::ThrottleCheckResult
SupervisedUserNavigationThrottle::WillRedirectRequest() {
return CheckURL();
}
const char* SupervisedUserNavigationThrottle::GetNameForLogging() {
return "SupervisedUserNavigationThrottle";
}
void SupervisedUserNavigationThrottle::OnCheckDone(
const GURL& url,
SupervisedUserURLFilter::FilteringBehavior behavior,
supervised_user_error_page::FilteringBehaviorReason reason,
bool uncertain) {
DCHECK_EQ(SupervisedUserURLFilter::INVALID, behavior_);
// If we got a result synchronously, pass it back to ShowInterstitialIfNeeded.
if (!deferred_)
behavior_ = behavior;
reason_ = reason;
ui::PageTransition transition = navigation_handle()->GetPageTransition();
RecordFilterResultEvent(false, behavior, reason, uncertain, transition);
// If both the static blacklist and the async checker are enabled, also record
// SafeSites-only UMA events.
if (url_filter_->HasBlacklist() && url_filter_->HasAsyncURLChecker() &&
(reason == supervised_user_error_page::ASYNC_CHECKER ||
reason == supervised_user_error_page::BLACKLIST)) {
RecordFilterResultEvent(true, behavior, reason, uncertain, transition);
}
if (behavior == SupervisedUserURLFilter::BLOCK)
ShowInterstitial(url, reason);
else if (deferred_)
Resume();
}
void SupervisedUserNavigationThrottle::OnInterstitialResult(
CallbackActions action) {
switch (action) {
case kContinueNavigation: {
Resume();
break;
}
case kCancelNavigation: {
CancelDeferredNavigation(CANCEL);
break;
}
case kCancelWithInterstitial: {
DCHECK(base::FeatureList::IsEnabled(
features::kSupervisedUserCommittedInterstitials));
std::string interstitial_html =
SupervisedUserInterstitial::GetHTMLContents(
Profile::FromBrowserContext(
navigation_handle()->GetWebContents()->GetBrowserContext()),
reason_);
// If committed interstitials are enabled, include the HTML content in the
// ThrottleCheckResult.
CancelDeferredNavigation(content::NavigationThrottle::ThrottleCheckResult(
CANCEL, net::ERR_BLOCKED_BY_CLIENT, interstitial_html));
}
}
}