extension_webstore_private_api.cc revision 201ade2fbba22bfb27ae029f4d23fca6ded109a0
1// Copyright (c) 2010 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 "app/l10n_util.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_prefs.h"
16#include "chrome/browser/extensions/extensions_service.h"
17#include "chrome/browser/net/gaia/token_service.h"
18#include "chrome/browser/profile_manager.h"
19#include "chrome/browser/sync/profile_sync_service.h"
20#include "chrome/browser/tab_contents/tab_contents.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 "chrome/common/notification_service.h"
26#include "chrome/common/notification_type.h"
27#include "grit/chromium_strings.h"
28#include "grit/generated_resources.h"
29#include "net/base/escape.h"
30
31namespace {
32
33const char kLoginKey[] = "login";
34const char kTokenKey[] = "token";
35const char kInvalidIdError[] = "Invalid id";
36const char kNoPreviousBeginInstallError[] =
37    "* does not match a previous call to beginInstall";
38const char kUserGestureRequiredError[] =
39    "This function must be called during a user gesture";
40
41ProfileSyncService* test_sync_service = NULL;
42BrowserSignin* test_signin = NULL;
43bool ignore_user_gesture_for_tests = false;
44
45// Returns either the test sync service, or the real one from |profile|.
46ProfileSyncService* GetSyncService(Profile* profile) {
47  if (test_sync_service)
48    return test_sync_service;
49  else
50    return profile->GetProfileSyncService();
51}
52
53BrowserSignin* GetBrowserSignin(Profile* profile) {
54  if (test_signin)
55    return test_signin;
56  else
57    return profile->GetBrowserSignin();
58}
59
60bool IsWebStoreURL(Profile* profile, const GURL& url) {
61  ExtensionsService* service = profile->GetExtensionsService();
62  const Extension* store = service->GetWebStoreApp();
63  if (!store) {
64    NOTREACHED();
65    return false;
66  }
67  return (service->GetExtensionByWebExtent(url) == store);
68}
69
70// Helper to create a dictionary with login and token properties set from
71// the appropriate values in the passed-in |profile|.
72DictionaryValue* CreateLoginResult(Profile* profile) {
73  DictionaryValue* dictionary = new DictionaryValue();
74  std::string username = GetBrowserSignin(profile)->GetSignedInUsername();
75  dictionary->SetString(kLoginKey, username);
76  if (!username.empty()) {
77    CommandLine* cmdline = CommandLine::ForCurrentProcess();
78    TokenService* token_service = profile->GetTokenService();
79    if (cmdline->HasSwitch(switches::kAppsGalleryReturnTokens) &&
80        token_service->HasTokenForService(GaiaConstants::kGaiaService)) {
81      dictionary->SetString(kTokenKey,
82                            token_service->GetTokenForService(
83                                GaiaConstants::kGaiaService));
84    }
85  }
86  return dictionary;
87}
88
89// If |profile| is not off the record, returns it. Otherwise returns the real
90// (not off the record) default profile.
91Profile* GetDefaultProfile(Profile* profile) {
92  if (!profile->IsOffTheRecord())
93    return profile;
94  else
95    return g_browser_process->profile_manager()->GetDefaultProfile();
96}
97
98}  // namespace
99
100// static
101void WebstorePrivateApi::SetTestingProfileSyncService(
102    ProfileSyncService* service) {
103  test_sync_service = service;
104}
105
106// static
107void WebstorePrivateApi::SetTestingBrowserSignin(BrowserSignin* signin) {
108  test_signin = signin;
109}
110
111// static
112void BeginInstallFunction::SetIgnoreUserGestureForTests(bool ignore) {
113  ignore_user_gesture_for_tests = ignore;
114}
115
116bool BeginInstallFunction::RunImpl() {
117  if (!IsWebStoreURL(profile_, source_url()))
118    return false;
119
120  std::string id;
121  EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &id));
122  if (!Extension::IdIsValid(id)) {
123    error_ = kInvalidIdError;
124    return false;
125  }
126
127  if (!user_gesture() && !ignore_user_gesture_for_tests) {
128    error_ = kUserGestureRequiredError;
129    return false;
130  }
131
132  // This gets cleared in CrxInstaller::ConfirmInstall(). TODO(asargent) - in
133  // the future we may also want to add time-based expiration, where a whitelist
134  // entry is only valid for some number of minutes.
135  CrxInstaller::SetWhitelistedInstallId(id);
136  return true;
137}
138
139bool CompleteInstallFunction::RunImpl() {
140  if (!IsWebStoreURL(profile_, source_url()))
141    return false;
142
143  std::string id;
144  EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &id));
145  if (!Extension::IdIsValid(id)) {
146    error_ = kInvalidIdError;
147    return false;
148  }
149
150  if (!CrxInstaller::IsIdWhitelisted(id)) {
151    error_ = ExtensionErrorUtils::FormatErrorMessage(
152        kNoPreviousBeginInstallError, id);
153    return false;
154  }
155
156  std::vector<std::string> params;
157  params.push_back("id=" + id);
158  params.push_back("lang=" + g_browser_process->GetApplicationLocale());
159  params.push_back("uc");
160  std::string url_string = Extension::GalleryUpdateUrl(true).spec();
161
162  GURL url(url_string + "?response=redirect&x=" +
163      EscapeQueryParamValue(JoinString(params, '&'), true));
164  DCHECK(url.is_valid());
165
166  // The download url for the given |id| is now contained in |url|. We
167  // navigate the current (calling) tab to this url which will result in a
168  // download starting. Once completed it will go through the normal extension
169  // install flow. The above call to SetWhitelistedInstallId will bypass the
170  // normal permissions install dialog.
171  NavigationController& controller =
172      dispatcher()->delegate()->associated_tab_contents()->controller();
173  controller.LoadURL(url, source_url(), PageTransition::LINK);
174
175  return true;
176}
177
178bool GetBrowserLoginFunction::RunImpl() {
179  if (!IsWebStoreURL(profile_, source_url()))
180    return false;
181  result_.reset(CreateLoginResult(GetDefaultProfile(profile_)));
182  return true;
183}
184
185bool GetStoreLoginFunction::RunImpl() {
186  if (!IsWebStoreURL(profile_, source_url()))
187    return false;
188  ExtensionsService* service = profile_->GetExtensionsService();
189  ExtensionPrefs* prefs = service->extension_prefs();
190  std::string login;
191  if (prefs->GetWebStoreLogin(&login)) {
192    result_.reset(Value::CreateStringValue(login));
193  } else {
194    result_.reset(Value::CreateStringValue(std::string()));
195  }
196  return true;
197}
198
199bool SetStoreLoginFunction::RunImpl() {
200  if (!IsWebStoreURL(profile_, source_url()))
201    return false;
202  std::string login;
203  EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &login));
204  ExtensionsService* service = profile_->GetExtensionsService();
205  ExtensionPrefs* prefs = service->extension_prefs();
206  prefs->SetWebStoreLogin(login);
207  return true;
208}
209
210PromptBrowserLoginFunction::PromptBrowserLoginFunction()
211    : waiting_for_token_(false) {}
212
213PromptBrowserLoginFunction::~PromptBrowserLoginFunction() {
214}
215
216bool PromptBrowserLoginFunction::RunImpl() {
217  if (!IsWebStoreURL(profile_, source_url()))
218    return false;
219
220  std::string preferred_email;
221  if (args_->GetSize() > 0) {
222    EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &preferred_email));
223  }
224
225  Profile* profile = GetDefaultProfile(profile_);
226
227  // Login can currently only be invoked tab-modal.  Since this is
228  // coming from the webstore, we should always have a tab, but check
229  // just in case.
230  TabContents* tab = dispatcher()->delegate()->associated_tab_contents();
231  if (!tab)
232    return false;
233
234  // We return the result asynchronously, so we addref to keep ourself alive.
235  // Matched with a Release in OnLoginSuccess() and OnLoginFailure().
236  AddRef();
237
238  // Start listening for notifications about the token.
239  TokenService* token_service = profile->GetTokenService();
240  registrar_.Add(this,
241                 NotificationType::TOKEN_AVAILABLE,
242                 Source<TokenService>(token_service));
243  registrar_.Add(this,
244                 NotificationType::TOKEN_REQUEST_FAILED,
245                 Source<TokenService>(token_service));
246
247  GetBrowserSignin(profile)->RequestSignin(tab,
248                                           ASCIIToUTF16(preferred_email),
249                                           GetLoginMessage(),
250                                           this);
251
252  // The response will be sent asynchronously in OnLoginSuccess/OnLoginFailure.
253  return true;
254}
255
256string16 PromptBrowserLoginFunction::GetLoginMessage() {
257  using l10n_util::GetStringUTF16;
258  using l10n_util::GetStringFUTF16;
259
260  // TODO(johnnyg): This would be cleaner as an HTML template.
261  // http://crbug.com/60216
262  string16 message;
263  message = ASCIIToUTF16("<p>")
264      + GetStringUTF16(IDS_WEB_STORE_LOGIN_INTRODUCTION_1)
265      + ASCIIToUTF16("</p>");
266  message = message + ASCIIToUTF16("<p>")
267      + GetStringFUTF16(IDS_WEB_STORE_LOGIN_INTRODUCTION_2,
268                        GetStringUTF16(IDS_PRODUCT_NAME))
269      + ASCIIToUTF16("</p>");
270  return message;
271}
272
273void PromptBrowserLoginFunction::OnLoginSuccess() {
274  // Ensure that apps are synced.
275  // - If the user has already setup sync, we add Apps to the current types.
276  // - If not, we create a new set which is just Apps.
277  ProfileSyncService* service = GetSyncService(GetDefaultProfile(profile_));
278  syncable::ModelTypeSet types;
279  if (service->HasSyncSetupCompleted())
280    service->GetPreferredDataTypes(&types);
281  types.insert(syncable::APPS);
282  service->ChangePreferredDataTypes(types);
283  service->SetSyncSetupCompleted();
284
285  // We'll finish up in Observe() when the token is ready.
286  waiting_for_token_ = true;
287}
288
289void PromptBrowserLoginFunction::OnLoginFailure(
290    const GoogleServiceAuthError& error) {
291  SendResponse(false);
292  // Matches the AddRef in RunImpl().
293  Release();
294}
295
296void PromptBrowserLoginFunction::Observe(NotificationType type,
297                                         const NotificationSource& source,
298                                         const NotificationDetails& details) {
299  // Make sure this notification is for the service we are interested in.
300  std::string service;
301  if (type == NotificationType::TOKEN_AVAILABLE) {
302    TokenService::TokenAvailableDetails* available =
303        Details<TokenService::TokenAvailableDetails>(details).ptr();
304    service = available->service();
305  } else if (type == NotificationType::TOKEN_REQUEST_FAILED) {
306    TokenService::TokenRequestFailedDetails* failed =
307        Details<TokenService::TokenRequestFailedDetails>(details).ptr();
308    service = failed->service();
309  } else {
310    NOTREACHED();
311  }
312
313  if (service != GaiaConstants::kGaiaService) {
314    return;
315  }
316
317  DCHECK(waiting_for_token_);
318
319  result_.reset(CreateLoginResult(GetDefaultProfile(profile_)));
320  SendResponse(true);
321
322  // Matches the AddRef in RunImpl().
323  Release();
324}
325