| // 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 |