// 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 "extensions/common/manifest_handlers/permissions_parser.h" #include "base/command_line.h" #include "base/memory/ref_counted.h" #include "base/strings/utf_string_conversions.h" #include "base/values.h" #include "content/public/common/url_constants.h" #include "extensions/common/error_utils.h" #include "extensions/common/extension.h" #include "extensions/common/extensions_client.h" #include "extensions/common/features/feature.h" #include "extensions/common/features/feature_provider.h" #include "extensions/common/manifest.h" #include "extensions/common/manifest_constants.h" #include "extensions/common/manifest_handler.h" #include "extensions/common/permissions/api_permission_set.h" #include "extensions/common/permissions/permission_set.h" #include "extensions/common/permissions/permissions_data.h" #include "extensions/common/switches.h" #include "extensions/common/url_pattern_set.h" #include "url/url_constants.h" namespace extensions { namespace { namespace keys = manifest_keys; namespace errors = manifest_errors; struct ManifestPermissions : public Extension::ManifestData { ManifestPermissions(scoped_refptr<const PermissionSet> permissions); virtual ~ManifestPermissions(); scoped_refptr<const PermissionSet> permissions; }; ManifestPermissions::ManifestPermissions( scoped_refptr<const PermissionSet> permissions) : permissions(permissions) { } ManifestPermissions::~ManifestPermissions() { } // Custom checks for the experimental permission that can't be expressed in // _permission_features.json. bool CanSpecifyExperimentalPermission(const Extension* extension) { if (extension->location() == Manifest::COMPONENT) return true; if (CommandLine::ForCurrentProcess()->HasSwitch( switches::kEnableExperimentalExtensionApis)) { return true; } // We rely on the webstore to check access to experimental. This way we can // whitelist extensions to have access to experimental in just the store, and // not have to push a new version of the client. if (extension->from_webstore()) return true; return false; } // Checks whether the host |pattern| is allowed for the given |extension|, // given API permissions |permissions|. bool CanSpecifyHostPermission(const Extension* extension, const URLPattern& pattern, const APIPermissionSet& permissions) { if (!pattern.match_all_urls() && pattern.MatchesScheme(content::kChromeUIScheme)) { URLPatternSet chrome_scheme_hosts = ExtensionsClient::Get()->GetPermittedChromeSchemeHosts(extension, permissions); if (chrome_scheme_hosts.ContainsPattern(pattern)) return true; // Component extensions can have access to all of chrome://*. if (PermissionsData::CanExecuteScriptEverywhere(extension)) return true; if (CommandLine::ForCurrentProcess()->HasSwitch( switches::kExtensionsOnChromeURLs)) { return true; } // TODO(aboxhall): return from_webstore() when webstore handles blocking // extensions which request chrome:// urls return false; } // Otherwise, the valid schemes were handled by URLPattern. return true; } // Parses the host and api permissions from the specified permission |key| // from |extension|'s manifest. bool ParseHelper(Extension* extension, const char* key, APIPermissionSet* api_permissions, URLPatternSet* host_permissions, base::string16* error) { if (!extension->manifest()->HasKey(key)) return true; const base::ListValue* permissions = NULL; if (!extension->manifest()->GetList(key, &permissions)) { *error = ErrorUtils::FormatErrorMessageUTF16(errors::kInvalidPermissions, std::string()); return false; } // NOTE: We need to get the APIPermission before we check if features // associated with them are available because the feature system does not // know about aliases. std::vector<std::string> host_data; if (!APIPermissionSet::ParseFromJSON( permissions, APIPermissionSet::kDisallowInternalPermissions, api_permissions, error, &host_data)) { return false; } // Verify feature availability of permissions. std::vector<APIPermission::ID> to_remove; const FeatureProvider* permission_features = FeatureProvider::GetPermissionFeatures(); for (APIPermissionSet::const_iterator iter = api_permissions->begin(); iter != api_permissions->end(); ++iter) { Feature* feature = permission_features->GetFeature(iter->name()); // The feature should exist since we just got an APIPermission for it. The // two systems should be updated together whenever a permission is added. DCHECK(feature) << "Could not find feature for " << iter->name(); // http://crbug.com/176381 if (!feature) { to_remove.push_back(iter->id()); continue; } Feature::Availability availability = feature->IsAvailableToExtension(extension); if (!availability.is_available()) { // Don't fail, but warn the developer that the manifest contains // unrecognized permissions. This may happen legitimately if the // extensions requests platform- or channel-specific permissions. extension->AddInstallWarning( InstallWarning(availability.message(), feature->name())); to_remove.push_back(iter->id()); continue; } if (iter->id() == APIPermission::kExperimental) { if (!CanSpecifyExperimentalPermission(extension)) { *error = base::ASCIIToUTF16(errors::kExperimentalFlagRequired); return false; } } } api_permissions->AddImpliedPermissions(); // Remove permissions that are not available to this extension. for (std::vector<APIPermission::ID>::const_iterator iter = to_remove.begin(); iter != to_remove.end(); ++iter) { api_permissions->erase(*iter); } // Parse host pattern permissions. const int kAllowedSchemes = PermissionsData::CanExecuteScriptEverywhere(extension) ? URLPattern::SCHEME_ALL : Extension::kValidHostPermissionSchemes; for (std::vector<std::string>::const_iterator iter = host_data.begin(); iter != host_data.end(); ++iter) { const std::string& permission_str = *iter; // Check if it's a host pattern permission. URLPattern pattern = URLPattern(kAllowedSchemes); URLPattern::ParseResult parse_result = pattern.Parse(permission_str); if (parse_result == URLPattern::PARSE_SUCCESS) { // The path component is not used for host permissions, so we force it // to match all paths. pattern.SetPath("/*"); int valid_schemes = pattern.valid_schemes(); if (pattern.MatchesScheme(url::kFileScheme) && !PermissionsData::CanExecuteScriptEverywhere(extension)) { extension->set_wants_file_access(true); if (!(extension->creation_flags() & Extension::ALLOW_FILE_ACCESS)) valid_schemes &= ~URLPattern::SCHEME_FILE; } if (pattern.scheme() != content::kChromeUIScheme && !PermissionsData::CanExecuteScriptEverywhere(extension)) { // Keep chrome:// in allowed schemes only if it's explicitly requested // or CanExecuteScriptEverywhere is true. If the // extensions_on_chrome_urls flag is not set, CanSpecifyHostPermission // will fail, so don't check the flag here. valid_schemes &= ~URLPattern::SCHEME_CHROMEUI; } pattern.SetValidSchemes(valid_schemes); if (!CanSpecifyHostPermission(extension, pattern, *api_permissions)) { // TODO(aboxhall): make a warning (see pattern.match_all_urls() block // below). extension->AddInstallWarning(InstallWarning( ErrorUtils::FormatErrorMessage(errors::kInvalidPermissionScheme, permission_str), key, permission_str)); continue; } host_permissions->AddPattern(pattern); // We need to make sure all_urls matches chrome://favicon and (maybe) // chrome://thumbnail, so add them back in to host_permissions separately. if (pattern.match_all_urls()) { host_permissions->AddPatterns( ExtensionsClient::Get()->GetPermittedChromeSchemeHosts( extension, *api_permissions)); } continue; } // It's probably an unknown API permission. Do not throw an error so // extensions can retain backwards compatability (http://crbug.com/42742). extension->AddInstallWarning(InstallWarning( ErrorUtils::FormatErrorMessage( manifest_errors::kPermissionUnknownOrMalformed, permission_str), key, permission_str)); } return true; } } // namespace struct PermissionsParser::InitialPermissions { APIPermissionSet api_permissions; ManifestPermissionSet manifest_permissions; URLPatternSet host_permissions; URLPatternSet scriptable_hosts; }; PermissionsParser::PermissionsParser() { } PermissionsParser::~PermissionsParser() { } bool PermissionsParser::Parse(Extension* extension, base::string16* error) { initial_required_permissions_.reset(new InitialPermissions); if (!ParseHelper(extension, keys::kPermissions, &initial_required_permissions_->api_permissions, &initial_required_permissions_->host_permissions, error)) { return false; } initial_optional_permissions_.reset(new InitialPermissions); if (!ParseHelper(extension, keys::kOptionalPermissions, &initial_optional_permissions_->api_permissions, &initial_optional_permissions_->host_permissions, error)) { return false; } return true; } void PermissionsParser::Finalize(Extension* extension) { ManifestHandler::AddExtensionInitialRequiredPermissions( extension, &initial_required_permissions_->manifest_permissions); scoped_refptr<const PermissionSet> required_permissions( new PermissionSet(initial_required_permissions_->api_permissions, initial_required_permissions_->manifest_permissions, initial_required_permissions_->host_permissions, initial_required_permissions_->scriptable_hosts)); extension->SetManifestData(keys::kPermissions, new ManifestPermissions(required_permissions)); scoped_refptr<const PermissionSet> optional_permissions( new PermissionSet(initial_optional_permissions_->api_permissions, initial_optional_permissions_->manifest_permissions, initial_optional_permissions_->host_permissions, URLPatternSet())); extension->SetManifestData(keys::kOptionalPermissions, new ManifestPermissions(optional_permissions)); } // static void PermissionsParser::AddAPIPermission(Extension* extension, APIPermission::ID permission) { DCHECK(extension->permissions_parser()); extension->permissions_parser() ->initial_required_permissions_->api_permissions.insert(permission); } // static void PermissionsParser::AddAPIPermission(Extension* extension, APIPermission* permission) { DCHECK(extension->permissions_parser()); extension->permissions_parser() ->initial_required_permissions_->api_permissions.insert(permission); } // static bool PermissionsParser::HasAPIPermission(const Extension* extension, APIPermission::ID permission) { DCHECK(extension->permissions_parser()); return extension->permissions_parser() ->initial_required_permissions_->api_permissions.count( permission) > 0; } // static void PermissionsParser::SetScriptableHosts( Extension* extension, const URLPatternSet& scriptable_hosts) { DCHECK(extension->permissions_parser()); extension->permissions_parser() ->initial_required_permissions_->scriptable_hosts = scriptable_hosts; } // static scoped_refptr<const PermissionSet> PermissionsParser::GetRequiredPermissions( const Extension* extension) { DCHECK(extension->GetManifestData(keys::kPermissions)); return static_cast<const ManifestPermissions*>( extension->GetManifestData(keys::kPermissions))->permissions; } // static scoped_refptr<const PermissionSet> PermissionsParser::GetOptionalPermissions( const Extension* extension) { DCHECK(extension->GetManifestData(keys::kOptionalPermissions)); return static_cast<const ManifestPermissions*>( extension->GetManifestData(keys::kOptionalPermissions)) ->permissions; } } // namespace extensions