Home | History | Annotate | Download | only in extensions
      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