1 // Copyright (c) 2011 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 "base/memory/scoped_temp_dir.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_install_dialog.h" 16 #include "chrome/browser/extensions/extension_prefs.h" 17 #include "chrome/browser/extensions/extension_service.h" 18 #include "chrome/browser/net/gaia/token_service.h" 19 #include "chrome/browser/profiles/profile_manager.h" 20 #include "chrome/browser/sync/profile_sync_service.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 "content/browser/tab_contents/tab_contents.h" 26 #include "content/common/notification_details.h" 27 #include "content/common/notification_source.h" 28 #include "content/common/notification_type.h" 29 #include "grit/chromium_strings.h" 30 #include "grit/generated_resources.h" 31 #include "net/base/escape.h" 32 #include "ui/base/l10n/l10n_util.h" 33 34 namespace { 35 36 const char kLoginKey[] = "login"; 37 const char kTokenKey[] = "token"; 38 const char kImageDecodeError[] = "Image decode failed"; 39 const char kInvalidIdError[] = "Invalid id"; 40 const char kInvalidManifestError[] = "Invalid manifest"; 41 const char kNoPreviousBeginInstallError[] = 42 "* does not match a previous call to beginInstall"; 43 const char kUserCancelledError[] = "User cancelled install"; 44 const char kUserGestureRequiredError[] = 45 "This function must be called during a user gesture"; 46 47 ProfileSyncService* test_sync_service = NULL; 48 BrowserSignin* test_signin = NULL; 49 bool ignore_user_gesture_for_tests = false; 50 51 // Returns either the test sync service, or the real one from |profile|. 52 ProfileSyncService* GetSyncService(Profile* profile) { 53 if (test_sync_service) 54 return test_sync_service; 55 else 56 return profile->GetProfileSyncService(); 57 } 58 59 BrowserSignin* GetBrowserSignin(Profile* profile) { 60 if (test_signin) 61 return test_signin; 62 else 63 return profile->GetBrowserSignin(); 64 } 65 66 bool IsWebStoreURL(Profile* profile, const GURL& url) { 67 ExtensionService* service = profile->GetExtensionService(); 68 const Extension* store = service->GetWebStoreApp(); 69 if (!store) { 70 NOTREACHED(); 71 return false; 72 } 73 return (service->GetExtensionByWebExtent(url) == store); 74 } 75 76 // Helper to create a dictionary with login and token properties set from 77 // the appropriate values in the passed-in |profile|. 78 DictionaryValue* CreateLoginResult(Profile* profile) { 79 DictionaryValue* dictionary = new DictionaryValue(); 80 std::string username = GetBrowserSignin(profile)->GetSignedInUsername(); 81 dictionary->SetString(kLoginKey, username); 82 if (!username.empty()) { 83 CommandLine* cmdline = CommandLine::ForCurrentProcess(); 84 TokenService* token_service = profile->GetTokenService(); 85 if (cmdline->HasSwitch(switches::kAppsGalleryReturnTokens) && 86 token_service->HasTokenForService(GaiaConstants::kGaiaService)) { 87 dictionary->SetString(kTokenKey, 88 token_service->GetTokenForService( 89 GaiaConstants::kGaiaService)); 90 } 91 } 92 return dictionary; 93 } 94 95 // If |profile| is not incognito, returns it. Otherwise returns the real 96 // (not incognito) default profile. 97 Profile* GetDefaultProfile(Profile* profile) { 98 if (!profile->IsOffTheRecord()) 99 return profile; 100 else 101 return g_browser_process->profile_manager()->GetDefaultProfile(); 102 } 103 104 } // namespace 105 106 // static 107 void WebstorePrivateApi::SetTestingProfileSyncService( 108 ProfileSyncService* service) { 109 test_sync_service = service; 110 } 111 112 // static 113 void WebstorePrivateApi::SetTestingBrowserSignin(BrowserSignin* signin) { 114 test_signin = signin; 115 } 116 117 // static 118 void BeginInstallFunction::SetIgnoreUserGestureForTests(bool ignore) { 119 ignore_user_gesture_for_tests = ignore; 120 } 121 122 bool BeginInstallFunction::RunImpl() { 123 if (!IsWebStoreURL(profile_, source_url())) 124 return false; 125 126 std::string id; 127 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &id)); 128 if (!Extension::IdIsValid(id)) { 129 error_ = kInvalidIdError; 130 return false; 131 } 132 133 if (!user_gesture() && !ignore_user_gesture_for_tests) { 134 error_ = kUserGestureRequiredError; 135 return false; 136 } 137 138 // This gets cleared in CrxInstaller::ConfirmInstall(). TODO(asargent) - in 139 // the future we may also want to add time-based expiration, where a whitelist 140 // entry is only valid for some number of minutes. 141 CrxInstaller::SetWhitelistedInstallId(id); 142 return true; 143 } 144 145 // This is a class to help BeginInstallWithManifestFunction manage sending 146 // JSON manifests and base64-encoded icon data to the utility process for 147 // parsing. 148 class SafeBeginInstallHelper : public UtilityProcessHost::Client { 149 public: 150 SafeBeginInstallHelper(BeginInstallWithManifestFunction* client, 151 const std::string& icon_data, 152 const std::string& manifest) 153 : client_(client), 154 icon_data_(icon_data), 155 manifest_(manifest), 156 utility_host_(NULL), 157 icon_decode_complete_(false), 158 manifest_parse_complete_(false), 159 parse_error_(BeginInstallWithManifestFunction::UNKNOWN_ERROR) {} 160 161 void Start() { 162 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 163 BrowserThread::PostTask( 164 BrowserThread::IO, 165 FROM_HERE, 166 NewRunnableMethod(this, 167 &SafeBeginInstallHelper::StartWorkOnIOThread)); 168 } 169 170 void StartWorkOnIOThread() { 171 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 172 utility_host_ = new UtilityProcessHost(this, BrowserThread::IO); 173 utility_host_->StartBatchMode(); 174 if (icon_data_.empty()) 175 icon_decode_complete_ = true; 176 else 177 utility_host_->StartImageDecodingBase64(icon_data_); 178 utility_host_->StartJSONParsing(manifest_); 179 } 180 181 // Implementing pieces of the UtilityProcessHost::Client interface. 182 virtual void OnDecodeImageSucceeded(const SkBitmap& decoded_image) { 183 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 184 icon_ = decoded_image; 185 icon_decode_complete_ = true; 186 ReportResultsIfComplete(); 187 } 188 virtual void OnDecodeImageFailed() { 189 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 190 icon_decode_complete_ = true; 191 error_ = std::string(kImageDecodeError); 192 parse_error_ = BeginInstallWithManifestFunction::ICON_ERROR; 193 ReportResultsIfComplete(); 194 } 195 virtual void OnJSONParseSucceeded(const ListValue& wrapper) { 196 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 197 manifest_parse_complete_ = true; 198 Value* value = NULL; 199 CHECK(wrapper.Get(0, &value)); 200 if (value->IsType(Value::TYPE_DICTIONARY)) { 201 parsed_manifest_.reset( 202 static_cast<DictionaryValue*>(value)->DeepCopy()); 203 } else { 204 parse_error_ = BeginInstallWithManifestFunction::MANIFEST_ERROR; 205 } 206 ReportResultsIfComplete(); 207 } 208 209 virtual void OnJSONParseFailed(const std::string& error_message) { 210 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 211 manifest_parse_complete_ = true; 212 error_ = error_message; 213 parse_error_ = BeginInstallWithManifestFunction::MANIFEST_ERROR; 214 ReportResultsIfComplete(); 215 } 216 217 void ReportResultsIfComplete() { 218 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 219 220 if (!icon_decode_complete_ || !manifest_parse_complete_) 221 return; 222 223 // The utility_host_ will take care of deleting itself after this call. 224 utility_host_->EndBatchMode(); 225 utility_host_ = NULL; 226 227 BrowserThread::PostTask( 228 BrowserThread::UI, 229 FROM_HERE, 230 NewRunnableMethod(this, 231 &SafeBeginInstallHelper::ReportResultFromUIThread)); 232 } 233 234 void ReportResultFromUIThread() { 235 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 236 if (error_.empty() && parsed_manifest_.get()) 237 client_->OnParseSuccess(icon_, parsed_manifest_.release()); 238 else 239 client_->OnParseFailure(parse_error_, error_); 240 } 241 242 private: 243 ~SafeBeginInstallHelper() {} 244 245 // The client who we'll report results back to. 246 BeginInstallWithManifestFunction* client_; 247 248 // The data to parse. 249 std::string icon_data_; 250 std::string manifest_; 251 252 UtilityProcessHost* utility_host_; 253 254 // Flags for whether we're done doing icon decoding and manifest parsing. 255 bool icon_decode_complete_; 256 bool manifest_parse_complete_; 257 258 // The results of succesful decoding/parsing. 259 SkBitmap icon_; 260 scoped_ptr<DictionaryValue> parsed_manifest_; 261 262 // A details string for keeping track of any errors. 263 std::string error_; 264 265 // A code to distinguish between an error with the icon, and an error with the 266 // manifest. 267 BeginInstallWithManifestFunction::ResultCode parse_error_; 268 }; 269 270 BeginInstallWithManifestFunction::BeginInstallWithManifestFunction() {} 271 272 BeginInstallWithManifestFunction::~BeginInstallWithManifestFunction() {} 273 274 bool BeginInstallWithManifestFunction::RunImpl() { 275 if (!IsWebStoreURL(profile_, source_url())) { 276 SetResult(PERMISSION_DENIED); 277 return false; 278 } 279 280 if (!user_gesture() && !ignore_user_gesture_for_tests) { 281 SetResult(NO_GESTURE); 282 error_ = kUserGestureRequiredError; 283 return false; 284 } 285 286 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &id_)); 287 if (!Extension::IdIsValid(id_)) { 288 SetResult(INVALID_ID); 289 error_ = kInvalidIdError; 290 return false; 291 } 292 293 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &icon_data_)); 294 EXTENSION_FUNCTION_VALIDATE(args_->GetString(2, &manifest_)); 295 296 scoped_refptr<SafeBeginInstallHelper> helper = 297 new SafeBeginInstallHelper(this, icon_data_, manifest_); 298 // The helper will call us back via OnParseSucces or OnParseFailure. 299 helper->Start(); 300 301 // Matched with a Release in OnSuccess/OnFailure. 302 AddRef(); 303 304 // The response is sent asynchronously in OnSuccess/OnFailure. 305 return true; 306 } 307 308 309 void BeginInstallWithManifestFunction::SetResult(ResultCode code) { 310 switch (code) { 311 case ERROR_NONE: 312 result_.reset(Value::CreateStringValue("")); 313 break; 314 case UNKNOWN_ERROR: 315 result_.reset(Value::CreateStringValue("unknown_error")); 316 break; 317 case USER_CANCELLED: 318 result_.reset(Value::CreateStringValue("user_cancelled")); 319 break; 320 case MANIFEST_ERROR: 321 result_.reset(Value::CreateStringValue("manifest_error")); 322 break; 323 case ICON_ERROR: 324 result_.reset(Value::CreateStringValue("icon_error")); 325 break; 326 case INVALID_ID: 327 result_.reset(Value::CreateStringValue("invalid_id")); 328 break; 329 case PERMISSION_DENIED: 330 result_.reset(Value::CreateStringValue("permission_denied")); 331 break; 332 case NO_GESTURE: 333 result_.reset(Value::CreateStringValue("no_gesture")); 334 break; 335 default: 336 CHECK(false); 337 } 338 } 339 340 341 void BeginInstallWithManifestFunction::OnParseSuccess( 342 const SkBitmap& icon, DictionaryValue* parsed_manifest) { 343 CHECK(parsed_manifest); 344 icon_ = icon; 345 parsed_manifest_.reset(parsed_manifest); 346 347 // Create a dummy extension and show the extension install confirmation 348 // dialog. 349 std::string init_errors; 350 dummy_extension_ = Extension::Create( 351 FilePath(), 352 Extension::INTERNAL, 353 *static_cast<DictionaryValue*>(parsed_manifest_.get()), 354 Extension::NO_FLAGS, 355 &init_errors); 356 if (!dummy_extension_.get()) { 357 OnParseFailure(MANIFEST_ERROR, std::string(kInvalidManifestError)); 358 return; 359 } 360 if (icon_.empty()) 361 icon_ = Extension::GetDefaultIcon(dummy_extension_->is_app()); 362 363 ShowExtensionInstallDialog(profile(), 364 this, 365 dummy_extension_.get(), 366 &icon_, 367 dummy_extension_->GetPermissionMessageStrings(), 368 ExtensionInstallUI::INSTALL_PROMPT); 369 370 // Control flow finishes up in InstallUIProceed or InstallUIAbort. 371 } 372 373 void BeginInstallWithManifestFunction::OnParseFailure( 374 ResultCode result_code, const std::string& error_message) { 375 SetResult(result_code); 376 error_ = error_message; 377 SendResponse(false); 378 379 // Matches the AddRef in RunImpl(). 380 Release(); 381 } 382 383 void BeginInstallWithManifestFunction::InstallUIProceed() { 384 CrxInstaller::SetWhitelistedManifest(id_, parsed_manifest_.release()); 385 SetResult(ERROR_NONE); 386 SendResponse(true); 387 388 // Matches the AddRef in RunImpl(). 389 Release(); 390 } 391 392 void BeginInstallWithManifestFunction::InstallUIAbort() { 393 error_ = std::string(kUserCancelledError); 394 SetResult(USER_CANCELLED); 395 SendResponse(false); 396 397 // Matches the AddRef in RunImpl(). 398 Release(); 399 } 400 401 bool CompleteInstallFunction::RunImpl() { 402 if (!IsWebStoreURL(profile_, source_url())) 403 return false; 404 405 std::string id; 406 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &id)); 407 if (!Extension::IdIsValid(id)) { 408 error_ = kInvalidIdError; 409 return false; 410 } 411 412 if (!CrxInstaller::IsIdWhitelisted(id) && 413 !CrxInstaller::GetWhitelistedManifest(id)) { 414 error_ = ExtensionErrorUtils::FormatErrorMessage( 415 kNoPreviousBeginInstallError, id); 416 return false; 417 } 418 419 std::vector<std::string> params; 420 params.push_back("id=" + id); 421 params.push_back("lang=" + g_browser_process->GetApplicationLocale()); 422 params.push_back("uc"); 423 std::string url_string = Extension::GalleryUpdateUrl(true).spec(); 424 425 GURL url(url_string + "?response=redirect&x=" + 426 EscapeQueryParamValue(JoinString(params, '&'), true)); 427 DCHECK(url.is_valid()); 428 429 // The download url for the given |id| is now contained in |url|. We 430 // navigate the current (calling) tab to this url which will result in a 431 // download starting. Once completed it will go through the normal extension 432 // install flow. The above call to SetWhitelistedInstallId will bypass the 433 // normal permissions install dialog. 434 NavigationController& controller = 435 dispatcher()->delegate()->associated_tab_contents()->controller(); 436 controller.LoadURL(url, source_url(), PageTransition::LINK); 437 438 return true; 439 } 440 441 bool GetBrowserLoginFunction::RunImpl() { 442 if (!IsWebStoreURL(profile_, source_url())) 443 return false; 444 result_.reset(CreateLoginResult(GetDefaultProfile(profile_))); 445 return true; 446 } 447 448 bool GetStoreLoginFunction::RunImpl() { 449 if (!IsWebStoreURL(profile_, source_url())) 450 return false; 451 ExtensionService* service = profile_->GetExtensionService(); 452 ExtensionPrefs* prefs = service->extension_prefs(); 453 std::string login; 454 if (prefs->GetWebStoreLogin(&login)) { 455 result_.reset(Value::CreateStringValue(login)); 456 } else { 457 result_.reset(Value::CreateStringValue(std::string())); 458 } 459 return true; 460 } 461 462 bool SetStoreLoginFunction::RunImpl() { 463 if (!IsWebStoreURL(profile_, source_url())) 464 return false; 465 std::string login; 466 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &login)); 467 ExtensionService* service = profile_->GetExtensionService(); 468 ExtensionPrefs* prefs = service->extension_prefs(); 469 prefs->SetWebStoreLogin(login); 470 return true; 471 } 472 473 PromptBrowserLoginFunction::PromptBrowserLoginFunction() 474 : waiting_for_token_(false) {} 475 476 PromptBrowserLoginFunction::~PromptBrowserLoginFunction() { 477 } 478 479 bool PromptBrowserLoginFunction::RunImpl() { 480 if (!IsWebStoreURL(profile_, source_url())) 481 return false; 482 483 std::string preferred_email; 484 if (args_->GetSize() > 0) { 485 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &preferred_email)); 486 } 487 488 Profile* profile = GetDefaultProfile(profile_); 489 490 // Login can currently only be invoked tab-modal. Since this is 491 // coming from the webstore, we should always have a tab, but check 492 // just in case. 493 TabContents* tab = dispatcher()->delegate()->associated_tab_contents(); 494 if (!tab) 495 return false; 496 497 // We return the result asynchronously, so we addref to keep ourself alive. 498 // Matched with a Release in OnLoginSuccess() and OnLoginFailure(). 499 AddRef(); 500 501 // Start listening for notifications about the token. 502 TokenService* token_service = profile->GetTokenService(); 503 registrar_.Add(this, 504 NotificationType::TOKEN_AVAILABLE, 505 Source<TokenService>(token_service)); 506 registrar_.Add(this, 507 NotificationType::TOKEN_REQUEST_FAILED, 508 Source<TokenService>(token_service)); 509 510 GetBrowserSignin(profile)->RequestSignin(tab, 511 ASCIIToUTF16(preferred_email), 512 GetLoginMessage(), 513 this); 514 515 // The response will be sent asynchronously in OnLoginSuccess/OnLoginFailure. 516 return true; 517 } 518 519 string16 PromptBrowserLoginFunction::GetLoginMessage() { 520 using l10n_util::GetStringUTF16; 521 using l10n_util::GetStringFUTF16; 522 523 // TODO(johnnyg): This would be cleaner as an HTML template. 524 // http://crbug.com/60216 525 string16 message; 526 message = ASCIIToUTF16("<p>") 527 + GetStringUTF16(IDS_WEB_STORE_LOGIN_INTRODUCTION_1) 528 + ASCIIToUTF16("</p>"); 529 message = message + ASCIIToUTF16("<p>") 530 + GetStringFUTF16(IDS_WEB_STORE_LOGIN_INTRODUCTION_2, 531 GetStringUTF16(IDS_PRODUCT_NAME)) 532 + ASCIIToUTF16("</p>"); 533 return message; 534 } 535 536 void PromptBrowserLoginFunction::OnLoginSuccess() { 537 // Ensure that apps are synced. 538 // - If the user has already setup sync, we add Apps to the current types. 539 // - If not, we create a new set which is just Apps. 540 ProfileSyncService* service = GetSyncService(GetDefaultProfile(profile_)); 541 syncable::ModelTypeSet types; 542 if (service->HasSyncSetupCompleted()) 543 service->GetPreferredDataTypes(&types); 544 types.insert(syncable::APPS); 545 service->ChangePreferredDataTypes(types); 546 service->SetSyncSetupCompleted(); 547 548 // We'll finish up in Observe() when the token is ready. 549 waiting_for_token_ = true; 550 } 551 552 void PromptBrowserLoginFunction::OnLoginFailure( 553 const GoogleServiceAuthError& error) { 554 SendResponse(false); 555 // Matches the AddRef in RunImpl(). 556 Release(); 557 } 558 559 void PromptBrowserLoginFunction::Observe(NotificationType type, 560 const NotificationSource& source, 561 const NotificationDetails& details) { 562 // Make sure this notification is for the service we are interested in. 563 std::string service; 564 if (type == NotificationType::TOKEN_AVAILABLE) { 565 TokenService::TokenAvailableDetails* available = 566 Details<TokenService::TokenAvailableDetails>(details).ptr(); 567 service = available->service(); 568 } else if (type == NotificationType::TOKEN_REQUEST_FAILED) { 569 TokenService::TokenRequestFailedDetails* failed = 570 Details<TokenService::TokenRequestFailedDetails>(details).ptr(); 571 service = failed->service(); 572 } else { 573 NOTREACHED(); 574 } 575 576 if (service != GaiaConstants::kGaiaService) { 577 return; 578 } 579 580 DCHECK(waiting_for_token_); 581 582 result_.reset(CreateLoginResult(GetDefaultProfile(profile_))); 583 SendResponse(true); 584 585 // Matches the AddRef in RunImpl(). 586 Release(); 587 } 588