blob: 0b6e2cfcf0fc4ebdc575ee3fd3e73eba84a0e747 [file] [log] [blame]
// Copyright (c) 2012 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/extensions/api/permissions/permissions_api_helpers.h"
#include <stddef.h>
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/values.h"
#include "chrome/common/extensions/api/permissions.h"
#include "content/public/common/url_constants.h"
#include "extensions/common/error_utils.h"
#include "extensions/common/extension.h"
#include "extensions/common/permissions/permission_set.h"
#include "extensions/common/permissions/permissions_info.h"
#include "extensions/common/permissions/usb_device_permission.h"
#include "extensions/common/url_pattern_set.h"
namespace extensions {
using api::permissions::Permissions;
namespace permissions_api_helpers {
namespace {
const char kDelimiter[] = "|";
const char kInvalidParameter[] =
"Invalid argument for permission '*'.";
const char kInvalidOrigin[] =
"Invalid value for origin pattern *: *";
const char kUnknownPermissionError[] =
"'*' is not a recognized permission.";
const char kUnsupportedPermissionId[] =
"Only the usbDevices permission supports arguments.";
// Extracts an API permission that supports arguments. In practice, this is
// restricted to the UsbDevicePermission.
std::unique_ptr<APIPermission> UnpackPermissionWithArguments(
base::StringPiece permission_name,
base::StringPiece permission_arg,
const std::string& permission_str,
std::string* error) {
std::unique_ptr<base::Value> permission_json =
base::JSONReader::ReadDeprecated(permission_arg);
if (!permission_json.get()) {
*error = ErrorUtils::FormatErrorMessage(kInvalidParameter, permission_str);
return nullptr;
}
std::unique_ptr<APIPermission> permission;
// Explicitly check the permissions that accept arguments until
// https://crbug.com/162042 is fixed.
const APIPermissionInfo* usb_device_permission_info =
PermissionsInfo::GetInstance()->GetByID(APIPermission::kUsbDevice);
if (permission_name == usb_device_permission_info->name()) {
permission =
std::make_unique<UsbDevicePermission>(usb_device_permission_info);
} else {
*error = kUnsupportedPermissionId;
return nullptr;
}
CHECK(permission);
if (!permission->FromValue(permission_json.get(), nullptr, nullptr)) {
*error = ErrorUtils::FormatErrorMessage(kInvalidParameter, permission_str);
return nullptr;
}
return permission;
}
// A helper method to unpack API permissions from the list in
// |permissions_input|, and populate the appropriate fields of |result|.
// Returns true on success; on failure, returns false and populates |error|.
bool UnpackAPIPermissions(const std::vector<std::string>& permissions_input,
const PermissionSet& required_permissions,
const PermissionSet& optional_permissions,
UnpackPermissionSetResult* result,
std::string* error) {
PermissionsInfo* info = PermissionsInfo::GetInstance();
APIPermissionSet apis;
// Iterate over the inputs and find the corresponding API permissions.
for (const auto& permission_str : permissions_input) {
// This is a compromise: we currently can't switch to a blend of
// objects/strings all the way through the API. Until then, put this
// processing here.
// http://code.google.com/p/chromium/issues/detail?id=162042
size_t delimiter = permission_str.find(kDelimiter);
if (delimiter != std::string::npos) {
base::StringPiece permission_piece(permission_str);
std::unique_ptr<APIPermission> permission = UnpackPermissionWithArguments(
permission_piece.substr(0, delimiter),
permission_piece.substr(delimiter + 1), permission_str, error);
if (!permission)
return false;
apis.insert(std::move(permission));
} else {
const APIPermissionInfo* permission_info =
info->GetByName(permission_str);
if (!permission_info) {
*error = ErrorUtils::FormatErrorMessage(kUnknownPermissionError,
permission_str);
return false;
}
apis.insert(permission_info->id());
}
}
// Validate and partition the parsed APIs.
for (const auto* api_permission : apis) {
if (required_permissions.apis().count(api_permission->id())) {
result->required_apis.insert(api_permission->Clone());
continue;
}
if (!optional_permissions.apis().count(api_permission->id())) {
result->unlisted_apis.insert(api_permission->Clone());
continue;
}
if (!api_permission->info()->supports_optional()) {
result->unsupported_optional_apis.insert(api_permission->Clone());
continue;
}
result->optional_apis.insert(api_permission->Clone());
}
return true;
}
// A helper method to unpack host permissions from the list in
// |permissions_input|, and populate the appropriate fields of |result|.
// Returns true on success; on failure, returns false and populates |error|.
bool UnpackOriginPermissions(const std::vector<std::string>& origins_input,
const PermissionSet& required_permissions,
const PermissionSet& optional_permissions,
bool allow_file_access,
UnpackPermissionSetResult* result,
std::string* error) {
int user_script_schemes = UserScript::ValidUserScriptSchemes();
int explicit_schemes = Extension::kValidHostPermissionSchemes;
auto filter_schemes = [allow_file_access](URLPattern* pattern) {
// NOTE: We use pattern->valid_schemes() here (instead of
// |user_script_schemes| or |explicit_schemes|) because
// URLPattern::Parse() can mutate the valid schemes for a pattern, and we
// don't want to override those changes.
int valid_schemes = pattern->valid_schemes();
// We disallow the chrome:-scheme unless the pattern is explicitly
// "chrome://..." - that is, <all_urls> should not match the chrome:-scheme.
// Patterns which explicitly specify the chrome:-scheme are safe, since
// manifest parsing won't allow them unless the kExtensionsOnChromeURLs
// switch is enabled.
// Note that we don't check PermissionsData::AllUrlsIncludesChromeUrls()
// here, since that's only needed for Chromevox (which doesn't use optional
// permissions).
if (pattern->scheme() != content::kChromeUIScheme)
valid_schemes &= ~URLPattern::SCHEME_CHROMEUI;
// Similarly, <all_urls> should only match file:-scheme URLs if file access
// is granted.
if (!allow_file_access && pattern->scheme() != url::kFileScheme)
valid_schemes &= ~URLPattern::SCHEME_FILE;
if (valid_schemes != pattern->valid_schemes())
pattern->SetValidSchemes(valid_schemes);
};
for (const auto& origin_str : origins_input) {
URLPattern explicit_origin(explicit_schemes);
URLPattern::ParseResult parse_result = explicit_origin.Parse(origin_str);
if (URLPattern::ParseResult::kSuccess != parse_result) {
*error = ErrorUtils::FormatErrorMessage(
kInvalidOrigin, origin_str,
URLPattern::GetParseResultString(parse_result));
return false;
}
filter_schemes(&explicit_origin);
if ((explicit_origin.valid_schemes() & URLPattern::SCHEME_FILE) &&
!allow_file_access) {
// This should only happen with patterns that specify file schemes;
// otherwise they should have been filtered out in filter_schemes().
DCHECK_EQ(url::kFileScheme, explicit_origin.scheme());
result->restricted_file_scheme_patterns.AddPattern(explicit_origin);
// Don't add the pattern to any other set to indicate that it can't be
// requested/granted/contained.
continue;
}
bool used_origin = false;
if (required_permissions.explicit_hosts().ContainsPattern(
explicit_origin)) {
used_origin = true;
result->required_explicit_hosts.AddPattern(explicit_origin);
} else if (optional_permissions.explicit_hosts().ContainsPattern(
explicit_origin)) {
used_origin = true;
result->optional_explicit_hosts.AddPattern(explicit_origin);
}
URLPattern scriptable_origin(user_script_schemes);
if (scriptable_origin.Parse(origin_str) ==
URLPattern::ParseResult::kSuccess) {
filter_schemes(&scriptable_origin);
if (required_permissions.scriptable_hosts().ContainsPattern(
scriptable_origin)) {
used_origin = true;
result->required_scriptable_hosts.AddPattern(scriptable_origin);
}
}
if (!used_origin)
result->unlisted_hosts.AddPattern(explicit_origin);
}
return true;
}
} // namespace
UnpackPermissionSetResult::UnpackPermissionSetResult() = default;
UnpackPermissionSetResult::~UnpackPermissionSetResult() = default;
std::unique_ptr<Permissions> PackPermissionSet(const PermissionSet& set) {
std::unique_ptr<Permissions> permissions(new Permissions());
permissions->permissions.reset(new std::vector<std::string>());
for (const APIPermission* api : set.apis()) {
std::unique_ptr<base::Value> value(api->ToValue());
if (!value) {
permissions->permissions->push_back(api->name());
} else {
std::string name(api->name());
std::string json;
base::JSONWriter::Write(*value, &json);
permissions->permissions->push_back(name + kDelimiter + json);
}
}
// TODO(rpaquay): We currently don't expose manifest permissions
// to apps/extensions via the permissions API.
permissions->origins.reset(new std::vector<std::string>());
for (const URLPattern& pattern : set.effective_hosts())
permissions->origins->push_back(pattern.GetAsString());
return permissions;
}
std::unique_ptr<UnpackPermissionSetResult> UnpackPermissionSet(
const Permissions& permissions_input,
const PermissionSet& required_permissions,
const PermissionSet& optional_permissions,
bool allow_file_access,
std::string* error) {
DCHECK(error);
// TODO(rpaquay): We currently don't expose manifest permissions
// to apps/extensions via the permissions API.
auto result = std::make_unique<UnpackPermissionSetResult>();
if (permissions_input.permissions &&
!UnpackAPIPermissions(*permissions_input.permissions,
required_permissions, optional_permissions,
result.get(), error)) {
return nullptr;
}
if (permissions_input.origins &&
!UnpackOriginPermissions(*permissions_input.origins, required_permissions,
optional_permissions, allow_file_access,
result.get(), error)) {
return nullptr;
}
return result;
}
} // namespace permissions_api_helpers
} // namespace extensions