blob: 18e2eec8f64ff390c80af30f0555f80e08e9750a [file] [log] [blame]
// 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.
#include "chrome/browser/permissions/chrome_permissions_client.h"
#include <vector>
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/functional/callback_helpers.h"
#include "base/no_destructor.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/bluetooth/bluetooth_chooser_context_factory.h"
#include "chrome/browser/content_settings/cookie_settings_factory.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/engagement/important_sites_util.h"
#include "chrome/browser/metrics/ukm_background_recorder_service.h"
#include "chrome/browser/permissions/adaptive_quiet_notification_permission_ui_enabler.h"
#include "chrome/browser/permissions/contextual_notification_permission_ui_selector.h"
#include "chrome/browser/permissions/origin_keyed_permission_action_service_factory.h"
#include "chrome/browser/permissions/permission_actions_history_factory.h"
#include "chrome/browser/permissions/permission_decision_auto_blocker_factory.h"
#include "chrome/browser/permissions/permission_revocation_request.h"
#include "chrome/browser/permissions/prediction_based_permission_ui_selector.h"
#include "chrome/browser/permissions/pref_notification_permission_ui_selector.h"
#include "chrome/browser/permissions/quiet_notification_permission_ui_config.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profiles_state.h"
#include "chrome/browser/search_engines/ui_thread_search_terms_data.h"
#include "chrome/browser/subresource_filter/subresource_filter_profile_context_factory.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/usb/usb_chooser_context.h"
#include "chrome/browser/usb/usb_chooser_context_factory.h"
#include "chrome/common/channel_info.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "components/content_settings/core/browser/cookie_settings.h"
#include "components/google/core/common/google_util.h"
#include "components/permissions/constants.h"
#include "components/permissions/contexts/bluetooth_chooser_context.h"
#include "components/permissions/features.h"
#include "components/permissions/permission_request.h"
#include "components/permissions/permission_uma_util.h"
#include "components/permissions/permission_util.h"
#include "components/permissions/request_type.h"
#include "components/prefs/pref_service.h"
#include "components/site_engagement/content/site_engagement_service.h"
#include "components/subresource_filter/content/browser/subresource_filter_content_settings_manager.h"
#include "components/subresource_filter/content/browser/subresource_filter_profile_context.h"
#include "components/unified_consent/pref_names.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/web_contents.h"
#include "extensions/buildflags/buildflags.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "url/origin.h"
#if BUILDFLAG(IS_ANDROID)
#include "chrome/browser/android/resource_mapper.h"
#include "chrome/browser/android/search_permissions/search_permissions_service.h"
#include "chrome/browser/permissions/notification_blocked_message_delegate_android.h"
#include "chrome/browser/permissions/permission_infobar_delegate_android.h"
#include "chrome/browser/permissions/permission_update_infobar_delegate_android.h"
#include "chrome/browser/permissions/permission_update_message_controller_android.h"
#include "components/infobars/content/content_infobar_manager.h"
#include "components/messages/android/messages_feature.h"
#include "components/permissions/permission_request_manager.h"
#else
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/hats/hats_service.h"
#include "chrome/browser/ui/hats/hats_service_factory.h"
#include "chrome/browser/ui/permission_bubble/permission_prompt.h"
#include "components/permissions/permission_hats_trigger_helper.h"
#include "components/vector_icons/vector_icons.h"
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chrome/browser/ash/app_mode/web_app/web_kiosk_app_data.h"
#include "chrome/browser/ash/app_mode/web_app/web_kiosk_app_manager.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#endif
#if BUILDFLAG(IS_CHROMEOS_LACROS)
#include "chrome/browser/lacros/app_mode/kiosk_session_service_lacros.h"
#endif
#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "extensions/common/constants.h"
#endif
namespace {
#if BUILDFLAG(IS_ANDROID)
bool ShouldUseQuietUI(content::WebContents* web_contents,
ContentSettingsType type) {
auto* manager =
permissions::PermissionRequestManager::FromWebContents(web_contents);
return type == ContentSettingsType::NOTIFICATIONS &&
manager->ShouldCurrentRequestUseQuietUI();
}
#endif
} // namespace
// static
ChromePermissionsClient* ChromePermissionsClient::GetInstance() {
static base::NoDestructor<ChromePermissionsClient> instance;
return instance.get();
}
HostContentSettingsMap* ChromePermissionsClient::GetSettingsMap(
content::BrowserContext* browser_context) {
return HostContentSettingsMapFactory::GetForProfile(
Profile::FromBrowserContext(browser_context));
}
scoped_refptr<content_settings::CookieSettings>
ChromePermissionsClient::GetCookieSettings(
content::BrowserContext* browser_context) {
return CookieSettingsFactory::GetForProfile(
Profile::FromBrowserContext(browser_context));
}
bool ChromePermissionsClient::IsSubresourceFilterActivated(
content::BrowserContext* browser_context,
const GURL& url) {
return SubresourceFilterProfileContextFactory::GetForProfile(
Profile::FromBrowserContext(browser_context))
->settings_manager()
->GetSiteActivationFromMetadata(url);
}
permissions::ObjectPermissionContextBase*
ChromePermissionsClient::GetChooserContext(
content::BrowserContext* browser_context,
ContentSettingsType type) {
switch (type) {
case ContentSettingsType::USB_CHOOSER_DATA:
return UsbChooserContextFactory::GetForProfile(
Profile::FromBrowserContext(browser_context));
case ContentSettingsType::BLUETOOTH_CHOOSER_DATA:
return BluetoothChooserContextFactory::GetForProfile(
Profile::FromBrowserContext(browser_context));
default:
NOTREACHED();
return nullptr;
}
}
permissions::OriginKeyedPermissionActionService*
ChromePermissionsClient::GetOriginKeyedPermissionActionService(
content::BrowserContext* browser_context) {
return OriginKeyedPermissionActionServiceFactory::GetForProfile(
Profile::FromBrowserContext(browser_context));
}
permissions::PermissionActionsHistory*
ChromePermissionsClient::GetPermissionActionsHistory(
content::BrowserContext* browser_context) {
return PermissionActionsHistoryFactory::GetForProfile(
Profile::FromBrowserContext(browser_context));
}
permissions::PermissionDecisionAutoBlocker*
ChromePermissionsClient::GetPermissionDecisionAutoBlocker(
content::BrowserContext* browser_context) {
return PermissionDecisionAutoBlockerFactory::GetForProfile(
Profile::FromBrowserContext(browser_context));
}
double ChromePermissionsClient::GetSiteEngagementScore(
content::BrowserContext* browser_context,
const GURL& origin) {
return site_engagement::SiteEngagementService::Get(
Profile::FromBrowserContext(browser_context))
->GetScore(origin);
}
void ChromePermissionsClient::AreSitesImportant(
content::BrowserContext* browser_context,
std::vector<std::pair<url::Origin, bool>>* origins) {
// We need to limit our size due to the algorithm in ImportantSiteUtil,
// but we want to be more on the liberal side here as we're not exposing
// these sites to the user, we're just using them for our 'clear
// unimportant' feature in ManageSpaceActivity.java.
const int kMaxImportantSites = 10;
std::vector<site_engagement::ImportantSitesUtil::ImportantDomainInfo>
important_domains =
site_engagement::ImportantSitesUtil::GetImportantRegisterableDomains(
Profile::FromBrowserContext(browser_context), kMaxImportantSites);
for (auto& entry : *origins) {
const url::Origin& origin = entry.first;
const std::string& host = origin.host();
std::string registerable_domain =
net::registry_controlled_domains::GetDomainAndRegistry(
origin,
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
if (registerable_domain.empty())
registerable_domain = host; // IP address or internal hostname.
entry.second = base::Contains(important_domains, registerable_domain,
&site_engagement::ImportantSitesUtil::
ImportantDomainInfo::registerable_domain);
}
}
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_CHROMEOS_ASH)
// Some Google-affiliated domains are not allowed to delete cookies for
// supervised accounts.
bool ChromePermissionsClient::IsCookieDeletionDisabled(
content::BrowserContext* browser_context,
const GURL& origin) {
if (!Profile::FromBrowserContext(browser_context)->IsChild())
return false;
return google_util::IsYoutubeDomainUrl(origin, google_util::ALLOW_SUBDOMAIN,
google_util::ALLOW_NON_STANDARD_PORTS);
}
#endif
void ChromePermissionsClient::GetUkmSourceId(
content::BrowserContext* browser_context,
content::WebContents* web_contents,
const GURL& requesting_origin,
GetUkmSourceIdCallback callback) {
if (web_contents) {
ukm::SourceId source_id =
web_contents->GetPrimaryMainFrame()->GetPageUkmSourceId();
std::move(callback).Run(source_id);
} else {
// We only record a permission change if the origin is in the user's
// history.
ukm::UkmBackgroundRecorderFactory::GetForProfile(
Profile::FromBrowserContext(browser_context))
->GetBackgroundSourceIdIfAllowed(url::Origin::Create(requesting_origin),
std::move(callback));
}
}
permissions::IconId ChromePermissionsClient::GetOverrideIconId(
permissions::RequestType request_type) {
#if BUILDFLAG(IS_CHROMEOS)
// TODO(xhwang): fix this icon, see crbug.com/446263.
if (request_type == permissions::RequestType::kProtectedMediaIdentifier)
return vector_icons::kProductIcon;
#endif
return PermissionsClient::GetOverrideIconId(request_type);
}
#if !BUILDFLAG(IS_ANDROID)
// Triggers the prompt HaTS survey if enabled by field trials for this
// combination of prompt parameters.
void ChromePermissionsClient::TriggerPromptHatsSurveyIfEnabled(
content::BrowserContext* context,
permissions::RequestType request_type,
absl::optional<permissions::PermissionAction> action,
permissions::PermissionPromptDisposition prompt_disposition,
permissions::PermissionPromptDispositionReason prompt_disposition_reason,
permissions::PermissionRequestGestureType gesture_type,
absl::optional<base::TimeDelta> prompt_display_duration,
bool is_post_prompt,
const GURL& gurl,
base::OnceCallback<void()> hats_shown_callback) {
Profile* profile = Profile::FromBrowserContext(context);
absl::optional<GURL> recorded_gurl =
profile->GetPrefs()->GetBoolean(
unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled)
? absl::make_optional(gurl)
: absl::nullopt;
auto prompt_parameters =
permissions::PermissionHatsTriggerHelper::PromptParametersForHaTS(
request_type, action, prompt_disposition, prompt_disposition_reason,
gesture_type,
std::string(version_info::GetChannelString(chrome::GetChannel())),
is_post_prompt ? permissions::kOnPromptResolved
: permissions::kOnPromptAppearing,
prompt_display_duration,
permissions::PermissionHatsTriggerHelper::
GetOneTimePromptsDecidedBucket(profile->GetPrefs()),
recorded_gurl);
if (!permissions::PermissionHatsTriggerHelper::
ArePromptTriggerCriteriaSatisfied(
prompt_parameters, kHatsSurveyTriggerPermissionsPrompt)) {
return;
}
auto trigger_and_probability = permissions::PermissionHatsTriggerHelper::
GetPermissionPromptTriggerNameAndProbabilityForRequestType(
kHatsSurveyTriggerPermissionsPrompt,
permissions::PermissionUmaUtil::GetRequestTypeString(request_type));
auto* hats_service =
HatsServiceFactory::GetForProfile(profile,
/*create_if_necessary=*/true);
if (!hats_service || !trigger_and_probability.has_value()) {
return;
}
auto survey_data = permissions::PermissionHatsTriggerHelper::
SurveyProductSpecificData::PopulateFrom(prompt_parameters);
hats_service->LaunchSurvey(trigger_and_probability->first,
std::move(hats_shown_callback), base::DoNothing(),
survey_data.survey_bits_data,
survey_data.survey_string_data);
}
permissions::PermissionIgnoredReason
ChromePermissionsClient::DetermineIgnoreReason(
content::WebContents* web_contents) {
Profile* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
Browser* browser = chrome::FindLastActiveWithProfile(profile);
if (browser) {
if (browser->tab_strip_model()->empty()) {
return permissions::PermissionIgnoredReason::WINDOW_CLOSED;
} else if (web_contents->IsBeingDestroyed()) {
return permissions::PermissionIgnoredReason::TAB_CLOSED;
} else {
return permissions::PermissionIgnoredReason::NAVIGATION;
}
}
return permissions::PermissionIgnoredReason::UNKNOWN;
}
#endif
std::vector<std::unique_ptr<permissions::PermissionUiSelector>>
ChromePermissionsClient::CreatePermissionUiSelectors(
content::BrowserContext* browser_context) {
std::vector<std::unique_ptr<permissions::PermissionUiSelector>> selectors;
selectors.emplace_back(
std::make_unique<ContextualNotificationPermissionUiSelector>());
selectors.emplace_back(std::make_unique<PrefNotificationPermissionUiSelector>(
Profile::FromBrowserContext(browser_context)));
selectors.emplace_back(std::make_unique<PredictionBasedPermissionUiSelector>(
Profile::FromBrowserContext(browser_context)));
return selectors;
}
void ChromePermissionsClient::OnPromptResolved(
permissions::RequestType request_type,
permissions::PermissionAction action,
const GURL& origin,
permissions::PermissionPromptDisposition prompt_disposition,
permissions::PermissionPromptDispositionReason prompt_disposition_reason,
permissions::PermissionRequestGestureType gesture_type,
absl::optional<QuietUiReason> quiet_ui_reason,
base::TimeDelta prompt_display_duration,
content::WebContents* web_contents) {
Profile* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
PermissionActionsHistoryFactory::GetForProfile(profile)->RecordAction(
action, request_type, prompt_disposition);
if (request_type == permissions::RequestType::kNotifications) {
AdaptiveQuietNotificationPermissionUiEnabler::GetForProfile(profile)
->PermissionPromptResolved();
if (action == permissions::PermissionAction::GRANTED &&
quiet_ui_reason.has_value() &&
(quiet_ui_reason.value() ==
QuietUiReason::kTriggeredDueToAbusiveRequests ||
quiet_ui_reason.value() ==
QuietUiReason::kTriggeredDueToAbusiveContent ||
quiet_ui_reason.value() ==
QuietUiReason::kTriggeredDueToDisruptiveBehavior)) {
PermissionRevocationRequest::ExemptOriginFromFutureRevocations(profile,
origin);
}
}
#if !BUILDFLAG(IS_ANDROID)
auto content_setting_type = RequestTypeToContentSettingsType(request_type);
if (content_setting_type.has_value()) {
permissions::PermissionHatsTriggerHelper::
IncrementOneTimePermissionPromptsDecidedIfApplicable(
content_setting_type.value(), profile->GetPrefs());
}
TriggerPromptHatsSurveyIfEnabled(
web_contents->GetBrowserContext(), request_type,
absl::make_optional(action), prompt_disposition,
prompt_disposition_reason, gesture_type,
absl::make_optional(prompt_display_duration), true,
web_contents->GetLastCommittedURL(), base::DoNothing());
#endif // !BUILDFLAG(IS_ANDROID)
}
absl::optional<bool>
ChromePermissionsClient::HadThreeConsecutiveNotificationPermissionDenies(
content::BrowserContext* browser_context) {
if (!QuietNotificationPermissionUiConfig::
IsAdaptiveActivationDryRunEnabled()) {
return absl::nullopt;
}
return Profile::FromBrowserContext(browser_context)
->GetPrefs()
->GetBoolean(prefs::kHadThreeConsecutiveNotificationPermissionDenies);
}
absl::optional<bool>
ChromePermissionsClient::HasPreviouslyAutoRevokedPermission(
content::BrowserContext* browser_context,
const GURL& origin,
ContentSettingsType permission) {
if (permission != ContentSettingsType::NOTIFICATIONS) {
return absl::nullopt;
}
Profile* profile = Profile::FromBrowserContext(browser_context);
return PermissionRevocationRequest::HasPreviouslyRevokedPermission(profile,
origin);
}
absl::optional<url::Origin> ChromePermissionsClient::GetAutoApprovalOrigin() {
// In web kiosk mode, all permission requests are auto-approved for the origin
// of the main app.
#if BUILDFLAG(IS_CHROMEOS_ASH)
if (user_manager::UserManager::IsInitialized() &&
user_manager::UserManager::Get()->IsLoggedInAsWebKioskApp()) {
const AccountId& account_id =
user_manager::UserManager::Get()->GetPrimaryUser()->GetAccountId();
DCHECK(ash::WebKioskAppManager::IsInitialized());
const ash::WebKioskAppData* app_data =
ash::WebKioskAppManager::Get()->GetAppByAccountId(account_id);
DCHECK(app_data);
return url::Origin::Create(app_data->install_url());
}
#elif BUILDFLAG(IS_CHROMEOS_LACROS)
if (profiles::IsWebKioskSession()) {
return url::Origin::Create(
KioskSessionServiceLacros::Get()->GetInstallURL());
}
#endif
return absl::nullopt;
}
bool ChromePermissionsClient::CanBypassEmbeddingOriginCheck(
const GURL& requesting_origin,
const GURL& embedding_origin) {
#if BUILDFLAG(ENABLE_EXTENSIONS)
// Extensions are excluded from origin checks as currently they can request
// permission from iframes when embedded in non-secure contexts
// (https://crbug.com/530507).
if (requesting_origin.SchemeIs(extensions::kExtensionScheme))
return true;
#endif
// The New Tab Page is excluded from origin checks as its effective
// requesting origin may be the Default Search Engine origin.
return embedding_origin ==
GURL(chrome::kChromeUINewTabURL).DeprecatedGetOriginAsURL() ||
embedding_origin ==
GURL(chrome::kChromeUINewTabPageURL).DeprecatedGetOriginAsURL();
}
absl::optional<GURL> ChromePermissionsClient::OverrideCanonicalOrigin(
const GURL& requesting_origin,
const GURL& embedding_origin) {
if (embedding_origin.DeprecatedGetOriginAsURL() ==
GURL(chrome::kChromeUINewTabURL).DeprecatedGetOriginAsURL()) {
if (requesting_origin.DeprecatedGetOriginAsURL() ==
GURL(chrome::kChromeUINewTabPageURL).DeprecatedGetOriginAsURL()) {
return GURL(UIThreadSearchTermsData().GoogleBaseURLValue())
.DeprecatedGetOriginAsURL();
}
return requesting_origin;
}
#if BUILDFLAG(ENABLE_EXTENSIONS)
// Note that currently chrome extensions are allowed to use permissions even
// when in embedded in non-secure contexts. This is unfortunate and we
// should remove this at some point, but for now always use the requesting
// origin for embedded extensions. https://crbug.com/530507.
if (requesting_origin.SchemeIs(extensions::kExtensionScheme)) {
return requesting_origin;
}
#endif
return absl::nullopt;
}
bool ChromePermissionsClient::DoURLsMatchNewTabPage(
const GURL& requesting_origin,
const GURL& embedding_origin) {
return embedding_origin ==
GURL(chrome::kChromeUINewTabURL).DeprecatedGetOriginAsURL() &&
requesting_origin ==
GURL(chrome::kChromeUINewTabPageURL).DeprecatedGetOriginAsURL();
}
#if BUILDFLAG(IS_ANDROID)
bool ChromePermissionsClient::IsDseOrigin(
content::BrowserContext* browser_context,
const url::Origin& origin) {
SearchPermissionsService* search_helper =
SearchPermissionsService::Factory::GetForBrowserContext(browser_context);
return search_helper && search_helper->IsDseOrigin(origin);
}
infobars::InfoBarManager* ChromePermissionsClient::GetInfoBarManager(
content::WebContents* web_contents) {
return infobars::ContentInfoBarManager::FromWebContents(web_contents);
}
infobars::InfoBar* ChromePermissionsClient::MaybeCreateInfoBar(
content::WebContents* web_contents,
ContentSettingsType type,
base::WeakPtr<permissions::PermissionPromptAndroid> prompt) {
infobars::ContentInfoBarManager* infobar_manager =
infobars::ContentInfoBarManager::FromWebContents(web_contents);
if (infobar_manager && ShouldUseQuietUI(web_contents, type)) {
return PermissionInfoBarDelegate::Create(std::move(prompt),
infobar_manager);
}
return nullptr;
}
std::unique_ptr<ChromePermissionsClient::PermissionMessageDelegate>
ChromePermissionsClient::MaybeCreateMessageUI(
content::WebContents* web_contents,
ContentSettingsType type,
base::WeakPtr<permissions::PermissionPromptAndroid> prompt) {
if (ShouldUseQuietUI(web_contents, type)) {
auto delegate =
std::make_unique<NotificationBlockedMessageDelegate::Delegate>(
std::move(prompt));
return std::make_unique<NotificationBlockedMessageDelegate>(
web_contents, std::move(delegate));
}
return {};
}
void ChromePermissionsClient::RepromptForAndroidPermissions(
content::WebContents* web_contents,
const std::vector<ContentSettingsType>& content_settings_types,
const std::vector<ContentSettingsType>& filtered_content_settings_types,
const std::vector<std::string>& required_permissions,
const std::vector<std::string>& optional_permissions,
PermissionsUpdatedCallback callback) {
if (messages::IsPermissionUpdateMessagesUiEnabled()) {
PermissionUpdateMessageController::CreateForWebContents(web_contents);
PermissionUpdateMessageController::FromWebContents(web_contents)
->ShowMessage(content_settings_types, filtered_content_settings_types,
required_permissions, optional_permissions,
std::move(callback));
} else {
PermissionUpdateInfoBarDelegate::Create(
web_contents, content_settings_types, filtered_content_settings_types,
required_permissions, optional_permissions, std::move(callback));
}
}
int ChromePermissionsClient::MapToJavaDrawableId(int resource_id) {
return ResourceMapper::MapToJavaDrawableId(resource_id);
}
#else
std::unique_ptr<permissions::PermissionPrompt>
ChromePermissionsClient::CreatePrompt(
content::WebContents* web_contents,
permissions::PermissionPrompt::Delegate* delegate) {
return CreatePermissionPrompt(web_contents, delegate);
}
#endif