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|.
GetSyncService(Profile * profile)52 ProfileSyncService* GetSyncService(Profile* profile) {
53 if (test_sync_service)
54 return test_sync_service;
55 else
56 return profile->GetProfileSyncService();
57 }
58
GetBrowserSignin(Profile * profile)59 BrowserSignin* GetBrowserSignin(Profile* profile) {
60 if (test_signin)
61 return test_signin;
62 else
63 return profile->GetBrowserSignin();
64 }
65
IsWebStoreURL(Profile * profile,const GURL & url)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|.
CreateLoginResult(Profile * 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.
GetDefaultProfile(Profile * 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
SetTestingProfileSyncService(ProfileSyncService * service)107 void WebstorePrivateApi::SetTestingProfileSyncService(
108 ProfileSyncService* service) {
109 test_sync_service = service;
110 }
111
112 // static
SetTestingBrowserSignin(BrowserSignin * signin)113 void WebstorePrivateApi::SetTestingBrowserSignin(BrowserSignin* signin) {
114 test_signin = signin;
115 }
116
117 // static
SetIgnoreUserGestureForTests(bool ignore)118 void BeginInstallFunction::SetIgnoreUserGestureForTests(bool ignore) {
119 ignore_user_gesture_for_tests = ignore;
120 }
121
RunImpl()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:
SafeBeginInstallHelper(BeginInstallWithManifestFunction * client,const std::string & icon_data,const std::string & manifest)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
Start()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
StartWorkOnIOThread()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.
OnDecodeImageSucceeded(const SkBitmap & decoded_image)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 }
OnDecodeImageFailed()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 }
OnJSONParseSucceeded(const ListValue & wrapper)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
OnJSONParseFailed(const std::string & error_message)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
ReportResultsIfComplete()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
ReportResultFromUIThread()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:
~SafeBeginInstallHelper()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
BeginInstallWithManifestFunction()270 BeginInstallWithManifestFunction::BeginInstallWithManifestFunction() {}
271
~BeginInstallWithManifestFunction()272 BeginInstallWithManifestFunction::~BeginInstallWithManifestFunction() {}
273
RunImpl()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
SetResult(ResultCode code)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
OnParseSuccess(const SkBitmap & icon,DictionaryValue * parsed_manifest)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
OnParseFailure(ResultCode result_code,const std::string & error_message)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
InstallUIProceed()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
InstallUIAbort()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
RunImpl()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
RunImpl()441 bool GetBrowserLoginFunction::RunImpl() {
442 if (!IsWebStoreURL(profile_, source_url()))
443 return false;
444 result_.reset(CreateLoginResult(GetDefaultProfile(profile_)));
445 return true;
446 }
447
RunImpl()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
RunImpl()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
PromptBrowserLoginFunction()473 PromptBrowserLoginFunction::PromptBrowserLoginFunction()
474 : waiting_for_token_(false) {}
475
~PromptBrowserLoginFunction()476 PromptBrowserLoginFunction::~PromptBrowserLoginFunction() {
477 }
478
RunImpl()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
GetLoginMessage()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
OnLoginSuccess()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
OnLoginFailure(const GoogleServiceAuthError & error)552 void PromptBrowserLoginFunction::OnLoginFailure(
553 const GoogleServiceAuthError& error) {
554 SendResponse(false);
555 // Matches the AddRef in RunImpl().
556 Release();
557 }
558
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)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