• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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