blob: 24ce7b6546a6e04c282c13aeaedb9cb9c9a638c6 [file] [log] [blame]
// Copyright 2021 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/web_applications/file_handlers_permission_helper.h"
#include "base/feature_list.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/permissions/permission_manager_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/manifest_update_task.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_install_finalizer.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_registry_update.h"
#include "chrome/browser/web_applications/web_app_sync_bridge.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "chrome/browser/web_applications/web_application_info.h"
#include "chrome/common/chrome_features.h"
#include "components/permissions/permission_manager.h"
#include "components/permissions/permission_result.h"
#include "url/gurl.h"
namespace web_app {
FileHandlersPermissionHelper::FileHandlersPermissionHelper(
WebAppInstallFinalizer* finalizer)
: finalizer_(finalizer) {
// This catches permission changes occurring when browser is active, from
// permission prompts, site settings, and site settings padlock. When
// permission setting is changed to be blocked/allowed, update app's
// `file_handler_permission_blocked` state and update file handlers on OS.
content_settings_observer_.Observe(
HostContentSettingsMapFactory::GetForProfile(finalizer_->profile()));
app_registrar_observer_.Observe(&finalizer_->registrar());
// This catches permission changes occurring when browser is not active, like
// enterprise policy changes. It detects any file handling permission mismatch
// between the app db state and permission settings, and correct the state in
// db as well as in the OS for all apps.
UpdateAppsMatchingPattern(ContentSettingsPattern::Wildcard());
}
FileHandlersPermissionHelper::~FileHandlersPermissionHelper() = default;
void FileHandlersPermissionHelper::WillInstallApp(
const WebApplicationInfo& web_app_info) {
// This step is necessary in case this app shares an origin with another PWA
// which already asked for file handling permissions, and the new app asks
// to handle more file types.
MaybeResetPermission(web_app_info);
}
FileHandlerUpdateAction FileHandlersPermissionHelper::WillUpdateApp(
const AppId app_id,
const WebApplicationInfo& web_app_info) {
if (!finalizer_->os_integration_manager().IsFileHandlingAPIAvailable(app_id))
return FileHandlerUpdateAction::kNoUpdate;
const GURL& url = web_app_info.scope;
// Keep in sync with chromeos::kChromeUIMediaAppURL.
const char kChromeUIMediaAppURL[] = "chrome://media-app/";
// Keep in sync with chromeos::kChromeUICameraAppURL.
const char kChromeUICameraAppURL[] = "chrome://camera-app/";
// Omit file handler removal and permission downgrade for the ChromeOS Media
// and Camera System Web Apps (SWAs), which have permissions granted by
// default. This exception and check is only relevant in ChromeOS, the only
// platform where SWAs are in use.
if (url == kChromeUIMediaAppURL || url == kChromeUICameraAppURL)
return FileHandlerUpdateAction::kUpdate;
if (base::FeatureList::IsEnabled(
features::kDesktopPWAsFileHandlingSettingsGated)) {
// TODO(estade): reset approval to ask if the file handlers have changed.
} else {
// It's possible we'll downgrade the permission and then fail to update OS
// integrations (ex. if the disk or icon downloads fail), but this is ok
// because these failures should rarely occur.
ContentSetting content_setting = MaybeResetPermission(web_app_info);
// If the permission is "BLOCK", leave it as is. When permission is
// "BLOCK", the `OnContentSettingChanged()` and
// `DetectAndCorrectFileHandlingPermissionBlocks()` should capture the
// permission change and make sure the OS and db state are in sync with the
// PermissionManager permission setting. Therefore, manifest update task
// should not update file handlers due to blocked permission state.
if (content_setting == CONTENT_SETTING_BLOCK)
return FileHandlerUpdateAction::kNoUpdate;
}
// TODO(https://crbug.com/1197013): Consider trying to re-use the comparison
// results from the ManifestUpdateTask.
const apps::FileHandlers* old_handlers =
finalizer_->registrar().GetAppFileHandlers(app_id);
DCHECK(old_handlers);
if (*old_handlers == web_app_info.file_handlers)
return FileHandlerUpdateAction::kNoUpdate;
return FileHandlerUpdateAction::kUpdate;
}
bool FileHandlersPermissionHelper::IsPermissionBlocked(const GURL& scope) {
permissions::PermissionManager* permission_manager =
PermissionManagerFactory::GetForProfile(finalizer_->profile());
permissions::PermissionResult status =
permission_manager->GetPermissionStatus(
ContentSettingsType::FILE_HANDLING, scope, scope);
return status.content_setting == CONTENT_SETTING_BLOCK;
}
void FileHandlersPermissionHelper::OnContentSettingChanged(
const ContentSettingsPattern& primary_pattern,
const ContentSettingsPattern& secondary_pattern,
ContentSettingsType content_type) {
if (content_type != ContentSettingsType::FILE_HANDLING)
return;
UpdateAppsMatchingPattern(primary_pattern);
}
void FileHandlersPermissionHelper::OnWebAppManifestUpdated(
const AppId& app_id,
base::StringPiece old_name) {
ScopedRegistryUpdate update(&finalizer_->sync_bridge());
WebApp* app = update->UpdateApp(app_id);
app->SetFileHandlerPermissionBlocked(IsPermissionBlocked(app->scope()));
}
void FileHandlersPermissionHelper::OnWebAppWillBeUninstalled(
const AppId& app_id) {
// If the uninstalled app had no file handler registrations, do nothing.
const WebAppRegistrar& registrar = finalizer_->GetWebAppRegistrar();
if (registrar.GetAppFileHandlers(app_id)->empty())
return;
// See if there are any remaining apps in the same origin that have file
// handlers. If so, do nothing.
const WebApp* app = registrar.GetAppById(app_id);
const GURL origin = app->scope().GetOrigin();
std::vector<AppId> app_ids = registrar.FindAppsInScope(origin);
for (const AppId& app_id_iter : app_ids) {
const apps::FileHandlers* handlers =
registrar.GetAppFileHandlers(app_id_iter);
if (app_id != app_id_iter && !handlers->empty())
return;
}
// This was the last app in the given origin that had file handlers, so reset
// the permission.
PermissionManagerFactory::GetForProfile(finalizer_->profile())
->ResetPermission(content::PermissionType::FILE_HANDLING, origin, origin);
}
ContentSetting FileHandlersPermissionHelper::MaybeResetPermission(
const WebApplicationInfo& web_app_info) {
permissions::PermissionManager* permission_manager =
PermissionManagerFactory::GetForProfile(finalizer_->profile());
DCHECK(permission_manager);
const GURL& url = web_app_info.scope;
// Note: Since a frame is not available, using GetPermissionStatus() instead
// of GetPermissionStatusForFrame().
permissions::PermissionResult status =
permission_manager->GetPermissionStatus(
ContentSettingsType::FILE_HANDLING, url, url);
// If file handling permission is "ALLOW", downgrade to "ASK" via reset, as
// the user may not want to allow newly added file handlers, which may include
// more dangerous extensions.
if (status.content_setting == CONTENT_SETTING_ALLOW &&
!AreFileHandlersAlreadyRegistered(finalizer_->profile(), url,
web_app_info.file_handlers)) {
permission_manager->ResetPermission(content::PermissionType::FILE_HANDLING,
url, url);
return CONTENT_SETTING_ASK;
}
return status.content_setting;
}
void FileHandlersPermissionHelper::UpdateAppsMatchingPattern(
const ContentSettingsPattern& pattern) {
if (base::FeatureList::IsEnabled(
features::kDesktopPWAsFileHandlingSettingsGated)) {
return;
}
ScopedRegistryUpdate update(&finalizer_->sync_bridge());
for (const AppId& app_id : finalizer_->registrar().GetAppIds()) {
const WebApp* app = finalizer_->GetWebAppRegistrar().GetAppById(app_id);
if (!app || !app->is_locally_installed())
continue;
const GURL url = app->scope();
if (!pattern.Matches(url))
continue;
bool permission_blocked = IsPermissionBlocked(url);
if (permission_blocked == app->file_handler_permission_blocked())
continue;
WebApp* app_to_update = update->UpdateApp(app_id);
app_to_update->SetFileHandlerPermissionBlocked(permission_blocked);
FileHandlerUpdateAction file_handlers_need_os_update =
permission_blocked ? FileHandlerUpdateAction::kRemove
: FileHandlerUpdateAction::kUpdate;
finalizer_->os_integration_manager().UpdateFileHandlers(
app_id, file_handlers_need_os_update, base::DoNothing());
}
}
} // namespace web_app