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.
5 #include "chrome/browser/ui/webui/signin/inline_login_handler_impl.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/profiles/profile_window.h"
16 #include "chrome/browser/signin/about_signin_internals_factory.h"
17 #include "chrome/browser/signin/account_tracker_service_factory.h"
18 #include "chrome/browser/signin/chrome_signin_client_factory.h"
19 #include "chrome/browser/signin/local_auth.h"
20 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
21 #include "chrome/browser/signin/signin_error_controller_factory.h"
22 #include "chrome/browser/signin/signin_manager_factory.h"
23 #include "chrome/browser/signin/signin_promo.h"
24 #include "chrome/browser/sync/profile_sync_service.h"
25 #include "chrome/browser/sync/profile_sync_service_factory.h"
26 #include "chrome/browser/ui/browser_finder.h"
27 #include "chrome/browser/ui/browser_window.h"
28 #include "chrome/browser/ui/sync/one_click_signin_helper.h"
29 #include "chrome/browser/ui/tabs/tab_strip_model.h"
30 #include "chrome/browser/ui/webui/signin/inline_login_ui.h"
31 #include "chrome/browser/ui/webui/signin/login_ui_service.h"
32 #include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
33 #include "chrome/common/url_constants.h"
34 #include "components/signin/core/browser/about_signin_internals.h"
35 #include "components/signin/core/browser/account_tracker_service.h"
36 #include "components/signin/core/browser/profile_oauth2_token_service.h"
37 #include "components/signin/core/browser/signin_error_controller.h"
38 #include "components/signin/core/browser/signin_metrics.h"
39 #include "components/signin/core/common/profile_management_switches.h"
40 #include "content/public/browser/storage_partition.h"
41 #include "content/public/browser/web_ui.h"
42 #include "google_apis/gaia/gaia_auth_consumer.h"
43 #include "google_apis/gaia/gaia_auth_fetcher.h"
44 #include "google_apis/gaia/gaia_auth_util.h"
45 #include "google_apis/gaia/gaia_constants.h"
46 #include "google_apis/gaia/gaia_urls.h"
47 #include "net/base/url_util.h"
51 class InlineSigninHelper
: public GaiaAuthConsumer
{
54 base::WeakPtr
<InlineLoginHandlerImpl
> handler
,
55 net::URLRequestContextGetter
* getter
,
57 const GURL
& current_url
,
58 const std::string
& email
,
59 const std::string
& gaia_id
,
60 const std::string
& password
,
61 const std::string
& session_index
,
62 const std::string
& signin_scoped_device_id
,
63 bool choose_what_to_sync
,
64 bool confirm_untrusted_signin
);
67 // Overridden from GaiaAuthConsumer.
68 void OnClientOAuthSuccess(const ClientOAuthResult
& result
) override
;
69 void OnClientOAuthFailure(const GoogleServiceAuthError
& error
)
72 GaiaAuthFetcher gaia_auth_fetcher_
;
73 base::WeakPtr
<InlineLoginHandlerImpl
> handler_
;
78 std::string password_
;
79 std::string session_index_
;
80 bool choose_what_to_sync_
;
81 bool confirm_untrusted_signin_
;
83 DISALLOW_COPY_AND_ASSIGN(InlineSigninHelper
);
86 InlineSigninHelper::InlineSigninHelper(
87 base::WeakPtr
<InlineLoginHandlerImpl
> handler
,
88 net::URLRequestContextGetter
* getter
,
90 const GURL
& current_url
,
91 const std::string
& email
,
92 const std::string
& gaia_id
,
93 const std::string
& password
,
94 const std::string
& session_index
,
95 const std::string
& signin_scoped_device_id
,
96 bool choose_what_to_sync
,
97 bool confirm_untrusted_signin
)
98 : gaia_auth_fetcher_(this, GaiaConstants::kChromeSource
, getter
),
101 current_url_(current_url
),
105 session_index_(session_index
),
106 choose_what_to_sync_(choose_what_to_sync
),
107 confirm_untrusted_signin_(confirm_untrusted_signin
) {
109 DCHECK(!email_
.empty());
110 gaia_auth_fetcher_
.StartCookieForOAuthLoginTokenExchangeWithDeviceId(
111 session_index
, signin_scoped_device_id
);
114 void InlineSigninHelper::OnClientOAuthSuccess(const ClientOAuthResult
& result
) {
115 content::WebContents
* contents
= NULL
;
116 Browser
* browser
= NULL
;
118 contents
= handler_
->web_ui()->GetWebContents();
119 browser
= handler_
->GetDesktopBrowser();
122 AboutSigninInternals
* about_signin_internals
=
123 AboutSigninInternalsFactory::GetForProfile(profile_
);
124 about_signin_internals
->OnRefreshTokenReceived("Successful");
126 AccountTrackerService
* account_tracker
=
127 AccountTrackerServiceFactory::GetForProfile(profile_
);
128 std::string account_id
=
129 account_tracker
->PickAccountIdForAccount(gaia_id_
, email_
);
131 // Prime the account tracker with this combination of gaia id/display email.
132 account_tracker
->SeedAccountInfo(gaia_id_
, email_
);
134 signin_metrics::Source source
= signin::GetSourceForPromoURL(current_url_
);
136 SigninManager
* signin_manager
= SigninManagerFactory::GetForProfile(profile_
);
137 std::string primary_email
= signin_manager
->GetAuthenticatedUsername();
138 if (gaia::AreEmailsSame(email_
, primary_email
) &&
139 source
== signin_metrics::SOURCE_REAUTH
&&
140 switches::IsNewProfileManagement() &&
141 !password_
.empty() &&
142 profiles::IsLockAvailable(profile_
)) {
143 LocalAuth::SetLocalAuthCredentials(profile_
, password_
);
146 if (source
== signin_metrics::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT
||
147 source
== signin_metrics::SOURCE_REAUTH
) {
148 ProfileOAuth2TokenServiceFactory::GetForProfile(profile_
)->
149 UpdateCredentials(account_id
, result
.refresh_token
);
151 if (signin::IsAutoCloseEnabledInURL(current_url_
)) {
152 // Close the gaia sign in tab via a task to make sure we aren't in the
153 // middle of any webui handler code.
154 base::MessageLoop::current()->PostTask(
156 base::Bind(&InlineLoginHandlerImpl::CloseTab
,
158 signin::ShouldShowAccountManagement(current_url_
)));
161 if (source
== signin_metrics::SOURCE_REAUTH
)
162 signin_manager
->MergeSigninCredentialIntoCookieJar();
164 ProfileSyncService
* sync_service
=
165 ProfileSyncServiceFactory::GetForProfile(profile_
);
166 SigninErrorController
* error_controller
=
167 SigninErrorControllerFactory::GetForProfile(profile_
);
169 bool is_new_avatar_menu
= switches::IsNewAvatarMenu();
171 OneClickSigninSyncStarter::StartSyncMode start_mode
;
172 if (source
== signin_metrics::SOURCE_SETTINGS
|| choose_what_to_sync_
) {
173 bool show_settings_without_configure
=
174 error_controller
->HasError() &&
176 sync_service
->HasSyncSetupCompleted();
177 start_mode
= show_settings_without_configure
?
178 OneClickSigninSyncStarter::SHOW_SETTINGS_WITHOUT_CONFIGURE
:
179 OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST
;
181 start_mode
= is_new_avatar_menu
?
182 OneClickSigninSyncStarter::CONFIRM_SYNC_SETTINGS_FIRST
:
183 OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS
;
186 OneClickSigninSyncStarter::ConfirmationRequired confirmation_required
;
187 if (confirm_untrusted_signin_
) {
188 confirmation_required
=
189 OneClickSigninSyncStarter::CONFIRM_UNTRUSTED_SIGNIN
;
190 } else if (is_new_avatar_menu
) {
191 confirmation_required
= OneClickSigninSyncStarter::CONFIRM_AFTER_SIGNIN
;
193 confirmation_required
=
194 source
== signin_metrics::SOURCE_SETTINGS
||
195 choose_what_to_sync_
?
196 OneClickSigninSyncStarter::NO_CONFIRMATION
:
197 OneClickSigninSyncStarter::CONFIRM_AFTER_SIGNIN
;
201 !OneClickSigninHelper::HandleCrossAccountError(
203 email_
, password_
, result
.refresh_token
,
204 OneClickSigninHelper::AUTO_ACCEPT_EXPLICIT
,
206 base::Bind(&InlineLoginHandlerImpl::SyncStarterCallback
,
209 // Call OneClickSigninSyncStarter to exchange oauth code for tokens.
210 // OneClickSigninSyncStarter will delete itself once the job is done.
211 new OneClickSigninSyncStarter(
213 email_
, password_
, result
.refresh_token
,
216 confirmation_required
,
217 signin::GetNextPageURLForPromoURL(current_url_
),
218 base::Bind(&InlineLoginHandlerImpl::SyncStarterCallback
, handler_
));
222 base::MessageLoop::current()->DeleteSoon(FROM_HERE
, this);
225 void InlineSigninHelper::OnClientOAuthFailure(
226 const GoogleServiceAuthError
& error
) {
228 handler_
->HandleLoginError(error
.ToString());
230 AboutSigninInternals
* about_signin_internals
=
231 AboutSigninInternalsFactory::GetForProfile(profile_
);
232 about_signin_internals
->OnRefreshTokenReceived("Failure");
234 base::MessageLoop::current()->DeleteSoon(FROM_HERE
, this);
239 InlineLoginHandlerImpl::InlineLoginHandlerImpl()
240 : confirm_untrusted_signin_(false),
241 weak_factory_(this) {
244 InlineLoginHandlerImpl::~InlineLoginHandlerImpl() {}
246 // This method is not called with webview sign in enabled.
247 void InlineLoginHandlerImpl::DidCommitProvisionalLoadForFrame(
248 content::RenderFrameHost
* render_frame_host
,
250 ui::PageTransition transition_type
) {
254 // Returns early if this is not a gaia iframe navigation.
255 const GURL
kGaiaExtOrigin(
256 "chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik/");
257 content::RenderFrameHost
* gaia_iframe
= InlineLoginUI::GetAuthIframe(
258 web_contents(), kGaiaExtOrigin
, "signin-frame");
259 if (render_frame_host
!= gaia_iframe
)
262 // Loading any untrusted (e.g., HTTP) URLs in the privileged sign-in process
263 // will require confirmation before the sign in takes effect.
264 if (!url
.is_empty()) {
265 GURL
origin(url
.GetOrigin());
266 if (url
.spec() != url::kAboutBlankURL
&&
267 origin
!= kGaiaExtOrigin
&&
268 !gaia::IsGaiaSignonRealm(origin
)) {
269 confirm_untrusted_signin_
= true;
274 void InlineLoginHandlerImpl::SetExtraInitParams(base::DictionaryValue
& params
) {
275 params
.SetString("service", "chromiumsync");
277 content::WebContents
* contents
= web_ui()->GetWebContents();
278 const GURL
& current_url
= contents
->GetURL();
279 std::string is_constrained
;
280 net::GetValueForKeyInQuery(current_url
, "constrained", &is_constrained
);
282 content::WebContentsObserver::Observe(contents
);
283 OneClickSigninHelper::LogHistogramValue(signin_metrics::HISTOGRAM_SHOWN
);
286 void InlineLoginHandlerImpl::CompleteLogin(const base::ListValue
* args
) {
287 content::WebContents
* contents
= web_ui()->GetWebContents();
288 const GURL
& current_url
= contents
->GetURL();
290 const base::DictionaryValue
* dict
= NULL
;
291 args
->GetDictionary(0, &dict
);
293 bool skip_for_now
= false;
294 dict
->GetBoolean("skipForNow", &skip_for_now
);
296 signin::SetUserSkippedPromo(Profile::FromWebUI(web_ui()));
297 SyncStarterCallback(OneClickSigninSyncStarter::SYNC_SETUP_FAILURE
);
301 // This value exists only for webview sign in.
302 bool trusted
= false;
303 if (dict
->GetBoolean("trusted", &trusted
))
304 confirm_untrusted_signin_
= !trusted
;
306 base::string16 email_string16
;
307 dict
->GetString("email", &email_string16
);
308 DCHECK(!email_string16
.empty());
309 std::string
email(base::UTF16ToASCII(email_string16
));
311 base::string16 password_string16
;
312 dict
->GetString("password", &password_string16
);
313 std::string
password(base::UTF16ToASCII(password_string16
));
315 base::string16 gaia_id_string16
;
316 dict
->GetString("gaiaId", &gaia_id_string16
);
317 DCHECK(!gaia_id_string16
.empty());
318 std::string gaia_id
= base::UTF16ToASCII(gaia_id_string16
);
320 // When doing a SAML sign in, this email check may result in a false
321 // positive. This happens when the user types one email address in the
322 // gaia sign in page, but signs in to a different account in the SAML sign in
324 std::string default_email
;
325 std::string validate_email
;
326 if (net::GetValueForKeyInQuery(current_url
, "email", &default_email
) &&
327 net::GetValueForKeyInQuery(current_url
, "validateEmail",
329 validate_email
== "1") {
330 if (!gaia::AreEmailsSame(email
, default_email
)) {
331 SyncStarterCallback(OneClickSigninSyncStarter::SYNC_SETUP_FAILURE
);
336 base::string16 session_index_string16
;
337 dict
->GetString("sessionIndex", &session_index_string16
);
338 std::string session_index
= base::UTF16ToASCII(session_index_string16
);
339 DCHECK(!session_index
.empty());
341 bool choose_what_to_sync
= false;
342 dict
->GetBoolean("chooseWhatToSync", &choose_what_to_sync
);
344 signin_metrics::Source source
= signin::GetSourceForPromoURL(current_url
);
345 OneClickSigninHelper::LogHistogramValue(signin_metrics::HISTOGRAM_ACCEPTED
);
346 bool switch_to_advanced
=
347 choose_what_to_sync
&& (source
!= signin_metrics::SOURCE_SETTINGS
);
348 OneClickSigninHelper::LogHistogramValue(
349 switch_to_advanced
? signin_metrics::HISTOGRAM_WITH_ADVANCED
:
350 signin_metrics::HISTOGRAM_WITH_DEFAULTS
);
352 OneClickSigninHelper::CanOfferFor can_offer_for
=
353 OneClickSigninHelper::CAN_OFFER_FOR_ALL
;
355 case signin_metrics::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT
:
356 can_offer_for
= OneClickSigninHelper::CAN_OFFER_FOR_SECONDARY_ACCOUNT
;
358 case signin_metrics::SOURCE_REAUTH
: {
359 std::string primary_username
=
360 SigninManagerFactory::GetForProfile(
361 Profile::FromWebUI(web_ui()))->GetAuthenticatedUsername();
362 if (!gaia::AreEmailsSame(default_email
, primary_username
))
363 can_offer_for
= OneClickSigninHelper::CAN_OFFER_FOR_SECONDARY_ACCOUNT
;
367 // No need to change |can_offer_for|.
371 std::string error_msg
;
372 bool can_offer
= OneClickSigninHelper::CanOffer(
373 contents
, can_offer_for
, email
, &error_msg
);
375 HandleLoginError(error_msg
);
379 AboutSigninInternals
* about_signin_internals
=
380 AboutSigninInternalsFactory::GetForProfile(Profile::FromWebUI(web_ui()));
381 about_signin_internals
->OnAuthenticationResultReceived(
382 "GAIA Auth Successful");
384 content::StoragePartition
* partition
=
385 content::BrowserContext::GetStoragePartitionForSite(
386 contents
->GetBrowserContext(), signin::GetSigninPartitionURL());
388 SigninClient
* signin_client
=
389 ChromeSigninClientFactory::GetForProfile(Profile::FromWebUI(web_ui()));
390 std::string signin_scoped_device_id
=
391 signin_client
->GetSigninScopedDeviceId();
392 // InlineSigninHelper will delete itself.
393 new InlineSigninHelper(GetWeakPtr(), partition
->GetURLRequestContext(),
394 Profile::FromWebUI(web_ui()), current_url
,
395 email
, gaia_id
, password
, session_index
,
396 signin_scoped_device_id
, choose_what_to_sync
,
397 confirm_untrusted_signin_
);
399 web_ui()->CallJavascriptFunction("inline.login.closeDialog");
402 void InlineLoginHandlerImpl::HandleLoginError(const std::string
& error_msg
) {
403 SyncStarterCallback(OneClickSigninSyncStarter::SYNC_SETUP_FAILURE
);
405 Browser
* browser
= GetDesktopBrowser();
406 if (browser
&& !error_msg
.empty()) {
407 LoginUIServiceFactory::GetForProfile(Profile::FromWebUI(web_ui()))->
408 DisplayLoginResult(browser
, base::UTF8ToUTF16(error_msg
));
412 Browser
* InlineLoginHandlerImpl::GetDesktopBrowser() {
413 Browser
* browser
= chrome::FindBrowserWithWebContents(
414 web_ui()->GetWebContents());
416 browser
= chrome::FindLastActiveWithProfile(
417 Profile::FromWebUI(web_ui()), chrome::GetActiveDesktop());
422 void InlineLoginHandlerImpl::SyncStarterCallback(
423 OneClickSigninSyncStarter::SyncSetupResult result
) {
424 content::WebContents
* contents
= web_ui()->GetWebContents();
426 if (contents
->GetController().GetPendingEntry()) {
427 // Do nothing if a navigation is pending, since this call can be triggered
428 // from DidStartLoading. This avoids deleting the pending entry while we are
429 // still navigating to it. See crbug/346632.
433 const GURL
& current_url
= contents
->GetLastCommittedURL();
434 signin_metrics::Source source
= signin::GetSourceForPromoURL(current_url
);
435 bool auto_close
= signin::IsAutoCloseEnabledInURL(current_url
);
437 if (result
== OneClickSigninSyncStarter::SYNC_SETUP_FAILURE
) {
438 OneClickSigninHelper::RedirectToNtpOrAppsPage(contents
, source
);
439 } else if (auto_close
) {
440 base::MessageLoop::current()->PostTask(
442 base::Bind(&InlineLoginHandlerImpl::CloseTab
,
443 weak_factory_
.GetWeakPtr(),
444 signin::ShouldShowAccountManagement(current_url
)));
446 OneClickSigninHelper::RedirectToNtpOrAppsPageIfNecessary(contents
, source
);
450 void InlineLoginHandlerImpl::CloseTab(bool show_account_management
) {
451 content::WebContents
* tab
= web_ui()->GetWebContents();
452 Browser
* browser
= chrome::FindBrowserWithWebContents(tab
);
454 TabStripModel
* tab_strip_model
= browser
->tab_strip_model();
455 if (tab_strip_model
) {
456 int index
= tab_strip_model
->GetIndexOfWebContents(tab
);
457 if (index
!= TabStripModel::kNoTab
) {
458 tab_strip_model
->ExecuteContextMenuCommand(
459 index
, TabStripModel::CommandCloseTab
);
463 if (show_account_management
) {
464 browser
->window()->ShowAvatarBubbleFromAvatarButton(
465 BrowserWindow::AVATAR_BUBBLE_MODE_ACCOUNT_MANAGEMENT
,
466 signin::ManageAccountsParams());