blob: 33345c0750dfeed6e515f2738410c90665e7558d [file] [log] [blame]
// Copyright 2017 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/chromeos/extensions/public_session_permission_helper.h"
#include <algorithm>
#include <map>
#include <memory>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/lazy_instance.h"
#include "base/macros.h"
#include "chrome/browser/chromeos/extensions/device_local_account_management_policy_provider.h"
#include "chrome/browser/extensions/extension_install_prompt.h"
#include "chrome/browser/profiles/profiles_state.h"
#include "chrome/grit/generated_resources.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/permissions/api_permission_set.h"
#include "extensions/common/permissions/manifest_permission_set.h"
#include "extensions/common/permissions/permission_message.h"
#include "extensions/common/permissions/permission_set.h"
#include "extensions/common/url_pattern_set.h"
#include "ui/base/l10n/l10n_util.h"
namespace extensions {
namespace permission_helper {
namespace {
std::unique_ptr<ExtensionInstallPrompt> CreateExtensionInstallPrompt(
content::WebContents* web_contents) {
return std::make_unique<ExtensionInstallPrompt>(web_contents);
}
bool PermissionCheckNeeded(const Extension* extension) {
return !chromeos::DeviceLocalAccountManagementPolicyProvider::IsWhitelisted(
extension->id());
}
// This class is the internal implementation of HandlePermissionRequest(). It
// contains the actual prompt showing and resolving logic, and it caches the
// user choices.
class PublicSessionPermissionHelper {
public:
PublicSessionPermissionHelper();
PublicSessionPermissionHelper(PublicSessionPermissionHelper&& other);
~PublicSessionPermissionHelper();
bool HandlePermissionRequestImpl(const Extension& extension,
const PermissionIDSet& requested_permissions,
content::WebContents* web_contents,
const RequestResolvedCallback& callback,
const PromptFactory& prompt_factory);
bool PermissionAllowedImpl(const Extension* extension,
APIPermission::ID permission);
private:
void ResolvePermissionPrompt(const ExtensionInstallPrompt* prompt,
const PermissionIDSet& unprompted_permissions,
ExtensionInstallPrompt::Result prompt_result);
PermissionIDSet FilterAllowedPermissions(const PermissionIDSet& permissions);
struct RequestCallback {
RequestCallback(const RequestResolvedCallback& callback,
const PermissionIDSet& permission_list);
RequestCallback(const RequestCallback& other);
~RequestCallback();
RequestResolvedCallback callback;
PermissionIDSet permission_list;
};
using RequestCallbackList = std::vector<RequestCallback>;
std::set<std::unique_ptr<ExtensionInstallPrompt>> prompts_;
PermissionIDSet prompted_permission_set_;
PermissionIDSet allowed_permission_set_;
PermissionIDSet denied_permission_set_;
RequestCallbackList callbacks_;
DISALLOW_COPY_AND_ASSIGN(PublicSessionPermissionHelper);
};
PublicSessionPermissionHelper::PublicSessionPermissionHelper() {}
PublicSessionPermissionHelper::PublicSessionPermissionHelper(
PublicSessionPermissionHelper&& other) = default;
PublicSessionPermissionHelper::~PublicSessionPermissionHelper() {}
bool PublicSessionPermissionHelper::HandlePermissionRequestImpl(
const Extension& extension,
const PermissionIDSet& requested_permissions,
content::WebContents* web_contents,
const RequestResolvedCallback& callback,
const PromptFactory& prompt_factory) {
DCHECK(profiles::IsPublicSession());
if (!PermissionCheckNeeded(&extension)) {
if (!callback.is_null())
callback.Run(requested_permissions);
return true;
}
PermissionIDSet unresolved_permissions = PermissionIDSet::Difference(
requested_permissions, allowed_permission_set_);
unresolved_permissions = PermissionIDSet::Difference(
unresolved_permissions, denied_permission_set_);
if (unresolved_permissions.empty()) {
// All requested permissions are already resolved.
if (!callback.is_null())
callback.Run(FilterAllowedPermissions(requested_permissions));
return true;
}
// Since not all permissions are resolved yet, queue the callback to be called
// when all of them are resolved.
if (!callback.is_null())
callbacks_.push_back(RequestCallback(callback, requested_permissions));
PermissionIDSet unprompted_permissions = PermissionIDSet::Difference(
unresolved_permissions, prompted_permission_set_);
if (unprompted_permissions.empty()) {
// Some permissions aren't resolved yet, but they are currently being
// prompted for, so no need to show a prompt.
return false;
}
// Some permissions need prompting, setup the prompt and show it.
APIPermissionSet new_apis;
for (const auto& permission : unprompted_permissions) {
prompted_permission_set_.insert(permission.id());
new_apis.insert(permission.id());
}
auto permission_set = std::make_unique<PermissionSet>(
new_apis, ManifestPermissionSet(), URLPatternSet(), URLPatternSet());
auto prompt = prompt_factory.Run(web_contents);
auto permissions_prompt = std::make_unique<ExtensionInstallPrompt::Prompt>(
ExtensionInstallPrompt::PERMISSIONS_PROMPT);
// activeTab has no permission message by default, so one is added here.
if (unprompted_permissions.ContainsID(APIPermission::kActiveTab)) {
PermissionMessages messages;
messages.push_back(PermissionMessage(
l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_CURRENT_HOST),
extensions::PermissionIDSet()));
permissions_prompt->AddPermissions(
messages, ExtensionInstallPrompt::REGULAR_PERMISSIONS);
}
// This Unretained is safe because the lifetime of this object is until
// process exit.
prompt->ShowDialog(
base::Bind(&PublicSessionPermissionHelper::ResolvePermissionPrompt,
base::Unretained(this), prompt.get(),
std::move(unprompted_permissions)),
&extension,
nullptr, // Use the extension icon.
std::move(permissions_prompt),
std::move(permission_set),
ExtensionInstallPrompt::GetDefaultShowDialogCallback());
prompts_.insert(std::move(prompt));
return false;
}
bool PublicSessionPermissionHelper::PermissionAllowedImpl(
const Extension* extension,
APIPermission::ID permission) {
DCHECK(profiles::IsPublicSession());
return !PermissionCheckNeeded(extension) ||
allowed_permission_set_.ContainsID(permission);
}
void PublicSessionPermissionHelper::ResolvePermissionPrompt(
const ExtensionInstallPrompt* prompt,
const PermissionIDSet& unprompted_permissions,
ExtensionInstallPrompt::Result prompt_result) {
PermissionIDSet& add_to_set =
prompt_result == ExtensionInstallPrompt::Result::ACCEPTED ?
allowed_permission_set_ : denied_permission_set_;
for (const auto& permission : unprompted_permissions) {
prompted_permission_set_.erase(permission.id());
add_to_set.insert(permission.id());
}
// Here a list of callbacks to be invoked is created first from callbacks_,
// then those callbacks are invoked later because a callback can change
// callbacks_ while we're traversing them.
RequestCallbackList callbacks_to_invoke;
for (auto callback = callbacks_.begin(); callback != callbacks_.end(); ) {
if (prompted_permission_set_.ContainsAnyID(callback->permission_list)) {
// The request is still waiting on other permissions to be resolved - wait
// until all of them are resolved before calling the callback.
callback++;
continue;
}
callbacks_to_invoke.push_back(std::move(*callback));
callback = callbacks_.erase(callback);
}
for (auto callback = callbacks_to_invoke.begin();
callback != callbacks_to_invoke.end(); callback++) {
callback->callback.Run(FilterAllowedPermissions(callback->permission_list));
}
// Dispose of the prompt as it's not needed anymore.
auto iter = std::find_if(
prompts_.begin(), prompts_.end(),
[prompt](const std::unique_ptr<ExtensionInstallPrompt>& check) {
return check.get() == prompt;
});
DCHECK(iter != prompts_.end());
prompts_.erase(iter);
}
PermissionIDSet PublicSessionPermissionHelper::FilterAllowedPermissions(
const PermissionIDSet& permissions) {
PermissionIDSet allowed_permissions;
for (auto iter = permissions.begin(); iter != permissions.end(); iter++) {
if (allowed_permission_set_.ContainsID(*iter)) {
allowed_permissions.insert(iter->id());
}
}
return allowed_permissions;
}
PublicSessionPermissionHelper::RequestCallback::RequestCallback(
const RequestResolvedCallback& callback,
const PermissionIDSet& permission_list)
: callback(callback), permission_list(permission_list) {}
PublicSessionPermissionHelper::RequestCallback::RequestCallback(
const RequestCallback& other) = default;
PublicSessionPermissionHelper::RequestCallback::~RequestCallback() {}
base::LazyInstance<std::map<ExtensionId, PublicSessionPermissionHelper>>::Leaky
g_helpers = LAZY_INSTANCE_INITIALIZER;
} // namespace
bool HandlePermissionRequest(const Extension& extension,
const PermissionIDSet& requested_permissions,
content::WebContents* web_contents,
const RequestResolvedCallback& callback,
const PromptFactory& prompt_factory) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const PromptFactory& factory = prompt_factory.is_null()
? base::Bind(&CreateExtensionInstallPrompt)
: prompt_factory;
return g_helpers.Get()[extension.id()].HandlePermissionRequestImpl(
extension, requested_permissions, web_contents, callback, factory);
}
bool PermissionAllowed(const Extension* extension,
APIPermission::ID permission) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
return g_helpers.Get()[extension->id()].PermissionAllowedImpl(extension,
permission);
}
void ResetPermissionsForTesting() {
// Clear out the std::map between tests. Just setting g_helpers to
// LAZY_INSTANCE_INITIALIZER again causes a memory leak (because of the
// LazyInstance::Leaky).
g_helpers.Get().clear();
}
} // namespace permission_helper
} // namespace extensions