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
34namespace {
35
36const char kLoginKey[] = "login";
37const char kTokenKey[] = "token";
38const char kImageDecodeError[] = "Image decode failed";
39const char kInvalidIdError[] = "Invalid id";
40const char kInvalidManifestError[] = "Invalid manifest";
41const char kNoPreviousBeginInstallError[] =
42    "* does not match a previous call to beginInstall";
43const char kUserCancelledError[] = "User cancelled install";
44const char kUserGestureRequiredError[] =
45    "This function must be called during a user gesture";
46
47ProfileSyncService* test_sync_service = NULL;
48BrowserSignin* test_signin = NULL;
49bool ignore_user_gesture_for_tests = false;
50
51// Returns either the test sync service, or the real one from |profile|.
52ProfileSyncService* GetSyncService(Profile* profile) {
53  if (test_sync_service)
54    return test_sync_service;
55  else
56    return profile->GetProfileSyncService();
57}
58
59BrowserSignin* GetBrowserSignin(Profile* profile) {
60  if (test_signin)
61    return test_signin;
62  else
63    return profile->GetBrowserSignin();
64}
65
66bool 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|.
78DictionaryValue* 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.
97Profile* 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
107void WebstorePrivateApi::SetTestingProfileSyncService(
108    ProfileSyncService* service) {
109  test_sync_service = service;
110}
111
112// static
113void WebstorePrivateApi::SetTestingBrowserSignin(BrowserSignin* signin) {
114  test_signin = signin;
115}
116
117// static
118void BeginInstallFunction::SetIgnoreUserGestureForTests(bool ignore) {
119  ignore_user_gesture_for_tests = ignore;
120}
121
122bool 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.
148class 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
270BeginInstallWithManifestFunction::BeginInstallWithManifestFunction() {}
271
272BeginInstallWithManifestFunction::~BeginInstallWithManifestFunction() {}
273
274bool 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
309void 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
341void 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
373void 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
383void 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
392void 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
401bool 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
441bool GetBrowserLoginFunction::RunImpl() {
442  if (!IsWebStoreURL(profile_, source_url()))
443    return false;
444  result_.reset(CreateLoginResult(GetDefaultProfile(profile_)));
445  return true;
446}
447
448bool 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
462bool 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
473PromptBrowserLoginFunction::PromptBrowserLoginFunction()
474    : waiting_for_token_(false) {}
475
476PromptBrowserLoginFunction::~PromptBrowserLoginFunction() {
477}
478
479bool 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
519string16 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
536void 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
552void PromptBrowserLoginFunction::OnLoginFailure(
553    const GoogleServiceAuthError& error) {
554  SendResponse(false);
555  // Matches the AddRef in RunImpl().
556  Release();
557}
558
559void 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