// Copyright (c) 2013 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/permissions/permissions_data.h" #include "base/command_line.h" #include "base/memory/scoped_ptr.h" #include "base/strings/string16.h" #include "base/strings/string_number_conversions.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/values.h" #include "content/public/common/url_constants.h" #include "extensions/common/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_message_provider.h" #include "extensions/common/permissions/permission_set.h" #include "extensions/common/permissions/permissions_info.h" #include "extensions/common/switches.h" #include "extensions/common/url_pattern_set.h" #include "extensions/common/user_script.h" #include "url/gurl.h" namespace extensions { namespace keys = manifest_keys; namespace errors = manifest_errors; namespace { PermissionsData::PolicyDelegate* g_policy_delegate = NULL; // 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(chrome::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, 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; 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); // http://crbug.com/176381 if (!feature) { to_remove.push_back(iter->id()); continue; } Feature::Availability availability = feature->IsAvailableToManifest( extension->id(), extension->GetType(), Feature::ConvertLocation(extension->location()), extension->manifest_version()); 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 = 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(chrome::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() != chrome::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; } // Returns true if this extension id is from a trusted provider. bool IsTrustedId(const std::string& extension_id) { // See http://b/4946060 for more details. return extension_id == std::string("nckgahadagoaajjgafhacjanaoiihapd"); } } // namespace struct PermissionsData::InitialPermissions { APIPermissionSet api_permissions; ManifestPermissionSet manifest_permissions; URLPatternSet host_permissions; URLPatternSet scriptable_hosts; }; PermissionsData::PermissionsData() { } PermissionsData::~PermissionsData() { } // static void PermissionsData::SetPolicyDelegate(PolicyDelegate* delegate) { g_policy_delegate = delegate; } // static const PermissionSet* PermissionsData::GetOptionalPermissions( const Extension* extension) { return extension->permissions_data()->optional_permission_set_.get(); } // static const PermissionSet* PermissionsData::GetRequiredPermissions( const Extension* extension) { return extension->permissions_data()->required_permission_set_.get(); } // static const APIPermissionSet* PermissionsData::GetInitialAPIPermissions( const Extension* extension) { return &extension->permissions_data()-> initial_required_permissions_->api_permissions; } // static APIPermissionSet* PermissionsData::GetInitialAPIPermissions( Extension* extension) { return &extension->permissions_data()-> initial_required_permissions_->api_permissions; } // static void PermissionsData::SetInitialScriptableHosts( Extension* extension, const URLPatternSet& scriptable_hosts) { extension->permissions_data()-> initial_required_permissions_->scriptable_hosts = scriptable_hosts; } // static void PermissionsData::SetActivePermissions(const Extension* extension, const PermissionSet* permissions) { base::AutoLock auto_lock(extension->permissions_data()->runtime_lock_); extension->permissions_data()->active_permissions_ = permissions; } // static scoped_refptr<const PermissionSet> PermissionsData::GetActivePermissions( const Extension* extension) { return extension->permissions_data()->active_permissions_; } // static scoped_refptr<const PermissionSet> PermissionsData::GetTabSpecificPermissions( const Extension* extension, int tab_id) { CHECK_GE(tab_id, 0); TabPermissionsMap::const_iterator iter = extension->permissions_data()->tab_specific_permissions_.find(tab_id); return (iter != extension->permissions_data()->tab_specific_permissions_.end()) ? iter->second : NULL; } // static void PermissionsData::UpdateTabSpecificPermissions( const Extension* extension, int tab_id, scoped_refptr<const PermissionSet> permissions) { CHECK_GE(tab_id, 0); TabPermissionsMap* tab_permissions = &extension->permissions_data()->tab_specific_permissions_; if (tab_permissions->count(tab_id)) { (*tab_permissions)[tab_id] = PermissionSet::CreateUnion( (*tab_permissions)[tab_id].get(), permissions.get()); } else { (*tab_permissions)[tab_id] = permissions; } } // static void PermissionsData::ClearTabSpecificPermissions( const Extension* extension, int tab_id) { CHECK_GE(tab_id, 0); extension->permissions_data()->tab_specific_permissions_.erase(tab_id); } // static bool PermissionsData::HasAPIPermission(const Extension* extension, APIPermission::ID permission) { base::AutoLock auto_lock(extension->permissions_data()->runtime_lock_); return GetActivePermissions(extension)->HasAPIPermission(permission); } // static bool PermissionsData::HasAPIPermission( const Extension* extension, const std::string& permission_name) { base::AutoLock auto_lock(extension->permissions_data()->runtime_lock_); return GetActivePermissions(extension)->HasAPIPermission(permission_name); } // static bool PermissionsData::HasAPIPermissionForTab( const Extension* extension, int tab_id, APIPermission::ID permission) { if (HasAPIPermission(extension, permission)) return true; // Place autolock below the HasAPIPermission() check, since HasAPIPermission // also acquires the lock. base::AutoLock auto_lock(extension->permissions_data()->runtime_lock_); scoped_refptr<const PermissionSet> tab_permissions = GetTabSpecificPermissions(extension, tab_id); return tab_permissions.get() && tab_permissions->HasAPIPermission(permission); } // static bool PermissionsData::CheckAPIPermissionWithParam( const Extension* extension, APIPermission::ID permission, const APIPermission::CheckParam* param) { base::AutoLock auto_lock(extension->permissions_data()->runtime_lock_); return GetActivePermissions(extension)->CheckAPIPermissionWithParam( permission, param); } // static const URLPatternSet& PermissionsData::GetEffectiveHostPermissions( const Extension* extension) { base::AutoLock auto_lock(extension->permissions_data()->runtime_lock_); return GetActivePermissions(extension)->effective_hosts(); } // static bool PermissionsData::CanSilentlyIncreasePermissions( const Extension* extension) { return extension->location() != Manifest::INTERNAL; } // static bool PermissionsData::ShouldSkipPermissionWarnings(const Extension* extension) { return IsTrustedId(extension->id()); } // static bool PermissionsData::HasHostPermission(const Extension* extension, const GURL& url) { base::AutoLock auto_lock(extension->permissions_data()->runtime_lock_); return GetActivePermissions(extension)->HasExplicitAccessToOrigin(url); } // static bool PermissionsData::HasEffectiveAccessToAllHosts(const Extension* extension) { base::AutoLock auto_lock(extension->permissions_data()->runtime_lock_); return GetActivePermissions(extension)->HasEffectiveAccessToAllHosts(); } // static PermissionMessages PermissionsData::GetPermissionMessages( const Extension* extension) { base::AutoLock auto_lock(extension->permissions_data()->runtime_lock_); if (ShouldSkipPermissionWarnings(extension)) { return PermissionMessages(); } else { return PermissionMessageProvider::Get()->GetPermissionMessages( GetActivePermissions(extension), extension->GetType()); } } // static std::vector<string16> PermissionsData::GetPermissionMessageStrings( const Extension* extension) { base::AutoLock auto_lock(extension->permissions_data()->runtime_lock_); if (ShouldSkipPermissionWarnings(extension)) { return std::vector<string16>(); } else { return PermissionMessageProvider::Get()->GetWarningMessages( GetActivePermissions(extension), extension->GetType()); } } // static std::vector<string16> PermissionsData::GetPermissionMessageDetailsStrings( const Extension* extension) { base::AutoLock auto_lock(extension->permissions_data()->runtime_lock_); if (ShouldSkipPermissionWarnings(extension)) { return std::vector<string16>(); } else { return PermissionMessageProvider::Get()->GetWarningMessagesDetails( GetActivePermissions(extension), extension->GetType()); } } // static bool PermissionsData::CanExecuteScriptOnPage(const Extension* extension, const GURL& document_url, const GURL& top_frame_url, int tab_id, const UserScript* script, int process_id, std::string* error) { base::AutoLock auto_lock(extension->permissions_data()->runtime_lock_); const CommandLine* command_line = CommandLine::ForCurrentProcess(); bool can_execute_everywhere = CanExecuteScriptEverywhere(extension); if (g_policy_delegate && !g_policy_delegate->CanExecuteScriptOnPage( extension, document_url, top_frame_url, tab_id, script, process_id, error)) return false; if (!can_execute_everywhere && !ExtensionsClient::Get()->IsScriptableURL(document_url, error)) { return false; } if (!command_line->HasSwitch(switches::kExtensionsOnChromeURLs)) { if (document_url.SchemeIs(chrome::kChromeUIScheme) && !can_execute_everywhere) { if (error) *error = errors::kCannotAccessChromeUrl; return false; } } if (top_frame_url.SchemeIs(extensions::kExtensionScheme) && top_frame_url.GetOrigin() != Extension::GetBaseURLFromExtensionId(extension->id()).GetOrigin() && !can_execute_everywhere) { if (error) *error = errors::kCannotAccessExtensionUrl; return false; } // If a tab ID is specified, try the tab-specific permissions. if (tab_id >= 0) { scoped_refptr<const PermissionSet> tab_permissions = GetTabSpecificPermissions(extension, tab_id); if (tab_permissions.get() && tab_permissions->explicit_hosts().MatchesSecurityOrigin(document_url)) { return true; } } bool can_access = false; if (script) { // If a script is specified, use its matches. can_access = script->MatchesURL(document_url); } else { // Otherwise, see if this extension has permission to execute script // programmatically on pages. can_access = GetActivePermissions(extension)-> HasExplicitAccessToOrigin(document_url); } if (!can_access && error) { *error = ErrorUtils::FormatErrorMessage(errors::kCannotAccessPage, document_url.spec()); } return can_access; } // static bool PermissionsData::CanExecuteScriptEverywhere(const Extension* extension) { if (extension->location() == Manifest::COMPONENT) return true; const ExtensionsClient::ScriptingWhitelist& whitelist = ExtensionsClient::Get()->GetScriptingWhitelist(); return std::find(whitelist.begin(), whitelist.end(), extension->id()) != whitelist.end(); } // static bool PermissionsData::CanCaptureVisiblePage(const Extension* extension, const GURL& page_url, int tab_id, std::string* error) { if (tab_id >= 0) { scoped_refptr<const PermissionSet> tab_permissions = GetTabSpecificPermissions(extension, tab_id); if (tab_permissions.get() && tab_permissions->explicit_hosts().MatchesSecurityOrigin(page_url)) { return true; } } if (HasHostPermission(extension, page_url) || page_url.GetOrigin() == extension->url()) { return true; } if (error) { *error = ErrorUtils::FormatErrorMessage(errors::kCannotAccessPage, page_url.spec()); } return false; } bool PermissionsData::ParsePermissions(Extension* extension, 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; } // TODO(jeremya/kalman) do this via the features system by exposing the // app.window API to platform apps, with no dependency on any permissions. // See http://crbug.com/120069. if (extension->is_platform_app()) { initial_required_permissions_->api_permissions.insert( APIPermission::kAppCurrentWindowInternal); initial_required_permissions_->api_permissions.insert( APIPermission::kAppRuntime); initial_required_permissions_->api_permissions.insert( APIPermission::kAppWindow); } 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 PermissionsData::InitializeManifestPermissions(Extension* extension) { ManifestHandler::AddExtensionInitialRequiredPermissions( extension, &initial_required_permissions_->manifest_permissions); } void PermissionsData::FinalizePermissions(Extension* extension) { active_permissions_ = new PermissionSet( initial_required_permissions_->api_permissions, initial_required_permissions_->manifest_permissions, initial_required_permissions_->host_permissions, initial_required_permissions_->scriptable_hosts); required_permission_set_ = new PermissionSet( initial_required_permissions_->api_permissions, initial_required_permissions_->manifest_permissions, initial_required_permissions_->host_permissions, initial_required_permissions_->scriptable_hosts); optional_permission_set_ = new PermissionSet( initial_optional_permissions_->api_permissions, initial_optional_permissions_->manifest_permissions, initial_optional_permissions_->host_permissions, URLPatternSet()); initial_required_permissions_.reset(); initial_optional_permissions_.reset(); } } // namespace extensions