// Copyright (c) 2011 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/extension_webstore_private_api.h" #include #include "base/string_util.h" #include "base/utf_string_conversions.h" #include "base/values.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/extensions/crx_installer.h" #include "chrome/browser/extensions/extension_function_dispatcher.h" #include "chrome/browser/extensions/extension_install_dialog.h" #include "chrome/browser/extensions/extension_prefs.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/webstore_installer.h" #include "chrome/browser/net/gaia/token_service.h" #include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/common/chrome_notification_types.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/pref_names.h" #include "chrome/common/extensions/extension_constants.h" #include "chrome/common/extensions/extension_error_utils.h" #include "chrome/common/extensions/extension_l10n_util.h" #include "chrome/common/net/gaia/gaia_constants.h" #include "content/browser/tab_contents/tab_contents.h" #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_source.h" #include "grit/chromium_strings.h" #include "grit/generated_resources.h" #include "ui/base/l10n/l10n_util.h" namespace { const char kAppInstallBubbleKey[] = "appInstallBubble"; const char kIconDataKey[] = "iconData"; const char kIconUrlKey[] = "iconUrl"; const char kIdKey[] = "id"; const char kLocalizedNameKey[] = "localizedName"; const char kLoginKey[] = "login"; const char kManifestKey[] = "manifest"; const char kTokenKey[] = "token"; const char kCannotSpecifyIconDataAndUrlError[] = "You cannot specify both icon data and an icon url"; const char kInvalidIconUrlError[] = "Invalid icon url"; const char kInvalidIdError[] = "Invalid id"; const char kInvalidManifestError[] = "Invalid manifest"; const char kNoPreviousBeginInstallWithManifestError[] = "* does not match a previous call to beginInstallWithManifest3"; const char kUserCancelledError[] = "User cancelled install"; const char kPermissionDeniedError[] = "You do not have permission to use this method."; ProfileSyncService* test_sync_service = NULL; // Returns either the test sync service, or the real one from |profile|. ProfileSyncService* GetSyncService(Profile* profile) { if (test_sync_service) return test_sync_service; else return profile->GetProfileSyncService(); } bool IsWebStoreURL(Profile* profile, const GURL& url) { ExtensionService* service = profile->GetExtensionService(); const Extension* store = service->GetWebStoreApp(); if (!store) { NOTREACHED(); return false; } return (service->GetExtensionByWebExtent(url) == store); } // Whitelists extension IDs for use by webstorePrivate.silentlyInstall. bool trust_test_ids = false; bool IsTrustedForSilentInstall(const std::string& id) { // Trust the extensions in api_test/webstore_private/bundle when the flag // is set. if (trust_test_ids && (id == "begfmnajjkbjdgmffnjaojchoncnmngg" || id == "bmfoocgfinpmkmlbjhcbofejhkhlbchk" || id == "mpneghmdnmaolkljkipbhaienajcflfe")) return true; return id == "jgoepmocgafhnchmokaimcmlojpnlkhp" || // +1 Extension id == "cpembckmhnjipbgbnfiocbgnkpjdokdd" || // +1 Extension - dev id == "boemmnepglcoinjcdlfcpcbmhiecichi" || // Notifications id == "flibmgiapaohcbondaoopaalfejliklp" || // Notifications - dev id == "dlppkpafhbajpcmmoheippocdidnckmm" || // Remaining are placeholders id == "hmglfmpefabcafaimbpldpambdfomanl" || id == "idfijlieiecpfcjckpkliefekpokhhnd" || id == "jaokjbijaokooelpahnlmbciccldmfla" || id == "kdjeommiakphmeionoojjljlecmpaldd" || id == "lpdeojkfhenboeibhkjhiancceeboknd"; } // Helper to create a dictionary with login and token properties set from // the appropriate values in the passed-in |profile|. DictionaryValue* CreateLoginResult(Profile* profile) { DictionaryValue* dictionary = new DictionaryValue(); std::string username = profile->GetPrefs()->GetString( prefs::kGoogleServicesUsername); dictionary->SetString(kLoginKey, username); if (!username.empty()) { CommandLine* cmdline = CommandLine::ForCurrentProcess(); TokenService* token_service = profile->GetTokenService(); if (cmdline->HasSwitch(switches::kAppsGalleryReturnTokens) && token_service->HasTokenForService(GaiaConstants::kGaiaService)) { dictionary->SetString(kTokenKey, token_service->GetTokenForService( GaiaConstants::kGaiaService)); } } return dictionary; } WebstoreInstaller::Delegate* test_webstore_installer_delegate = NULL; } // namespace // static void WebstorePrivateApi::SetTestingProfileSyncService( ProfileSyncService* service) { test_sync_service = service; } // static void WebstorePrivateApi::SetWebstoreInstallerDelegateForTesting( WebstoreInstaller::Delegate* delegate) { test_webstore_installer_delegate = delegate; } // static void WebstorePrivateApi::SetTrustTestIDsForTesting(bool allow) { trust_test_ids = allow; } BeginInstallWithManifestFunction::BeginInstallWithManifestFunction() : use_app_installed_bubble_(false) {} BeginInstallWithManifestFunction::~BeginInstallWithManifestFunction() {} bool BeginInstallWithManifestFunction::RunImpl() { if (!IsWebStoreURL(profile_, source_url())) { SetResult(PERMISSION_DENIED); return false; } DictionaryValue* details = NULL; EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &details)); CHECK(details); EXTENSION_FUNCTION_VALIDATE(details->GetString(kIdKey, &id_)); if (!Extension::IdIsValid(id_)) { SetResult(INVALID_ID); error_ = kInvalidIdError; return false; } EXTENSION_FUNCTION_VALIDATE(details->GetString(kManifestKey, &manifest_)); if (details->HasKey(kIconDataKey) && details->HasKey(kIconUrlKey)) { SetResult(ICON_ERROR); error_ = kCannotSpecifyIconDataAndUrlError; return false; } if (details->HasKey(kIconDataKey)) EXTENSION_FUNCTION_VALIDATE(details->GetString(kIconDataKey, &icon_data_)); GURL icon_url; if (details->HasKey(kIconUrlKey)) { std::string tmp_url; EXTENSION_FUNCTION_VALIDATE(details->GetString(kIconUrlKey, &tmp_url)); icon_url = source_url().Resolve(tmp_url); if (!icon_url.is_valid()) { SetResult(INVALID_ICON_URL); error_ = kInvalidIconUrlError; return false; } } if (details->HasKey(kLocalizedNameKey)) EXTENSION_FUNCTION_VALIDATE(details->GetString(kLocalizedNameKey, &localized_name_)); if (details->HasKey(kAppInstallBubbleKey)) EXTENSION_FUNCTION_VALIDATE(details->GetBoolean( kAppInstallBubbleKey, &use_app_installed_bubble_)); net::URLRequestContextGetter* context_getter = NULL; if (!icon_url.is_empty()) context_getter = profile()->GetRequestContext(); scoped_refptr helper = new WebstoreInstallHelper( this, id_, manifest_, icon_data_, icon_url, context_getter); // The helper will call us back via OnWebstoreParseSuccess or // OnWebstoreParseFailure. helper->Start(); // Matched with a Release in OnWebstoreParseSuccess/OnWebstoreParseFailure. AddRef(); // The response is sent asynchronously in OnWebstoreParseSuccess/ // OnWebstoreParseFailure. return true; } void BeginInstallWithManifestFunction::SetResult(ResultCode code) { switch (code) { case ERROR_NONE: result_.reset(Value::CreateStringValue("")); break; case UNKNOWN_ERROR: result_.reset(Value::CreateStringValue("unknown_error")); break; case USER_CANCELLED: result_.reset(Value::CreateStringValue("user_cancelled")); break; case MANIFEST_ERROR: result_.reset(Value::CreateStringValue("manifest_error")); break; case ICON_ERROR: result_.reset(Value::CreateStringValue("icon_error")); break; case INVALID_ID: result_.reset(Value::CreateStringValue("invalid_id")); break; case PERMISSION_DENIED: result_.reset(Value::CreateStringValue("permission_denied")); break; case INVALID_ICON_URL: result_.reset(Value::CreateStringValue("invalid_icon_url")); break; default: CHECK(false); } } void BeginInstallWithManifestFunction::OnWebstoreParseSuccess( const std::string& id, const SkBitmap& icon, DictionaryValue* parsed_manifest) { CHECK_EQ(id_, id); CHECK(parsed_manifest); icon_ = icon; parsed_manifest_.reset(parsed_manifest); ExtensionInstallUI::Prompt prompt(ExtensionInstallUI::INSTALL_PROMPT); if (!ShowExtensionInstallDialogForManifest( profile(), this, parsed_manifest, id_, localized_name_, "", // no localized description &icon_, prompt, &dummy_extension_)) { OnWebstoreParseFailure(id_, WebstoreInstallHelper::Delegate::MANIFEST_ERROR, kInvalidManifestError); return; } // Control flow finishes up in InstallUIProceed or InstallUIAbort. } void BeginInstallWithManifestFunction::OnWebstoreParseFailure( const std::string& id, WebstoreInstallHelper::Delegate::InstallHelperResultCode result_code, const std::string& error_message) { CHECK_EQ(id_, id); // Map from WebstoreInstallHelper's result codes to ours. switch (result_code) { case WebstoreInstallHelper::Delegate::UNKNOWN_ERROR: SetResult(UNKNOWN_ERROR); break; case WebstoreInstallHelper::Delegate::ICON_ERROR: SetResult(ICON_ERROR); break; case WebstoreInstallHelper::Delegate::MANIFEST_ERROR: SetResult(MANIFEST_ERROR); break; default: CHECK(false); } error_ = error_message; SendResponse(false); // Matches the AddRef in RunImpl(). Release(); } void BeginInstallWithManifestFunction::InstallUIProceed() { // This gets cleared in CrxInstaller::ConfirmInstall(). TODO(asargent) - in // the future we may also want to add time-based expiration, where a whitelist // entry is only valid for some number of minutes. CrxInstaller::WhitelistEntry* entry = new CrxInstaller::WhitelistEntry; entry->parsed_manifest.reset(parsed_manifest_.release()); entry->localized_name = localized_name_; entry->use_app_installed_bubble = use_app_installed_bubble_; CrxInstaller::SetWhitelistEntry(id_, entry); SetResult(ERROR_NONE); SendResponse(true); // The Permissions_Install histogram is recorded from the ExtensionService // for all extension installs, so we only need to record the web store // specific histogram here. ExtensionService::RecordPermissionMessagesHistogram( dummy_extension_, "Extensions.Permissions_WebStoreInstall"); // Matches the AddRef in RunImpl(). Release(); } void BeginInstallWithManifestFunction::InstallUIAbort(bool user_initiated) { error_ = kUserCancelledError; SetResult(USER_CANCELLED); SendResponse(false); // The web store install histograms are a subset of the install histograms. // We need to record both histograms here since CrxInstaller::InstallUIAbort // is never called for web store install cancellations. std::string histogram_name = user_initiated ? "Extensions.Permissions_WebStoreInstallCancel" : "Extensions.Permissions_WebStoreInstallAbort"; ExtensionService::RecordPermissionMessagesHistogram( dummy_extension_, histogram_name.c_str()); histogram_name = user_initiated ? "Extensions.Permissions_InstallCancel" : "Extensions.Permissions_InstallAbort"; ExtensionService::RecordPermissionMessagesHistogram( dummy_extension_, histogram_name.c_str()); // Matches the AddRef in RunImpl(). Release(); } bool CompleteInstallFunction::RunImpl() { if (!IsWebStoreURL(profile_, source_url())) return false; std::string id; EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &id)); if (!Extension::IdIsValid(id)) { error_ = kInvalidIdError; return false; } if (!CrxInstaller::IsIdWhitelisted(id) && !CrxInstaller::GetWhitelistEntry(id)) { error_ = ExtensionErrorUtils::FormatErrorMessage( kNoPreviousBeginInstallWithManifestError, id); return false; } // The extension will install through the normal extension install flow, but // the above call to SetWhitelistedInstallId will bypass the normal // permissions install dialog. scoped_refptr installer = new WebstoreInstaller( profile(), test_webstore_installer_delegate, &(dispatcher()->delegate()->GetAssociatedTabContents()->controller()), id, WebstoreInstaller::FLAG_NONE); installer->Start(); return true; } SilentlyInstallFunction::SilentlyInstallFunction() {} SilentlyInstallFunction::~SilentlyInstallFunction() {} bool SilentlyInstallFunction::RunImpl() { if (!IsWebStoreURL(profile_, source_url())) { error_ = kPermissionDeniedError; return false; } DictionaryValue* details = NULL; EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &details)); CHECK(details); EXTENSION_FUNCTION_VALIDATE(details->GetString(kIdKey, &id_)); if (!IsTrustedForSilentInstall(id_)) { error_ = kInvalidIdError; return false; } EXTENSION_FUNCTION_VALIDATE(details->GetString(kManifestKey, &manifest_)); // Matched in OnWebstoreParseFailure, OnExtensionInstall{Success,Failure}. AddRef(); scoped_refptr helper = new WebstoreInstallHelper( this, id_, manifest_, std::string(), GURL(), NULL); helper->Start(); return true; } void SilentlyInstallFunction::OnWebstoreParseSuccess( const std::string& id, const SkBitmap& icon, base::DictionaryValue* parsed_manifest) { CHECK_EQ(id_, id); // This lets CrxInstaller bypass the permission confirmation UI for the // extension. The whitelist entry gets cleared in // CrxInstaller::ConfirmInstall. CrxInstaller::WhitelistEntry* entry = new CrxInstaller::WhitelistEntry; entry->parsed_manifest.reset(parsed_manifest); entry->use_app_installed_bubble = false; entry->skip_post_install_ui = true; CrxInstaller::SetWhitelistEntry(id_, entry); scoped_refptr installer = new WebstoreInstaller( profile(), this, &(dispatcher()->delegate()->GetAssociatedTabContents()->controller()), id_, WebstoreInstaller::FLAG_NONE); installer->Start(); } void SilentlyInstallFunction::OnWebstoreParseFailure( const std::string& id, InstallHelperResultCode result_code, const std::string& error_message) { CHECK_EQ(id_, id); error_ = error_message; SendResponse(false); Release(); // Matches the AddRef() in RunImpl(). } void SilentlyInstallFunction::OnExtensionInstallSuccess(const std::string& id) { CHECK_EQ(id_, id); SendResponse(true); Release(); // Matches the AddRef() in RunImpl(). } void SilentlyInstallFunction::OnExtensionInstallFailure( const std::string& id, const std::string& error) { CHECK_EQ(id_, id); error_ = error; SendResponse(false); Release(); // Matches the AddRef() in RunImpl(). } bool GetBrowserLoginFunction::RunImpl() { if (!IsWebStoreURL(profile_, source_url())) return false; result_.reset(CreateLoginResult(profile_->GetOriginalProfile())); return true; } bool GetStoreLoginFunction::RunImpl() { if (!IsWebStoreURL(profile_, source_url())) return false; ExtensionService* service = profile_->GetExtensionService(); ExtensionPrefs* prefs = service->extension_prefs(); std::string login; if (prefs->GetWebStoreLogin(&login)) { result_.reset(Value::CreateStringValue(login)); } else { result_.reset(Value::CreateStringValue(std::string())); } return true; } bool SetStoreLoginFunction::RunImpl() { if (!IsWebStoreURL(profile_, source_url())) return false; std::string login; EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &login)); ExtensionService* service = profile_->GetExtensionService(); ExtensionPrefs* prefs = service->extension_prefs(); prefs->SetWebStoreLogin(login); return true; }