// 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/management/management_api.h" #include #include #include #include "base/basictypes.h" #include "base/bind.h" #include "base/json/json_writer.h" #include "base/lazy_instance.h" #include "base/logging.h" #include "base/memory/linked_ptr.h" #include "base/memory/scoped_ptr.h" #include "base/metrics/histogram.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/extensions/api/management/management_api_constants.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_uninstall_dialog.h" #include "chrome/browser/extensions/launch_util.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/extensions/application_launch.h" #include "chrome/browser/ui/webui/extensions/extension_icon_source.h" #include "chrome/common/chrome_utility_messages.h" #include "chrome/common/extensions/api/management.h" #include "chrome/common/extensions/extension_constants.h" #include "chrome/common/extensions/extension_icon_set.h" #include "chrome/common/extensions/manifest_handlers/app_launch_info.h" #include "chrome/common/extensions/manifest_handlers/icons_handler.h" #include "chrome/common/extensions/manifest_url_handler.h" #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_source.h" #include "content/public/browser/utility_process_host.h" #include "content/public/browser/utility_process_host_client.h" #include "extensions/browser/event_router.h" #include "extensions/browser/extension_registry.h" #include "extensions/browser/extension_system.h" #include "extensions/browser/management_policy.h" #include "extensions/common/constants.h" #include "extensions/common/error_utils.h" #include "extensions/common/extension.h" #include "extensions/common/manifest_handlers/offline_enabled_info.h" #include "extensions/common/permissions/permission_set.h" #include "extensions/common/permissions/permissions_data.h" #include "extensions/common/url_pattern.h" #if !defined(OS_ANDROID) #include "chrome/browser/ui/webui/ntp/core_app_launcher_handler.h" #endif using base::IntToString; using content::BrowserThread; using content::UtilityProcessHost; using content::UtilityProcessHostClient; namespace keys = extension_management_api_constants; namespace extensions { namespace management = api::management; namespace { typedef std::vector > ExtensionInfoList; typedef std::vector > IconInfoList; enum AutoConfirmForTest { DO_NOT_SKIP = 0, PROCEED, ABORT }; AutoConfirmForTest auto_confirm_for_test = DO_NOT_SKIP; std::vector CreateWarningsList(const Extension* extension) { std::vector warnings_list; PermissionMessages warnings = PermissionsData::GetPermissionMessages(extension); for (PermissionMessages::const_iterator iter = warnings.begin(); iter != warnings.end(); ++iter) { warnings_list.push_back(base::UTF16ToUTF8(iter->message())); } return warnings_list; } scoped_ptr CreateExtensionInfo( const Extension& extension, ExtensionSystem* system) { scoped_ptr info(new management::ExtensionInfo()); ExtensionService* service = system->extension_service(); info->id = extension.id(); info->name = extension.name(); info->short_name = extension.short_name(); info->enabled = service->IsExtensionEnabled(info->id); info->offline_enabled = OfflineEnabledInfo::IsOfflineEnabled(&extension); info->version = extension.VersionString(); info->description = extension.description(); info->options_url = ManifestURL::GetOptionsPage(&extension).spec(); info->homepage_url.reset(new std::string( ManifestURL::GetHomepageURL(&extension).spec())); info->may_disable = system->management_policy()-> UserMayModifySettings(&extension, NULL); info->is_app = extension.is_app(); if (info->is_app) { if (extension.is_legacy_packaged_app()) info->type = management::ExtensionInfo::TYPE_LEGACY_PACKAGED_APP; else if (extension.is_hosted_app()) info->type = management::ExtensionInfo::TYPE_HOSTED_APP; else info->type = management::ExtensionInfo::TYPE_PACKAGED_APP; } else if (extension.is_theme()) { info->type = management::ExtensionInfo::TYPE_THEME; } else { info->type = management::ExtensionInfo::TYPE_EXTENSION; } if (info->enabled) { info->disabled_reason = management::ExtensionInfo::DISABLED_REASON_NONE; } else { ExtensionPrefs* prefs = ExtensionPrefs::Get(service->profile()); if (prefs->DidExtensionEscalatePermissions(extension.id())) { info->disabled_reason = management::ExtensionInfo::DISABLED_REASON_PERMISSIONS_INCREASE; } else { info->disabled_reason = management::ExtensionInfo::DISABLED_REASON_UNKNOWN; } } if (!ManifestURL::GetUpdateURL(&extension).is_empty()) { info->update_url.reset(new std::string( ManifestURL::GetUpdateURL(&extension).spec())); } if (extension.is_app()) { info->app_launch_url.reset(new std::string( AppLaunchInfo::GetFullLaunchURL(&extension).spec())); } const ExtensionIconSet::IconMap& icons = IconsInfo::GetIcons(&extension).map(); if (!icons.empty()) { info->icons.reset(new IconInfoList()); ExtensionIconSet::IconMap::const_iterator icon_iter; for (icon_iter = icons.begin(); icon_iter != icons.end(); ++icon_iter) { management::IconInfo* icon_info = new management::IconInfo(); icon_info->size = icon_iter->first; GURL url = ExtensionIconSource::GetIconURL( &extension, icon_info->size, ExtensionIconSet::MATCH_EXACTLY, false, NULL); icon_info->url = url.spec(); info->icons->push_back(make_linked_ptr(icon_info)); } } const std::set perms = extension.GetActivePermissions()->GetAPIsAsStrings(); if (!perms.empty()) { std::set::const_iterator perms_iter; for (perms_iter = perms.begin(); perms_iter != perms.end(); ++perms_iter) info->permissions.push_back(*perms_iter); } if (!extension.is_hosted_app()) { // Skip host permissions for hosted apps. const URLPatternSet host_perms = extension.GetActivePermissions()->explicit_hosts(); if (!host_perms.is_empty()) { for (URLPatternSet::const_iterator iter = host_perms.begin(); iter != host_perms.end(); ++iter) { info->host_permissions.push_back(iter->GetAsString()); } } } switch (extension.location()) { case Manifest::INTERNAL: info->install_type = management::ExtensionInfo::INSTALL_TYPE_NORMAL; break; case Manifest::UNPACKED: case Manifest::COMMAND_LINE: info->install_type = management::ExtensionInfo::INSTALL_TYPE_DEVELOPMENT; break; case Manifest::EXTERNAL_PREF: case Manifest::EXTERNAL_REGISTRY: case Manifest::EXTERNAL_PREF_DOWNLOAD: info->install_type = management::ExtensionInfo::INSTALL_TYPE_SIDELOAD; break; case Manifest::EXTERNAL_POLICY: case Manifest::EXTERNAL_POLICY_DOWNLOAD: info->install_type = management::ExtensionInfo::INSTALL_TYPE_ADMIN; break; case Manifest::NUM_LOCATIONS: NOTREACHED(); case Manifest::INVALID_LOCATION: case Manifest::COMPONENT: case Manifest::EXTERNAL_COMPONENT: info->install_type = management::ExtensionInfo::INSTALL_TYPE_OTHER; break; } return info.Pass(); } void AddExtensionInfo(const ExtensionSet& extensions, ExtensionSystem* system, ExtensionInfoList* extension_list) { for (ExtensionSet::const_iterator iter = extensions.begin(); iter != extensions.end(); ++iter) { const Extension& extension = *iter->get(); if (extension.ShouldNotBeVisible()) continue; // Skip built-in extensions/apps. extension_list->push_back(make_linked_ptr( CreateExtensionInfo(extension, system).release())); } } } // namespace ExtensionService* ManagementFunction::service() { return GetProfile()->GetExtensionService(); } ExtensionService* AsyncManagementFunction::service() { return GetProfile()->GetExtensionService(); } bool ManagementGetAllFunction::RunImpl() { ExtensionInfoList extensions; ExtensionRegistry* registry = ExtensionRegistry::Get(GetProfile()); ExtensionSystem* system = ExtensionSystem::Get(GetProfile()); AddExtensionInfo(registry->enabled_extensions(), system, &extensions); AddExtensionInfo(registry->disabled_extensions(), system, &extensions); AddExtensionInfo(registry->terminated_extensions(), system, &extensions); results_ = management::GetAll::Results::Create(extensions); return true; } bool ManagementGetFunction::RunImpl() { scoped_ptr params( management::Get::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params.get()); const Extension* extension = service()->GetExtensionById(params->id, true); if (!extension) { error_ = ErrorUtils::FormatErrorMessage(keys::kNoExtensionError, params->id); return false; } scoped_ptr info = CreateExtensionInfo(*extension, ExtensionSystem::Get(GetProfile())); results_ = management::Get::Results::Create(*info); return true; } bool ManagementGetPermissionWarningsByIdFunction::RunImpl() { scoped_ptr params( management::GetPermissionWarningsById::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params.get()); const Extension* extension = service()->GetExtensionById(params->id, true); if (!extension) { error_ = ErrorUtils::FormatErrorMessage(keys::kNoExtensionError, params->id); return false; } std::vector warnings = CreateWarningsList(extension); results_ = management::GetPermissionWarningsById::Results::Create(warnings); return true; } namespace { // This class helps ManagementGetPermissionWarningsByManifestFunction manage // sending manifest JSON strings to the utility process for parsing. class SafeManifestJSONParser : public UtilityProcessHostClient { public: SafeManifestJSONParser( ManagementGetPermissionWarningsByManifestFunction* client, const std::string& manifest) : client_(client), manifest_(manifest) {} void Start() { CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&SafeManifestJSONParser::StartWorkOnIOThread, this)); } void StartWorkOnIOThread() { CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); UtilityProcessHost* host = UtilityProcessHost::Create( this, base::MessageLoopProxy::current().get()); host->Send(new ChromeUtilityMsg_ParseJSON(manifest_)); } virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE { bool handled = true; IPC_BEGIN_MESSAGE_MAP(SafeManifestJSONParser, message) IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ParseJSON_Succeeded, OnJSONParseSucceeded) IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ParseJSON_Failed, OnJSONParseFailed) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; } void OnJSONParseSucceeded(const base::ListValue& wrapper) { CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); const base::Value* value = NULL; CHECK(wrapper.Get(0, &value)); if (value->IsType(base::Value::TYPE_DICTIONARY)) parsed_manifest_.reset( static_cast(value)->DeepCopy()); else error_ = keys::kManifestParseError; BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&SafeManifestJSONParser::ReportResultFromUIThread, this)); } void OnJSONParseFailed(const std::string& error) { CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); error_ = error; BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&SafeManifestJSONParser::ReportResultFromUIThread, this)); } void ReportResultFromUIThread() { CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); if (error_.empty() && parsed_manifest_.get()) client_->OnParseSuccess(parsed_manifest_.Pass()); else client_->OnParseFailure(error_); } private: virtual ~SafeManifestJSONParser() {} // The client who we'll report results back to. ManagementGetPermissionWarningsByManifestFunction* client_; // Data to parse. std::string manifest_; // Results of parsing. scoped_ptr parsed_manifest_; std::string error_; }; } // namespace bool ManagementGetPermissionWarningsByManifestFunction::RunImpl() { scoped_ptr params( management::GetPermissionWarningsByManifest::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params.get()); scoped_refptr parser = new SafeManifestJSONParser(this, params->manifest_str); parser->Start(); // Matched with a Release() in OnParseSuccess/Failure(). AddRef(); // Response is sent async in OnParseSuccess/Failure(). return true; } void ManagementGetPermissionWarningsByManifestFunction::OnParseSuccess( scoped_ptr parsed_manifest) { CHECK(parsed_manifest.get()); scoped_refptr extension = Extension::Create( base::FilePath(), Manifest::INVALID_LOCATION, *parsed_manifest, Extension::NO_FLAGS, &error_); if (!extension.get()) { OnParseFailure(keys::kExtensionCreateError); return; } std::vector warnings = CreateWarningsList(extension.get()); results_ = management::GetPermissionWarningsByManifest::Results::Create(warnings); SendResponse(true); // Matched with AddRef() in RunImpl(). Release(); } void ManagementGetPermissionWarningsByManifestFunction::OnParseFailure( const std::string& error) { error_ = error; SendResponse(false); // Matched with AddRef() in RunImpl(). Release(); } bool ManagementLaunchAppFunction::RunImpl() { scoped_ptr params( management::LaunchApp::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params.get()); const Extension* extension = service()->GetExtensionById(params->id, true); if (!extension) { error_ = ErrorUtils::FormatErrorMessage(keys::kNoExtensionError, params->id); return false; } if (!extension->is_app()) { error_ = ErrorUtils::FormatErrorMessage(keys::kNotAnAppError, params->id); return false; } // Look at prefs to find the right launch container. // If the user has not set a preference, the default launch value will be // returned. LaunchContainer launch_container = GetLaunchContainer(ExtensionPrefs::Get(GetProfile()), extension); OpenApplication(AppLaunchParams( GetProfile(), extension, launch_container, NEW_FOREGROUND_TAB)); #if !defined(OS_ANDROID) CoreAppLauncherHandler::RecordAppLaunchType( extension_misc::APP_LAUNCH_EXTENSION_API, extension->GetType()); #endif return true; } ManagementSetEnabledFunction::ManagementSetEnabledFunction() { } ManagementSetEnabledFunction::~ManagementSetEnabledFunction() { } bool ManagementSetEnabledFunction::RunImpl() { scoped_ptr params( management::SetEnabled::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params.get()); extension_id_ = params->id; const Extension* extension = service()->GetInstalledExtension(extension_id_); if (!extension || extension->ShouldNotBeVisible()) { error_ = ErrorUtils::FormatErrorMessage( keys::kNoExtensionError, extension_id_); return false; } const ManagementPolicy* policy = ExtensionSystem::Get(GetProfile())->management_policy(); if (!policy->UserMayModifySettings(extension, NULL) || (!params->enabled && policy->MustRemainEnabled(extension, NULL)) || (params->enabled && policy->MustRemainDisabled(extension, NULL, NULL))) { error_ = ErrorUtils::FormatErrorMessage( keys::kUserCantModifyError, extension_id_); return false; } bool currently_enabled = service()->IsExtensionEnabled(extension_id_); if (!currently_enabled && params->enabled) { ExtensionPrefs* prefs = ExtensionPrefs::Get(GetProfile()); if (prefs->DidExtensionEscalatePermissions(extension_id_)) { if (!user_gesture()) { error_ = keys::kGestureNeededForEscalationError; return false; } AddRef(); // Matched in InstallUIProceed/InstallUIAbort install_prompt_.reset( new ExtensionInstallPrompt(GetAssociatedWebContents())); install_prompt_->ConfirmReEnable(this, extension); return true; } service()->EnableExtension(extension_id_); } else if (currently_enabled && !params->enabled) { service()->DisableExtension(extension_id_, Extension::DISABLE_USER_ACTION); } BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&ManagementSetEnabledFunction::SendResponse, this, true)); return true; } void ManagementSetEnabledFunction::InstallUIProceed() { service()->EnableExtension(extension_id_); SendResponse(true); Release(); } void ManagementSetEnabledFunction::InstallUIAbort(bool user_initiated) { error_ = keys::kUserDidNotReEnableError; SendResponse(false); Release(); } ManagementUninstallFunctionBase::ManagementUninstallFunctionBase() { } ManagementUninstallFunctionBase::~ManagementUninstallFunctionBase() { } bool ManagementUninstallFunctionBase::Uninstall( const std::string& target_extension_id, bool show_confirm_dialog) { extension_id_ = target_extension_id; const Extension* target_extension = service()->GetExtensionById(extension_id_, true); if (!target_extension || target_extension->ShouldNotBeVisible()) { error_ = ErrorUtils::FormatErrorMessage( keys::kNoExtensionError, extension_id_); return false; } if (!ExtensionSystem::Get(GetProfile()) ->management_policy() ->UserMayModifySettings(target_extension, NULL)) { error_ = ErrorUtils::FormatErrorMessage( keys::kUserCantModifyError, extension_id_); return false; } if (auto_confirm_for_test == DO_NOT_SKIP) { if (show_confirm_dialog) { AddRef(); // Balanced in ExtensionUninstallAccepted/Canceled extension_uninstall_dialog_.reset(ExtensionUninstallDialog::Create( GetProfile(), GetCurrentBrowser(), this)); if (extension_id() != target_extension_id) { extension_uninstall_dialog_->ConfirmProgrammaticUninstall( target_extension, GetExtension()); } else { // If this is a self uninstall, show the generic uninstall dialog. extension_uninstall_dialog_->ConfirmUninstall(target_extension); } } else { Finish(true); } } else { Finish(auto_confirm_for_test == PROCEED); } return true; } // static void ManagementUninstallFunctionBase::SetAutoConfirmForTest( bool should_proceed) { auto_confirm_for_test = should_proceed ? PROCEED : ABORT; } void ManagementUninstallFunctionBase::Finish(bool should_uninstall) { if (should_uninstall) { bool success = service()->UninstallExtension( extension_id_, false, /* external uninstall */ NULL); // TODO set error_ if !success SendResponse(success); } else { error_ = ErrorUtils::FormatErrorMessage( keys::kUninstallCanceledError, extension_id_); SendResponse(false); } } void ManagementUninstallFunctionBase::ExtensionUninstallAccepted() { Finish(true); Release(); } void ManagementUninstallFunctionBase::ExtensionUninstallCanceled() { Finish(false); Release(); } ManagementUninstallFunction::ManagementUninstallFunction() { } ManagementUninstallFunction::~ManagementUninstallFunction() { } bool ManagementUninstallFunction::RunImpl() { scoped_ptr params( management::Uninstall::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(extension_); EXTENSION_FUNCTION_VALIDATE(params.get()); bool show_confirm_dialog = true; // By default confirmation dialog isn't shown when uninstalling self, but this // can be overridden with showConfirmDialog. if (params->id == extension_->id()) { show_confirm_dialog = params->options.get() && params->options->show_confirm_dialog.get() && *params->options->show_confirm_dialog; } if (show_confirm_dialog && !user_gesture()) { error_ = keys::kGestureNeededForUninstallError; return false; } return Uninstall(params->id, show_confirm_dialog); } ManagementUninstallSelfFunction::ManagementUninstallSelfFunction() { } ManagementUninstallSelfFunction::~ManagementUninstallSelfFunction() { } bool ManagementUninstallSelfFunction::RunImpl() { scoped_ptr params( management::UninstallSelf::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params.get()); bool show_confirm_dialog = false; if (params->options.get() && params->options->show_confirm_dialog.get()) show_confirm_dialog = *params->options->show_confirm_dialog; return Uninstall(extension_->id(), show_confirm_dialog); } ManagementEventRouter::ManagementEventRouter(Profile* profile) : profile_(profile) { int types[] = { chrome::NOTIFICATION_EXTENSION_INSTALLED, chrome::NOTIFICATION_EXTENSION_UNINSTALLED, chrome::NOTIFICATION_EXTENSION_LOADED, chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED }; CHECK(registrar_.IsEmpty()); for (size_t i = 0; i < arraysize(types); i++) { registrar_.Add(this, types[i], content::Source(profile_)); } } ManagementEventRouter::~ManagementEventRouter() {} void ManagementEventRouter::Observe( int type, const content::NotificationSource& source, const content::NotificationDetails& details) { const char* event_name = NULL; const Extension* extension = NULL; Profile* profile = content::Source(source).ptr(); CHECK(profile); CHECK(profile_->IsSameProfile(profile)); switch (type) { case chrome::NOTIFICATION_EXTENSION_INSTALLED: event_name = management::OnInstalled::kEventName; extension = content::Details(details)->extension; break; case chrome::NOTIFICATION_EXTENSION_UNINSTALLED: event_name = management::OnUninstalled::kEventName; extension = content::Details(details).ptr(); break; case chrome::NOTIFICATION_EXTENSION_LOADED: event_name = management::OnEnabled::kEventName; extension = content::Details(details).ptr(); break; case chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED: event_name = management::OnDisabled::kEventName; extension = content::Details(details)->extension; break; default: NOTREACHED(); return; } DCHECK(event_name); DCHECK(extension); if (extension->ShouldNotBeVisible()) return; // Don't dispatch events for built-in extensions. scoped_ptr args(new base::ListValue()); if (event_name == management::OnUninstalled::kEventName) { args->Append(new base::StringValue(extension->id())); } else { scoped_ptr info = CreateExtensionInfo( *extension, ExtensionSystem::Get(profile)); args->Append(info->ToValue().release()); } scoped_ptr event(new Event(event_name, args.Pass())); ExtensionSystem::Get(profile)->event_router()->BroadcastEvent(event.Pass()); } ManagementAPI::ManagementAPI(content::BrowserContext* context) : browser_context_(context) { EventRouter* event_router = ExtensionSystem::Get(browser_context_)->event_router(); event_router->RegisterObserver(this, management::OnInstalled::kEventName); event_router->RegisterObserver(this, management::OnUninstalled::kEventName); event_router->RegisterObserver(this, management::OnEnabled::kEventName); event_router->RegisterObserver(this, management::OnDisabled::kEventName); } ManagementAPI::~ManagementAPI() { } void ManagementAPI::Shutdown() { ExtensionSystem::Get(browser_context_)->event_router()->UnregisterObserver( this); } static base::LazyInstance > g_factory = LAZY_INSTANCE_INITIALIZER; // static BrowserContextKeyedAPIFactory* ManagementAPI::GetFactoryInstance() { return g_factory.Pointer(); } void ManagementAPI::OnListenerAdded(const EventListenerInfo& details) { management_event_router_.reset( new ManagementEventRouter(Profile::FromBrowserContext(browser_context_))); ExtensionSystem::Get(browser_context_)->event_router()->UnregisterObserver( this); } } // namespace extensions