blob: 6bca2a27902b162e19a7eb9f2b9043f433f71056 [file] [log] [blame]
// Copyright 2015 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/notifications/notification_permission_context.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/rand_util.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/visibility_timer_tab_helper.h"
#include "components/content_settings/browser/page_specific_content_settings.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings_pattern.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/permissions/permission_decision.h"
#include "components/permissions/permission_request_id.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/permission_descriptor_util.h"
#include "content/public/browser/permission_request_description.h"
#include "content/public/browser/permission_result.h"
#include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom.h"
#include "third_party/blink/public/common/permissions/permission_utils.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_ANDROID)
#include "base/android/android_info.h"
#include "chrome/browser/android/flags/chrome_cached_flags.h"
#include "chrome/browser/android/shortcut_helper.h"
#include "chrome/browser/flags/android/chrome_feature_list.h"
#include "chrome/browser/webapps/installable/installed_webapp_bridge.h"
#endif // BUILDFLAG(IS_ANDROID)
#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "chrome/browser/notifications/notifier_state_tracker.h"
#include "chrome/browser/notifications/notifier_state_tracker_factory.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/permissions/api_permission.h"
#include "extensions/common/permissions/permissions_data.h"
#include "ui/message_center/public/cpp/notifier_id.h"
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
// static
void NotificationPermissionContext::UpdatePermission(
content::BrowserContext* browser_context,
const GURL& origin,
ContentSetting setting) {
switch (setting) {
case CONTENT_SETTING_ALLOW:
case CONTENT_SETTING_BLOCK:
case CONTENT_SETTING_DEFAULT:
HostContentSettingsMapFactory::GetForProfile(browser_context)
->SetContentSettingDefaultScope(
origin, GURL(), ContentSettingsType::NOTIFICATIONS, setting);
break;
default:
NOTREACHED();
}
}
NotificationPermissionContext::NotificationPermissionContext(
content::BrowserContext* browser_context)
: ContentSettingPermissionContextBase(
browser_context,
ContentSettingsType::NOTIFICATIONS,
network::mojom::PermissionsPolicyFeature::kNotFound) {}
NotificationPermissionContext::~NotificationPermissionContext() = default;
ContentSetting NotificationPermissionContext::GetContentSettingStatusInternal(
content::RenderFrameHost* render_frame_host,
const GURL& requesting_origin,
const GURL& embedding_origin) const {
#if BUILDFLAG(ENABLE_EXTENSIONS)
// Extensions can declare the "notifications" permission in their manifest
// that also grant permission to use the Web Notification API.
ContentSetting extension_status =
GetPermissionStatusForExtension(requesting_origin);
if (extension_status != CONTENT_SETTING_ASK)
return extension_status;
#endif
ContentSetting setting = permissions::ContentSettingPermissionContextBase::
GetContentSettingStatusInternal(render_frame_host, requesting_origin,
embedding_origin);
content_settings::PageSpecificContentSettings::NotificationsAccessed(
render_frame_host, /*blocked=*/setting != CONTENT_SETTING_ALLOW);
if (requesting_origin != embedding_origin && setting == CONTENT_SETTING_ASK)
return CONTENT_SETTING_BLOCK;
return setting;
}
#if BUILDFLAG(ENABLE_EXTENSIONS)
ContentSetting NotificationPermissionContext::GetPermissionStatusForExtension(
const GURL& origin) const {
constexpr ContentSetting kDefaultSetting = CONTENT_SETTING_ASK;
if (!origin.SchemeIs(extensions::kExtensionScheme))
return kDefaultSetting;
const extensions::Extension* extension =
extensions::ExtensionRegistry::Get(
Profile::FromBrowserContext(browser_context()))
->enabled_extensions()
.GetByID(origin.host());
if (!extension || !extension->permissions_data()->HasAPIPermission(
extensions::mojom::APIPermissionID::kNotifications)) {
// The |extension| doesn't exist, or doesn't have the "notifications"
// permission declared in their manifest
return kDefaultSetting;
}
NotifierStateTracker* notifier_state_tracker =
NotifierStateTrackerFactory::GetForProfile(
Profile::FromBrowserContext(browser_context()));
DCHECK(notifier_state_tracker);
message_center::NotifierId notifier_id(
message_center::NotifierType::APPLICATION, extension->id());
return notifier_state_tracker->IsNotifierEnabled(notifier_id)
? CONTENT_SETTING_ALLOW
: CONTENT_SETTING_BLOCK;
}
#endif
void NotificationPermissionContext::DecidePermission(
std::unique_ptr<permissions::PermissionRequestData> request_data,
permissions::BrowserPermissionCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Permission requests for either Web Notifications and Push Notifications may
// only happen on top-level frames and same-origin iframes. Usage will
// continue to be allowed in all iframes: such frames could trivially work
// around the restriction by posting a message to their Service Worker, where
// showing a notification is allowed.
if (request_data->requesting_origin != request_data->embedding_origin) {
std::move(callback).Run(content::PermissionResult(
blink::mojom::PermissionStatus::DENIED,
content::PermissionStatusSource::UNSPECIFIED));
return;
}
content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
request_data->id.global_render_frame_host_id());
content::WebContents* web_contents =
content::WebContents::FromRenderFrameHost(rfh);
// Notifications permission is always denied in incognito. To prevent sites
// from using that to detect whether incognito mode is active, we deny after a
// random time delay, to simulate a user clicking a bubble/infobar. See also
// ContentSettingsRegistry::Init, which marks notifications as
// INHERIT_IF_LESS_PERMISSIVE, and
// PermissionMenuModel::PermissionMenuModel which prevents users from manually
// allowing the permission.
if (browser_context()->IsOffTheRecord()) {
// Random number of seconds in the range [1.0, 2.0).
double delay_seconds = 1.0 + 1.0 * base::RandDouble();
VisibilityTimerTabHelper::CreateForWebContents(web_contents);
VisibilityTimerTabHelper::FromWebContents(web_contents)
->PostTaskAfterVisibleDelay(
FROM_HERE,
base::BindOnce(
[](base::WeakPtr<NotificationPermissionContext> context,
std::unique_ptr<permissions::PermissionRequestData>
request_data,
permissions::BrowserPermissionCallback callback) {
if (context) {
context->NotifyPermissionSet(
*request_data, std::move(callback),
/*persist=*/true, PermissionDecision::kDeny,
/*is_final_decision=*/true);
}
},
weak_factory_ui_thread_.GetWeakPtr(), std::move(request_data),
std::move(callback)),
base::Seconds(delay_seconds));
return;
}
#if BUILDFLAG(IS_ANDROID)
bool contains_webapk = ShortcutHelper::DoesOriginContainAnyInstalledWebApk(
request_data->requesting_origin);
bool contains_twa =
ShortcutHelper::DoesOriginContainAnyInstalledTrustedWebActivity(
request_data->requesting_origin);
bool contains_installed_webapp = contains_twa || contains_webapk;
if (base::android::android_info::sdk_int() >=
base::android::android_info::SDK_VERSION_T &&
contains_installed_webapp) {
// WebAPKs match URLs using a scope URL which may contain a path. An origin
// has no path and would not fall within such a scope. So to find a matching
// WebAPK we must pass a more complete URL e.g. GetLastCommittedURL.
InstalledWebappBridge::DecidePermission(
ContentSettingsType::NOTIFICATIONS, request_data->requesting_origin,
web_contents->GetLastCommittedURL(),
base::BindOnce(&NotificationPermissionContext::NotifyPermissionSet,
weak_factory_ui_thread_.GetWeakPtr(),
permissions::PermissionRequestData(
this, request_data->id,
content::PermissionRequestDescription(
content::PermissionDescriptorUtil::
CreatePermissionDescriptorForPermissionType(
blink::PermissionType::NOTIFICATIONS)),
request_data->requesting_origin,
request_data->embedding_origin),
std::move(callback),
/*persist=*/false));
return;
}
#endif // BUILDFLAG(IS_ANDROID)
permissions::ContentSettingPermissionContextBase::DecidePermission(
std::move(request_data), std::move(callback));
}
void NotificationPermissionContext::UpdateTabContext(
const permissions::PermissionRequestID& id,
const GURL& requesting_frame,
bool allowed) {
auto* content_settings =
content_settings::PageSpecificContentSettings::GetForFrame(
id.global_render_frame_host_id());
if (!content_settings) {
return;
}
if (allowed) {
content_settings->OnContentAllowed(ContentSettingsType::NOTIFICATIONS);
} else {
content_settings->OnContentBlocked(ContentSettingsType::NOTIFICATIONS);
}
}