blob: 064107bb01905b192c9410dd7e9937a74d64afc1 [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 "content/browser/permissions/permission_service_impl.h"
#include <stddef.h>
#include <memory>
#include <set>
#include <utility>
#include "base/bind.h"
#include "base/memory/ptr_util.h"
#include "build/build_config.h"
#include "content/browser/bad_message.h"
#include "content/browser/permissions/permission_controller_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/permission_type.h"
#include "content/public/browser/render_frame_host.h"
using blink::mojom::PermissionDescriptorPtr;
using blink::mojom::PermissionName;
using blink::mojom::PermissionObserverPtr;
using blink::mojom::PermissionStatus;
namespace content {
namespace {
bool PermissionDescriptorToPermissionType(
const PermissionDescriptorPtr& descriptor,
PermissionType* permission_type) {
switch (descriptor->name) {
case PermissionName::GEOLOCATION:
*permission_type = PermissionType::GEOLOCATION;
return true;
case PermissionName::NOTIFICATIONS:
*permission_type = PermissionType::NOTIFICATIONS;
return true;
case PermissionName::MIDI: {
if (descriptor->extension && descriptor->extension->is_midi() &&
descriptor->extension->get_midi()->sysex) {
*permission_type = PermissionType::MIDI_SYSEX;
return true;
}
*permission_type = PermissionType::MIDI;
return true;
}
case PermissionName::PROTECTED_MEDIA_IDENTIFIER:
#if defined(ENABLE_PROTECTED_MEDIA_IDENTIFIER_PERMISSION)
*permission_type = PermissionType::PROTECTED_MEDIA_IDENTIFIER;
return true;
#else
NOTIMPLEMENTED();
return false;
#endif // defined(ENABLE_PROTECTED_MEDIA_IDENTIFIER_PERMISSION)
case PermissionName::DURABLE_STORAGE:
*permission_type = PermissionType::DURABLE_STORAGE;
return true;
case PermissionName::AUDIO_CAPTURE:
*permission_type = PermissionType::AUDIO_CAPTURE;
return true;
case PermissionName::VIDEO_CAPTURE:
*permission_type = PermissionType::VIDEO_CAPTURE;
return true;
case PermissionName::BACKGROUND_SYNC:
*permission_type = PermissionType::BACKGROUND_SYNC;
return true;
case PermissionName::SENSORS:
*permission_type = PermissionType::SENSORS;
return true;
case PermissionName::ACCESSIBILITY_EVENTS:
*permission_type = PermissionType::ACCESSIBILITY_EVENTS;
return true;
case PermissionName::CLIPBOARD_READ:
*permission_type = PermissionType::CLIPBOARD_READ;
return true;
case PermissionName::CLIPBOARD_WRITE:
*permission_type = PermissionType::CLIPBOARD_WRITE;
return true;
case PermissionName::PAYMENT_HANDLER:
*permission_type = PermissionType::PAYMENT_HANDLER;
return true;
case PermissionName::BACKGROUND_FETCH:
*permission_type = PermissionType::BACKGROUND_FETCH;
return true;
}
NOTREACHED();
return false;
}
// This function allows the usage of the the multiple request map with single
// requests.
void PermissionRequestResponseCallbackWrapper(
base::OnceCallback<void(PermissionStatus)> callback,
const std::vector<PermissionStatus>& vector) {
DCHECK_EQ(vector.size(), 1ul);
std::move(callback).Run(vector[0]);
}
} // anonymous namespace
class PermissionServiceImpl::PendingRequest {
public:
PendingRequest(std::vector<PermissionType> types,
RequestPermissionsCallback callback)
: callback_(std::move(callback)), request_size_(types.size()) {}
~PendingRequest() {
if (callback_.is_null())
return;
std::move(callback_).Run(
std::vector<PermissionStatus>(request_size_, PermissionStatus::DENIED));
}
int id() const { return id_; }
void set_id(int id) { id_ = id; }
void RunCallback(const std::vector<PermissionStatus>& results) {
std::move(callback_).Run(results);
}
private:
// Request ID received from the PermissionController.
int id_;
RequestPermissionsCallback callback_;
size_t request_size_;
};
PermissionServiceImpl::PermissionServiceImpl(PermissionServiceContext* context,
const url::Origin& origin)
: context_(context), origin_(origin), weak_factory_(this) {}
PermissionServiceImpl::~PermissionServiceImpl() {}
void PermissionServiceImpl::RequestPermission(
PermissionDescriptorPtr permission,
bool user_gesture,
PermissionStatusCallback callback) {
std::vector<PermissionDescriptorPtr> permissions;
permissions.push_back(std::move(permission));
RequestPermissions(std::move(permissions), user_gesture,
base::BindOnce(&PermissionRequestResponseCallbackWrapper,
std::move(callback)));
}
void PermissionServiceImpl::RequestPermissions(
std::vector<PermissionDescriptorPtr> permissions,
bool user_gesture,
RequestPermissionsCallback callback) {
// This condition is valid if the call is coming from a ChildThread instead of
// a RenderFrame. Some consumers of the service run in Workers and some in
// Frames. In the context of a Worker, it is not possible to show a
// permission prompt because there is no tab. In the context of a Frame, we
// can. Even if the call comes from a context where it is not possible to show
// any UI, we want to still return something relevant so the current
// permission status is returned for each permission.
BrowserContext* browser_context = context_->GetBrowserContext();
if (!browser_context)
return;
if (!context_->render_frame_host()) {
std::vector<PermissionStatus> result(permissions.size());
for (size_t i = 0; i < permissions.size(); ++i)
result[i] = GetPermissionStatus(permissions[i]);
std::move(callback).Run(result);
return;
}
std::vector<PermissionType> types(permissions.size());
std::set<PermissionType> duplicates_check;
for (size_t i = 0; i < types.size(); ++i) {
if (!PermissionDescriptorToPermissionType(permissions[i], &types[i])) {
ReceivedBadMessage();
return;
}
// Each permission should appear at most once in the message.
bool inserted = duplicates_check.insert(types[i]).second;
if (!inserted) {
ReceivedBadMessage();
return;
}
}
std::unique_ptr<PendingRequest> pending_request =
std::make_unique<PendingRequest>(types, std::move(callback));
int pending_request_id = pending_requests_.Add(std::move(pending_request));
int id =
PermissionControllerImpl::FromBrowserContext(browser_context)
->RequestPermissions(
types, context_->render_frame_host(), origin_.GetURL(),
user_gesture,
base::Bind(&PermissionServiceImpl::OnRequestPermissionsResponse,
weak_factory_.GetWeakPtr(), pending_request_id));
// Check if the request still exists. It may have been removed by the
// the response callback.
PendingRequest* in_progress_request =
pending_requests_.Lookup(pending_request_id);
if (!in_progress_request)
return;
in_progress_request->set_id(id);
}
void PermissionServiceImpl::OnRequestPermissionsResponse(
int pending_request_id,
const std::vector<PermissionStatus>& results) {
PendingRequest* request = pending_requests_.Lookup(pending_request_id);
request->RunCallback(results);
pending_requests_.Remove(pending_request_id);
}
void PermissionServiceImpl::HasPermission(PermissionDescriptorPtr permission,
PermissionStatusCallback callback) {
std::move(callback).Run(GetPermissionStatus(permission));
}
void PermissionServiceImpl::RevokePermission(
PermissionDescriptorPtr permission,
PermissionStatusCallback callback) {
PermissionType permission_type;
if (!PermissionDescriptorToPermissionType(permission, &permission_type)) {
ReceivedBadMessage();
return;
}
PermissionStatus status = GetPermissionStatusFromType(permission_type);
// Resetting the permission should only be possible if the permission is
// already granted.
if (status != PermissionStatus::GRANTED) {
std::move(callback).Run(status);
return;
}
ResetPermissionStatus(permission_type);
std::move(callback).Run(GetPermissionStatusFromType(permission_type));
}
void PermissionServiceImpl::AddPermissionObserver(
PermissionDescriptorPtr permission,
PermissionStatus last_known_status,
PermissionObserverPtr observer) {
PermissionStatus current_status = GetPermissionStatus(permission);
if (current_status != last_known_status) {
observer->OnPermissionStatusChange(current_status);
last_known_status = current_status;
}
PermissionType type;
if (!PermissionDescriptorToPermissionType(permission, &type)) {
ReceivedBadMessage();
return;
}
context_->CreateSubscription(type, origin_, std::move(observer));
}
PermissionStatus PermissionServiceImpl::GetPermissionStatus(
const PermissionDescriptorPtr& permission) {
PermissionType type;
if (!PermissionDescriptorToPermissionType(permission, &type)) {
ReceivedBadMessage();
return PermissionStatus::DENIED;
}
return GetPermissionStatusFromType(type);
}
PermissionStatus PermissionServiceImpl::GetPermissionStatusFromType(
PermissionType type) {
BrowserContext* browser_context = context_->GetBrowserContext();
if (!browser_context)
return PermissionStatus::DENIED;
GURL requesting_origin(origin_.GetURL());
if (context_->render_frame_host()) {
return BrowserContext::GetPermissionController(browser_context)
->GetPermissionStatusForFrame(type, context_->render_frame_host(),
requesting_origin);
}
DCHECK(context_->GetEmbeddingOrigin().is_empty());
return BrowserContext::GetPermissionController(browser_context)
->GetPermissionStatus(type, requesting_origin, requesting_origin);
}
void PermissionServiceImpl::ResetPermissionStatus(PermissionType type) {
BrowserContext* browser_context = context_->GetBrowserContext();
if (!browser_context)
return;
GURL requesting_origin(origin_.GetURL());
// If the embedding_origin is empty we'll use |origin_| instead.
GURL embedding_origin = context_->GetEmbeddingOrigin();
PermissionControllerImpl::FromBrowserContext(browser_context)
->ResetPermission(
type, requesting_origin,
embedding_origin.is_empty() ? requesting_origin : embedding_origin);
}
void PermissionServiceImpl::ReceivedBadMessage() {
if (context_->render_frame_host()) {
bad_message::ReceivedBadMessage(
context_->render_frame_host()->GetProcess(),
bad_message::PERMISSION_SERVICE_BAD_PERMISSION_DESCRIPTOR);
} else {
bad_message::ReceivedBadMessage(
context_->render_process_host(),
bad_message::PERMISSION_SERVICE_BAD_PERMISSION_DESCRIPTOR);
}
}
} // namespace content