1// Copyright 2013 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/ui/webui/signin/inline_login_handler_impl.h"
6
7#include <string>
8
9#include "base/bind.h"
10#include "base/strings/string_number_conversions.h"
11#include "base/strings/string_util.h"
12#include "base/strings/utf_string_conversions.h"
13#include "base/values.h"
14#include "chrome/browser/profiles/profile.h"
15#include "chrome/browser/signin/about_signin_internals_factory.h"
16#include "chrome/browser/signin/chrome_signin_client_factory.h"
17#include "chrome/browser/signin/local_auth.h"
18#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
19#include "chrome/browser/signin/signin_manager_factory.h"
20#include "chrome/browser/sync/profile_sync_service.h"
21#include "chrome/browser/sync/profile_sync_service_factory.h"
22#include "chrome/browser/ui/browser_finder.h"
23#include "chrome/browser/ui/browser_window.h"
24#include "chrome/browser/ui/sync/one_click_signin_helper.h"
25#include "chrome/browser/ui/sync/one_click_signin_histogram.h"
26#include "chrome/browser/ui/tabs/tab_strip_model.h"
27#include "chrome/browser/ui/webui/signin/inline_login_ui.h"
28#include "chrome/browser/ui/webui/signin/login_ui_service.h"
29#include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
30#include "chrome/common/url_constants.h"
31#include "components/signin/core/browser/about_signin_internals.h"
32#include "components/signin/core/browser/profile_oauth2_token_service.h"
33#include "components/signin/core/browser/signin_error_controller.h"
34#include "components/signin/core/browser/signin_oauth_helper.h"
35#include "components/signin/core/common/profile_management_switches.h"
36#include "content/public/browser/storage_partition.h"
37#include "content/public/browser/web_ui.h"
38#include "google_apis/gaia/gaia_auth_fetcher.h"
39#include "google_apis/gaia/gaia_auth_util.h"
40#include "google_apis/gaia/gaia_constants.h"
41#include "google_apis/gaia/gaia_urls.h"
42#include "net/base/url_util.h"
43
44namespace {
45
46class InlineSigninHelper : public SigninOAuthHelper::Consumer {
47 public:
48  InlineSigninHelper(
49      base::WeakPtr<InlineLoginHandlerImpl> handler,
50      net::URLRequestContextGetter* getter,
51      Profile* profile,
52      const GURL& current_url,
53      const std::string& email,
54      const std::string& password,
55      const std::string& session_index,
56      const std::string& signin_scoped_device_id,
57      bool choose_what_to_sync,
58      bool confirm_untrusted_signin);
59
60 private:
61  // Overriden from SigninOAuthHelper::Consumer.
62  virtual void OnSigninOAuthInformationAvailable(
63      const std::string& email,
64      const std::string& display_email,
65      const std::string& refresh_token) OVERRIDE;
66  virtual void OnSigninOAuthInformationFailure(
67      const GoogleServiceAuthError& error) OVERRIDE;
68
69  SigninOAuthHelper signin_oauth_helper_;
70  base::WeakPtr<InlineLoginHandlerImpl> handler_;
71  Profile* profile_;
72  GURL current_url_;
73  std::string email_;
74  std::string password_;
75  std::string session_index_;
76  bool choose_what_to_sync_;
77  bool confirm_untrusted_signin_;
78
79  DISALLOW_COPY_AND_ASSIGN(InlineSigninHelper);
80};
81
82InlineSigninHelper::InlineSigninHelper(
83    base::WeakPtr<InlineLoginHandlerImpl> handler,
84    net::URLRequestContextGetter* getter,
85    Profile* profile,
86    const GURL& current_url,
87    const std::string& email,
88    const std::string& password,
89    const std::string& session_index,
90    const std::string& signin_scoped_device_id,
91    bool choose_what_to_sync,
92    bool confirm_untrusted_signin)
93    : signin_oauth_helper_(getter, session_index, signin_scoped_device_id,
94                           this),
95      handler_(handler),
96      profile_(profile),
97      current_url_(current_url),
98      email_(email),
99      password_(password),
100      session_index_(session_index),
101      choose_what_to_sync_(choose_what_to_sync),
102      confirm_untrusted_signin_(confirm_untrusted_signin) {
103  DCHECK(profile_);
104  DCHECK(!email_.empty());
105}
106
107void InlineSigninHelper::OnSigninOAuthInformationAvailable(
108    const std::string& email,
109    const std::string& display_email,
110    const std::string& refresh_token) {
111  content::WebContents* contents = NULL;
112  Browser* browser = NULL;
113  if (handler_) {
114    contents = handler_->web_ui()->GetWebContents();
115    browser = handler_->GetDesktopBrowser();
116  }
117
118  AboutSigninInternals* about_signin_internals =
119      AboutSigninInternalsFactory::GetForProfile(profile_);
120  about_signin_internals->OnRefreshTokenReceived("Successful");
121
122  signin::Source source = signin::GetSourceForPromoURL(current_url_);
123
124  std::string primary_email =
125      SigninManagerFactory::GetForProfile(profile_)->GetAuthenticatedUsername();
126  if (gaia::AreEmailsSame(email, primary_email) &&
127      source == signin::SOURCE_REAUTH &&
128      switches::IsNewProfileManagement()) {
129    chrome::SetLocalAuthCredentials(profile_, password_);
130  }
131
132  if (source == signin::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT ||
133      source == signin::SOURCE_REAUTH) {
134    ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)->
135        UpdateCredentials(email, refresh_token);
136
137    if (signin::IsAutoCloseEnabledInURL(current_url_)) {
138      // Close the gaia sign in tab via a task to make sure we aren't in the
139      // middle of any webui handler code.
140      base::MessageLoop::current()->PostTask(
141          FROM_HERE,
142          base::Bind(&InlineLoginHandlerImpl::CloseTab,
143          handler_,
144          signin::ShouldShowAccountManagement(current_url_)));
145    }
146  } else {
147    ProfileSyncService* sync_service =
148        ProfileSyncServiceFactory::GetForProfile(profile_);
149    SigninErrorController* error_controller =
150        ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)->
151            signin_error_controller();
152
153    bool is_new_avatar_menu = switches::IsNewAvatarMenu();
154
155    OneClickSigninSyncStarter::StartSyncMode start_mode;
156    if (source == signin::SOURCE_SETTINGS || choose_what_to_sync_) {
157      bool show_settings_without_configure =
158          error_controller->HasError() &&
159          sync_service &&
160          sync_service->HasSyncSetupCompleted();
161      start_mode = show_settings_without_configure ?
162          OneClickSigninSyncStarter::SHOW_SETTINGS_WITHOUT_CONFIGURE :
163          OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST;
164    } else {
165      start_mode = is_new_avatar_menu ?
166          OneClickSigninSyncStarter::CONFIRM_SYNC_SETTINGS_FIRST :
167          OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS;
168    }
169
170    OneClickSigninSyncStarter::ConfirmationRequired confirmation_required;
171    if (confirm_untrusted_signin_) {
172      confirmation_required =
173          OneClickSigninSyncStarter::CONFIRM_UNTRUSTED_SIGNIN;
174    } else if (is_new_avatar_menu) {
175      confirmation_required = OneClickSigninSyncStarter::CONFIRM_AFTER_SIGNIN;
176    } else {
177      confirmation_required =
178          source == signin::SOURCE_SETTINGS ||
179          choose_what_to_sync_ ?
180              OneClickSigninSyncStarter::NO_CONFIRMATION :
181              OneClickSigninSyncStarter::CONFIRM_AFTER_SIGNIN;
182    }
183
184    bool start_signin =
185        !OneClickSigninHelper::HandleCrossAccountError(
186            profile_, "",
187            email, password_, refresh_token,
188            OneClickSigninHelper::AUTO_ACCEPT_EXPLICIT,
189            source, start_mode,
190            base::Bind(&InlineLoginHandlerImpl::SyncStarterCallback,
191                       handler_));
192    if (start_signin) {
193      // Call OneClickSigninSyncStarter to exchange oauth code for tokens.
194      // OneClickSigninSyncStarter will delete itself once the job is done.
195      new OneClickSigninSyncStarter(
196          profile_, browser,
197          email, password_, refresh_token,
198          start_mode,
199          contents,
200          confirmation_required,
201          signin::GetNextPageURLForPromoURL(current_url_),
202          base::Bind(&InlineLoginHandlerImpl::SyncStarterCallback, handler_));
203    }
204  }
205
206  base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
207}
208
209void InlineSigninHelper::OnSigninOAuthInformationFailure(
210  const GoogleServiceAuthError& error) {
211  if (handler_)
212    handler_->HandleLoginError(error.ToString());
213
214  AboutSigninInternals* about_signin_internals =
215    AboutSigninInternalsFactory::GetForProfile(profile_);
216  about_signin_internals->OnRefreshTokenReceived("Failure");
217
218  base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
219}
220
221}  // namespace
222
223InlineLoginHandlerImpl::InlineLoginHandlerImpl()
224      : confirm_untrusted_signin_(false),
225        weak_factory_(this) {
226}
227
228InlineLoginHandlerImpl::~InlineLoginHandlerImpl() {}
229
230bool InlineLoginHandlerImpl::HandleContextMenu(
231    const content::ContextMenuParams& params) {
232#ifndef NDEBUG
233  return false;
234#else
235  return true;
236#endif
237}
238
239void InlineLoginHandlerImpl::DidCommitProvisionalLoadForFrame(
240    content::RenderFrameHost* render_frame_host,
241    const GURL& url,
242    ui::PageTransition transition_type) {
243  if (!web_contents())
244    return;
245
246  // Returns early if this is not a gaia iframe navigation.
247  const GURL kGaiaExtOrigin(
248      "chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik/");
249  content::RenderFrameHost* gaia_iframe = InlineLoginUI::GetAuthIframe(
250      web_contents(), kGaiaExtOrigin, "signin-frame");
251  if (render_frame_host != gaia_iframe)
252    return;
253
254  // Loading any untrusted (e.g., HTTP) URLs in the privileged sign-in process
255  // will require confirmation before the sign in takes effect.
256  if (!url.is_empty()) {
257    GURL origin(url.GetOrigin());
258    if (url.spec() != url::kAboutBlankURL &&
259        origin != kGaiaExtOrigin &&
260        !gaia::IsGaiaSignonRealm(origin)) {
261      confirm_untrusted_signin_ = true;
262    }
263  }
264}
265
266void InlineLoginHandlerImpl::SetExtraInitParams(base::DictionaryValue& params) {
267  params.SetString("service", "chromiumsync");
268
269  content::WebContents* contents = web_ui()->GetWebContents();
270  const GURL& current_url = contents->GetURL();
271  std::string is_constrained;
272  net::GetValueForKeyInQuery(current_url, "constrained", &is_constrained);
273  if (is_constrained == "1")
274    contents->SetDelegate(this);
275
276  content::WebContentsObserver::Observe(contents);
277
278  signin::Source source = signin::GetSourceForPromoURL(current_url);
279  OneClickSigninHelper::LogHistogramValue(
280      source, one_click_signin::HISTOGRAM_SHOWN);
281}
282
283void InlineLoginHandlerImpl::CompleteLogin(const base::ListValue* args) {
284  content::WebContents* contents = web_ui()->GetWebContents();
285  const GURL& current_url = contents->GetURL();
286
287  const base::DictionaryValue* dict = NULL;
288  args->GetDictionary(0, &dict);
289
290  bool skip_for_now = false;
291  dict->GetBoolean("skipForNow", &skip_for_now);
292  if (skip_for_now) {
293    signin::SetUserSkippedPromo(Profile::FromWebUI(web_ui()));
294    SyncStarterCallback(OneClickSigninSyncStarter::SYNC_SETUP_FAILURE);
295    return;
296  }
297
298  base::string16 email_string16;
299  dict->GetString("email", &email_string16);
300  DCHECK(!email_string16.empty());
301  std::string email(base::UTF16ToASCII(email_string16));
302
303  base::string16 password_string16;
304  dict->GetString("password", &password_string16);
305  std::string password(base::UTF16ToASCII(password_string16));
306
307  // When doing a SAML sign in, this email check may result in a false
308  // positive.  This happens when the user types one email address in the
309  // gaia sign in page, but signs in to a different account in the SAML sign in
310  // page.
311  std::string default_email;
312  std::string validate_email;
313  if (net::GetValueForKeyInQuery(current_url, "email", &default_email) &&
314      net::GetValueForKeyInQuery(current_url, "validateEmail",
315                                 &validate_email) &&
316      validate_email == "1") {
317    if (!gaia::AreEmailsSame(email, default_email)) {
318      SyncStarterCallback(OneClickSigninSyncStarter::SYNC_SETUP_FAILURE);
319      return;
320    }
321  }
322
323  base::string16 session_index_string16;
324  dict->GetString("sessionIndex", &session_index_string16);
325  std::string session_index = base::UTF16ToASCII(session_index_string16);
326  DCHECK(!session_index.empty());
327
328  bool choose_what_to_sync = false;
329  dict->GetBoolean("chooseWhatToSync", &choose_what_to_sync);
330
331  signin::Source source = signin::GetSourceForPromoURL(current_url);
332  OneClickSigninHelper::LogHistogramValue(
333      source, one_click_signin::HISTOGRAM_ACCEPTED);
334  bool switch_to_advanced =
335      choose_what_to_sync && (source != signin::SOURCE_SETTINGS);
336  OneClickSigninHelper::LogHistogramValue(
337      source,
338      switch_to_advanced ? one_click_signin::HISTOGRAM_WITH_ADVANCED :
339                           one_click_signin::HISTOGRAM_WITH_DEFAULTS);
340
341  OneClickSigninHelper::CanOfferFor can_offer_for =
342      OneClickSigninHelper::CAN_OFFER_FOR_ALL;
343  switch (source) {
344    case signin::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT:
345      can_offer_for = OneClickSigninHelper::CAN_OFFER_FOR_SECONDARY_ACCOUNT;
346      break;
347    case signin::SOURCE_REAUTH: {
348      std::string primary_username =
349          SigninManagerFactory::GetForProfile(
350              Profile::FromWebUI(web_ui()))->GetAuthenticatedUsername();
351      if (!gaia::AreEmailsSame(default_email, primary_username))
352        can_offer_for = OneClickSigninHelper::CAN_OFFER_FOR_SECONDARY_ACCOUNT;
353      break;
354    }
355    default:
356      // No need to change |can_offer_for|.
357      break;
358  }
359
360  std::string error_msg;
361  bool can_offer = OneClickSigninHelper::CanOffer(
362      contents, can_offer_for, email, &error_msg);
363  if (!can_offer) {
364    HandleLoginError(error_msg);
365    return;
366  }
367
368  AboutSigninInternals* about_signin_internals =
369      AboutSigninInternalsFactory::GetForProfile(Profile::FromWebUI(web_ui()));
370  about_signin_internals->OnAuthenticationResultReceived(
371      "GAIA Auth Successful");
372
373  content::StoragePartition* partition =
374      content::BrowserContext::GetStoragePartitionForSite(
375          contents->GetBrowserContext(),
376          GURL(chrome::kChromeUIChromeSigninURL));
377
378  SigninClient* signin_client =
379      ChromeSigninClientFactory::GetForProfile(Profile::FromWebUI(web_ui()));
380  std::string signin_scoped_device_id =
381      signin_client->GetSigninScopedDeviceId();
382  // InlineSigninHelper will delete itself.
383  new InlineSigninHelper(GetWeakPtr(), partition->GetURLRequestContext(),
384                         Profile::FromWebUI(web_ui()), current_url,
385                         email, password, session_index,
386                         signin_scoped_device_id, choose_what_to_sync,
387                         confirm_untrusted_signin_);
388
389  web_ui()->CallJavascriptFunction("inline.login.closeDialog");
390}
391
392void InlineLoginHandlerImpl::HandleLoginError(const std::string& error_msg) {
393  SyncStarterCallback(OneClickSigninSyncStarter::SYNC_SETUP_FAILURE);
394
395  Browser* browser = GetDesktopBrowser();
396  if (browser && !error_msg.empty()) {
397    LoginUIServiceFactory::GetForProfile(Profile::FromWebUI(web_ui()))->
398        DisplayLoginResult(browser, base::UTF8ToUTF16(error_msg));
399  }
400}
401
402Browser* InlineLoginHandlerImpl::GetDesktopBrowser() {
403  Browser* browser = chrome::FindBrowserWithWebContents(
404      web_ui()->GetWebContents());
405  if (!browser) {
406    browser = chrome::FindLastActiveWithProfile(
407        Profile::FromWebUI(web_ui()), chrome::GetActiveDesktop());
408  }
409  return browser;
410}
411
412void InlineLoginHandlerImpl::SyncStarterCallback(
413    OneClickSigninSyncStarter::SyncSetupResult result) {
414  content::WebContents* contents = web_ui()->GetWebContents();
415
416  if (contents->GetController().GetPendingEntry()) {
417    // Do nothing if a navigation is pending, since this call can be triggered
418    // from DidStartLoading. This avoids deleting the pending entry while we are
419    // still navigating to it. See crbug/346632.
420    return;
421  }
422
423  const GURL& current_url = contents->GetLastCommittedURL();
424  signin::Source source = signin::GetSourceForPromoURL(current_url);
425  bool auto_close = signin::IsAutoCloseEnabledInURL(current_url);
426
427  if (result == OneClickSigninSyncStarter::SYNC_SETUP_FAILURE) {
428    OneClickSigninHelper::RedirectToNtpOrAppsPage(contents, source);
429  } else if (auto_close) {
430    base::MessageLoop::current()->PostTask(
431        FROM_HERE,
432        base::Bind(&InlineLoginHandlerImpl::CloseTab,
433                   weak_factory_.GetWeakPtr(),
434                   signin::ShouldShowAccountManagement(current_url)));
435  } else {
436     OneClickSigninHelper::RedirectToNtpOrAppsPageIfNecessary(contents, source);
437  }
438}
439
440void InlineLoginHandlerImpl::CloseTab(bool show_account_management) {
441  content::WebContents* tab = web_ui()->GetWebContents();
442  Browser* browser = chrome::FindBrowserWithWebContents(tab);
443  if (browser) {
444    TabStripModel* tab_strip_model = browser->tab_strip_model();
445    if (tab_strip_model) {
446      int index = tab_strip_model->GetIndexOfWebContents(tab);
447      if (index != TabStripModel::kNoTab) {
448        tab_strip_model->ExecuteContextMenuCommand(
449            index, TabStripModel::CommandCloseTab);
450      }
451    }
452
453    if (show_account_management) {
454      browser->window()->ShowAvatarBubbleFromAvatarButton(
455            BrowserWindow::AVATAR_BUBBLE_MODE_ACCOUNT_MANAGEMENT,
456            signin::ManageAccountsParams());
457    }
458  }
459}
460