extension_webstore_private_api.cc revision 201ade2fbba22bfb27ae029f4d23fca6ded109a0
1// Copyright (c) 2010 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/extensions/extension_webstore_private_api.h" 6 7#include <string> 8#include <vector> 9 10#include "app/l10n_util.h" 11#include "base/string_util.h" 12#include "base/values.h" 13#include "chrome/browser/browser_process.h" 14#include "chrome/browser/extensions/crx_installer.h" 15#include "chrome/browser/extensions/extension_prefs.h" 16#include "chrome/browser/extensions/extensions_service.h" 17#include "chrome/browser/net/gaia/token_service.h" 18#include "chrome/browser/profile_manager.h" 19#include "chrome/browser/sync/profile_sync_service.h" 20#include "chrome/browser/tab_contents/tab_contents.h" 21#include "chrome/common/chrome_switches.h" 22#include "chrome/common/extensions/extension_constants.h" 23#include "chrome/common/extensions/extension_error_utils.h" 24#include "chrome/common/net/gaia/gaia_constants.h" 25#include "chrome/common/notification_service.h" 26#include "chrome/common/notification_type.h" 27#include "grit/chromium_strings.h" 28#include "grit/generated_resources.h" 29#include "net/base/escape.h" 30 31namespace { 32 33const char kLoginKey[] = "login"; 34const char kTokenKey[] = "token"; 35const char kInvalidIdError[] = "Invalid id"; 36const char kNoPreviousBeginInstallError[] = 37 "* does not match a previous call to beginInstall"; 38const char kUserGestureRequiredError[] = 39 "This function must be called during a user gesture"; 40 41ProfileSyncService* test_sync_service = NULL; 42BrowserSignin* test_signin = NULL; 43bool ignore_user_gesture_for_tests = false; 44 45// Returns either the test sync service, or the real one from |profile|. 46ProfileSyncService* GetSyncService(Profile* profile) { 47 if (test_sync_service) 48 return test_sync_service; 49 else 50 return profile->GetProfileSyncService(); 51} 52 53BrowserSignin* GetBrowserSignin(Profile* profile) { 54 if (test_signin) 55 return test_signin; 56 else 57 return profile->GetBrowserSignin(); 58} 59 60bool IsWebStoreURL(Profile* profile, const GURL& url) { 61 ExtensionsService* service = profile->GetExtensionsService(); 62 const Extension* store = service->GetWebStoreApp(); 63 if (!store) { 64 NOTREACHED(); 65 return false; 66 } 67 return (service->GetExtensionByWebExtent(url) == store); 68} 69 70// Helper to create a dictionary with login and token properties set from 71// the appropriate values in the passed-in |profile|. 72DictionaryValue* CreateLoginResult(Profile* profile) { 73 DictionaryValue* dictionary = new DictionaryValue(); 74 std::string username = GetBrowserSignin(profile)->GetSignedInUsername(); 75 dictionary->SetString(kLoginKey, username); 76 if (!username.empty()) { 77 CommandLine* cmdline = CommandLine::ForCurrentProcess(); 78 TokenService* token_service = profile->GetTokenService(); 79 if (cmdline->HasSwitch(switches::kAppsGalleryReturnTokens) && 80 token_service->HasTokenForService(GaiaConstants::kGaiaService)) { 81 dictionary->SetString(kTokenKey, 82 token_service->GetTokenForService( 83 GaiaConstants::kGaiaService)); 84 } 85 } 86 return dictionary; 87} 88 89// If |profile| is not off the record, returns it. Otherwise returns the real 90// (not off the record) default profile. 91Profile* GetDefaultProfile(Profile* profile) { 92 if (!profile->IsOffTheRecord()) 93 return profile; 94 else 95 return g_browser_process->profile_manager()->GetDefaultProfile(); 96} 97 98} // namespace 99 100// static 101void WebstorePrivateApi::SetTestingProfileSyncService( 102 ProfileSyncService* service) { 103 test_sync_service = service; 104} 105 106// static 107void WebstorePrivateApi::SetTestingBrowserSignin(BrowserSignin* signin) { 108 test_signin = signin; 109} 110 111// static 112void BeginInstallFunction::SetIgnoreUserGestureForTests(bool ignore) { 113 ignore_user_gesture_for_tests = ignore; 114} 115 116bool BeginInstallFunction::RunImpl() { 117 if (!IsWebStoreURL(profile_, source_url())) 118 return false; 119 120 std::string id; 121 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &id)); 122 if (!Extension::IdIsValid(id)) { 123 error_ = kInvalidIdError; 124 return false; 125 } 126 127 if (!user_gesture() && !ignore_user_gesture_for_tests) { 128 error_ = kUserGestureRequiredError; 129 return false; 130 } 131 132 // This gets cleared in CrxInstaller::ConfirmInstall(). TODO(asargent) - in 133 // the future we may also want to add time-based expiration, where a whitelist 134 // entry is only valid for some number of minutes. 135 CrxInstaller::SetWhitelistedInstallId(id); 136 return true; 137} 138 139bool CompleteInstallFunction::RunImpl() { 140 if (!IsWebStoreURL(profile_, source_url())) 141 return false; 142 143 std::string id; 144 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &id)); 145 if (!Extension::IdIsValid(id)) { 146 error_ = kInvalidIdError; 147 return false; 148 } 149 150 if (!CrxInstaller::IsIdWhitelisted(id)) { 151 error_ = ExtensionErrorUtils::FormatErrorMessage( 152 kNoPreviousBeginInstallError, id); 153 return false; 154 } 155 156 std::vector<std::string> params; 157 params.push_back("id=" + id); 158 params.push_back("lang=" + g_browser_process->GetApplicationLocale()); 159 params.push_back("uc"); 160 std::string url_string = Extension::GalleryUpdateUrl(true).spec(); 161 162 GURL url(url_string + "?response=redirect&x=" + 163 EscapeQueryParamValue(JoinString(params, '&'), true)); 164 DCHECK(url.is_valid()); 165 166 // The download url for the given |id| is now contained in |url|. We 167 // navigate the current (calling) tab to this url which will result in a 168 // download starting. Once completed it will go through the normal extension 169 // install flow. The above call to SetWhitelistedInstallId will bypass the 170 // normal permissions install dialog. 171 NavigationController& controller = 172 dispatcher()->delegate()->associated_tab_contents()->controller(); 173 controller.LoadURL(url, source_url(), PageTransition::LINK); 174 175 return true; 176} 177 178bool GetBrowserLoginFunction::RunImpl() { 179 if (!IsWebStoreURL(profile_, source_url())) 180 return false; 181 result_.reset(CreateLoginResult(GetDefaultProfile(profile_))); 182 return true; 183} 184 185bool GetStoreLoginFunction::RunImpl() { 186 if (!IsWebStoreURL(profile_, source_url())) 187 return false; 188 ExtensionsService* service = profile_->GetExtensionsService(); 189 ExtensionPrefs* prefs = service->extension_prefs(); 190 std::string login; 191 if (prefs->GetWebStoreLogin(&login)) { 192 result_.reset(Value::CreateStringValue(login)); 193 } else { 194 result_.reset(Value::CreateStringValue(std::string())); 195 } 196 return true; 197} 198 199bool SetStoreLoginFunction::RunImpl() { 200 if (!IsWebStoreURL(profile_, source_url())) 201 return false; 202 std::string login; 203 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &login)); 204 ExtensionsService* service = profile_->GetExtensionsService(); 205 ExtensionPrefs* prefs = service->extension_prefs(); 206 prefs->SetWebStoreLogin(login); 207 return true; 208} 209 210PromptBrowserLoginFunction::PromptBrowserLoginFunction() 211 : waiting_for_token_(false) {} 212 213PromptBrowserLoginFunction::~PromptBrowserLoginFunction() { 214} 215 216bool PromptBrowserLoginFunction::RunImpl() { 217 if (!IsWebStoreURL(profile_, source_url())) 218 return false; 219 220 std::string preferred_email; 221 if (args_->GetSize() > 0) { 222 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &preferred_email)); 223 } 224 225 Profile* profile = GetDefaultProfile(profile_); 226 227 // Login can currently only be invoked tab-modal. Since this is 228 // coming from the webstore, we should always have a tab, but check 229 // just in case. 230 TabContents* tab = dispatcher()->delegate()->associated_tab_contents(); 231 if (!tab) 232 return false; 233 234 // We return the result asynchronously, so we addref to keep ourself alive. 235 // Matched with a Release in OnLoginSuccess() and OnLoginFailure(). 236 AddRef(); 237 238 // Start listening for notifications about the token. 239 TokenService* token_service = profile->GetTokenService(); 240 registrar_.Add(this, 241 NotificationType::TOKEN_AVAILABLE, 242 Source<TokenService>(token_service)); 243 registrar_.Add(this, 244 NotificationType::TOKEN_REQUEST_FAILED, 245 Source<TokenService>(token_service)); 246 247 GetBrowserSignin(profile)->RequestSignin(tab, 248 ASCIIToUTF16(preferred_email), 249 GetLoginMessage(), 250 this); 251 252 // The response will be sent asynchronously in OnLoginSuccess/OnLoginFailure. 253 return true; 254} 255 256string16 PromptBrowserLoginFunction::GetLoginMessage() { 257 using l10n_util::GetStringUTF16; 258 using l10n_util::GetStringFUTF16; 259 260 // TODO(johnnyg): This would be cleaner as an HTML template. 261 // http://crbug.com/60216 262 string16 message; 263 message = ASCIIToUTF16("<p>") 264 + GetStringUTF16(IDS_WEB_STORE_LOGIN_INTRODUCTION_1) 265 + ASCIIToUTF16("</p>"); 266 message = message + ASCIIToUTF16("<p>") 267 + GetStringFUTF16(IDS_WEB_STORE_LOGIN_INTRODUCTION_2, 268 GetStringUTF16(IDS_PRODUCT_NAME)) 269 + ASCIIToUTF16("</p>"); 270 return message; 271} 272 273void PromptBrowserLoginFunction::OnLoginSuccess() { 274 // Ensure that apps are synced. 275 // - If the user has already setup sync, we add Apps to the current types. 276 // - If not, we create a new set which is just Apps. 277 ProfileSyncService* service = GetSyncService(GetDefaultProfile(profile_)); 278 syncable::ModelTypeSet types; 279 if (service->HasSyncSetupCompleted()) 280 service->GetPreferredDataTypes(&types); 281 types.insert(syncable::APPS); 282 service->ChangePreferredDataTypes(types); 283 service->SetSyncSetupCompleted(); 284 285 // We'll finish up in Observe() when the token is ready. 286 waiting_for_token_ = true; 287} 288 289void PromptBrowserLoginFunction::OnLoginFailure( 290 const GoogleServiceAuthError& error) { 291 SendResponse(false); 292 // Matches the AddRef in RunImpl(). 293 Release(); 294} 295 296void PromptBrowserLoginFunction::Observe(NotificationType type, 297 const NotificationSource& source, 298 const NotificationDetails& details) { 299 // Make sure this notification is for the service we are interested in. 300 std::string service; 301 if (type == NotificationType::TOKEN_AVAILABLE) { 302 TokenService::TokenAvailableDetails* available = 303 Details<TokenService::TokenAvailableDetails>(details).ptr(); 304 service = available->service(); 305 } else if (type == NotificationType::TOKEN_REQUEST_FAILED) { 306 TokenService::TokenRequestFailedDetails* failed = 307 Details<TokenService::TokenRequestFailedDetails>(details).ptr(); 308 service = failed->service(); 309 } else { 310 NOTREACHED(); 311 } 312 313 if (service != GaiaConstants::kGaiaService) { 314 return; 315 } 316 317 DCHECK(waiting_for_token_); 318 319 result_.reset(CreateLoginResult(GetDefaultProfile(profile_))); 320 SendResponse(true); 321 322 // Matches the AddRef in RunImpl(). 323 Release(); 324} 325