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/prefs/pref_service.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/values.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/signin/about_signin_internals_factory.h"
17 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
18 #include "chrome/browser/signin/signin_promo.h"
19 #include "chrome/browser/sync/profile_sync_service.h"
20 #include "chrome/browser/sync/profile_sync_service_factory.h"
21 #include "chrome/browser/ui/browser_finder.h"
22 #include "chrome/browser/ui/sync/one_click_signin_helper.h"
23 #include "chrome/browser/ui/sync/one_click_signin_histogram.h"
24 #include "chrome/browser/ui/tabs/tab_strip_model.h"
25 #include "chrome/common/pref_names.h"
26 #include "chrome/common/url_constants.h"
27 #include "components/signin/core/browser/about_signin_internals.h"
28 #include "components/signin/core/browser/profile_oauth2_token_service.h"
29 #include "components/signin/core/browser/signin_error_controller.h"
30 #include "components/signin/core/browser/signin_oauth_helper.h"
31 #include "content/public/browser/storage_partition.h"
32 #include "content/public/browser/web_contents.h"
33 #include "content/public/browser/web_ui.h"
34 #include "google_apis/gaia/gaia_auth_fetcher.h"
35 #include "google_apis/gaia/gaia_auth_util.h"
36 #include "google_apis/gaia/gaia_constants.h"
37 #include "google_apis/gaia/gaia_urls.h"
38 #include "net/base/url_util.h"
42 class InlineSigninHelper : public SigninOAuthHelper,
43 public SigninOAuthHelper::Consumer {
46 base::WeakPtr<InlineLoginHandlerImpl> handler,
47 net::URLRequestContextGetter* getter,
49 const GURL& current_url,
50 const std::string& email,
51 const std::string& password,
52 const std::string& session_index,
53 bool choose_what_to_sync);
56 // Overriden from SigninOAuthHelper::Consumer.
57 virtual void OnSigninOAuthInformationAvailable(
58 const std::string& email,
59 const std::string& display_email,
60 const std::string& refresh_token) OVERRIDE;
61 virtual void OnSigninOAuthInformationFailure(
62 const GoogleServiceAuthError& error) OVERRIDE;
64 base::WeakPtr<InlineLoginHandlerImpl> handler_;
68 std::string password_;
69 std::string session_index_;
70 bool choose_what_to_sync_;
72 DISALLOW_COPY_AND_ASSIGN(InlineSigninHelper);
75 InlineSigninHelper::InlineSigninHelper(
76 base::WeakPtr<InlineLoginHandlerImpl> handler,
77 net::URLRequestContextGetter* getter,
79 const GURL& current_url,
80 const std::string& email,
81 const std::string& password,
82 const std::string& session_index,
83 bool choose_what_to_sync)
84 : SigninOAuthHelper(getter, session_index, this),
87 current_url_(current_url),
90 session_index_(session_index),
91 choose_what_to_sync_(choose_what_to_sync) {
93 DCHECK(!email_.empty());
94 DCHECK(!session_index_.empty());
97 void InlineSigninHelper::OnSigninOAuthInformationAvailable(
98 const std::string& email,
99 const std::string& display_email,
100 const std::string& refresh_token) {
101 content::WebContents* contents = NULL;
102 Browser* browser = NULL;
104 contents = handler_->web_ui()->GetWebContents();
105 browser = handler_->GetDesktopBrowser();
108 AboutSigninInternals* about_signin_internals =
109 AboutSigninInternalsFactory::GetForProfile(profile_);
110 about_signin_internals->OnRefreshTokenReceived("Successful");
112 signin::Source source = signin::GetSourceForPromoURL(current_url_);
113 if (source == signin::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT) {
114 ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)->
115 UpdateCredentials(email, refresh_token);
117 if (signin::IsAutoCloseEnabledInURL(current_url_)) {
118 // Close the gaia sign in tab via a task to make sure we aren't in the
119 // middle of any webui handler code.
120 base::MessageLoop::current()->PostTask(
122 base::Bind(&InlineLoginHandlerImpl::CloseTab,
126 ProfileSyncService* sync_service =
127 ProfileSyncServiceFactory::GetForProfile(profile_);
128 SigninErrorController* error_controller =
129 ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)->
130 signin_error_controller();
131 OneClickSigninSyncStarter::StartSyncMode start_mode =
132 source == signin::SOURCE_SETTINGS || choose_what_to_sync_ ?
133 (error_controller->HasError() &&
134 sync_service && sync_service->HasSyncSetupCompleted()) ?
135 OneClickSigninSyncStarter::SHOW_SETTINGS_WITHOUT_CONFIGURE :
136 OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST :
137 OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS;
138 OneClickSigninSyncStarter::ConfirmationRequired confirmation_required =
139 source == signin::SOURCE_SETTINGS ||
140 source == signin::SOURCE_WEBSTORE_INSTALL ||
141 choose_what_to_sync_ ?
142 OneClickSigninSyncStarter::NO_CONFIRMATION :
143 OneClickSigninSyncStarter::CONFIRM_AFTER_SIGNIN;
144 bool start_signin = true;
146 if (source != signin::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT) {
147 start_signin = !OneClickSigninHelper::HandleCrossAccountError(
149 email, password_, refresh_token,
150 OneClickSigninHelper::AUTO_ACCEPT_EXPLICIT,
152 base::Bind(&InlineLoginHandlerImpl::SyncStarterCallback,
157 // Call OneClickSigninSyncStarter to exchange oauth code for tokens.
158 // OneClickSigninSyncStarter will delete itself once the job is done.
159 new OneClickSigninSyncStarter(
161 email, password_, refresh_token,
164 confirmation_required,
165 signin::GetNextPageURLForPromoURL(current_url_),
166 base::Bind(&InlineLoginHandlerImpl::SyncStarterCallback, handler_));
170 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
173 void InlineSigninHelper::OnSigninOAuthInformationFailure(
174 const GoogleServiceAuthError& error) {
176 handler_->HandleLoginError(error.ToString());
178 AboutSigninInternals* about_signin_internals =
179 AboutSigninInternalsFactory::GetForProfile(profile_);
180 about_signin_internals->OnRefreshTokenReceived("Failure");
182 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
187 InlineLoginHandlerImpl::InlineLoginHandlerImpl()
188 : weak_factory_(this),
189 choose_what_to_sync_(false) {
192 InlineLoginHandlerImpl::~InlineLoginHandlerImpl() {}
194 void InlineLoginHandlerImpl::RegisterMessages() {
195 InlineLoginHandler::RegisterMessages();
197 web_ui()->RegisterMessageCallback("switchToFullTab",
198 base::Bind(&InlineLoginHandlerImpl::HandleSwitchToFullTabMessage,
199 base::Unretained(this)));
202 void InlineLoginHandlerImpl::SetExtraInitParams(base::DictionaryValue& params) {
203 params.SetInteger("authMode", InlineLoginHandler::kDesktopAuthMode);
205 const GURL& current_url = web_ui()->GetWebContents()->GetURL();
206 signin::Source source = signin::GetSourceForPromoURL(current_url);
207 if (source == signin::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT ||
208 source == signin::SOURCE_AVATAR_BUBBLE_SIGN_IN) {
209 // Drop the leading slash in the path.
210 params.SetString("gaiaPath",
211 GaiaUrls::GetInstance()->embedded_signin_url().path().substr(1));
214 params.SetString("service", "chromiumsync");
215 params.SetString("continueUrl",
216 signin::GetLandingURL("source", static_cast<int>(source)).spec());
218 std::string default_email;
219 if (source != signin::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT) {
220 default_email = Profile::FromWebUI(web_ui())->GetPrefs()->
221 GetString(prefs::kGoogleServicesLastUsername);
223 if (!net::GetValueForKeyInQuery(current_url, "email", &default_email))
224 default_email.clear();
226 if (!default_email.empty())
227 params.SetString("email", default_email);
229 std::string frame_url;
230 net::GetValueForKeyInQuery(current_url, "frameUrl", &frame_url);
231 if (!frame_url.empty())
232 params.SetString("frameUrl", frame_url);
234 std::string is_constrained;
235 net::GetValueForKeyInQuery(current_url, "constrained", &is_constrained);
236 if (!is_constrained.empty())
237 params.SetString("constrained", is_constrained);
239 // TODO(rogerta): this needs to be passed on to gaia somehow.
240 std::string read_only_email;
241 net::GetValueForKeyInQuery(current_url, "readOnlyEmail", &read_only_email);
242 if (!read_only_email.empty())
243 params.SetString("readOnlyEmail", read_only_email);
245 OneClickSigninHelper::LogHistogramValue(
246 source, one_click_signin::HISTOGRAM_SHOWN);
249 void InlineLoginHandlerImpl::HandleSwitchToFullTabMessage(
250 const base::ListValue* args) {
251 base::string16 url_str;
252 CHECK(args->GetString(0, &url_str));
254 content::WebContents* web_contents = web_ui()->GetWebContents();
255 GURL main_frame_url(web_contents->GetURL());
256 main_frame_url = net::AppendOrReplaceQueryParameter(
257 main_frame_url, "frameUrl", base::UTF16ToASCII(url_str));
258 chrome::NavigateParams params(
259 Profile::FromWebUI(web_ui()),
260 net::AppendOrReplaceQueryParameter(main_frame_url, "constrained", "0"),
261 content::PAGE_TRANSITION_AUTO_TOPLEVEL);
262 chrome::Navigate(¶ms);
264 web_ui()->CallJavascriptFunction("inline.login.closeDialog");
267 void InlineLoginHandlerImpl::CompleteLogin(const base::ListValue* args) {
268 content::WebContents* contents = web_ui()->GetWebContents();
269 const GURL& current_url = contents->GetURL();
271 const base::DictionaryValue* dict = NULL;
272 args->GetDictionary(0, &dict);
274 bool skip_for_now = false;
275 dict->GetBoolean("skipForNow", &skip_for_now);
277 signin::SetUserSkippedPromo(Profile::FromWebUI(web_ui()));
278 SyncStarterCallback(OneClickSigninSyncStarter::SYNC_SETUP_FAILURE);
282 base::string16 email;
283 dict->GetString("email", &email);
284 DCHECK(!email.empty());
285 email_ = base::UTF16ToASCII(email);
286 base::string16 password;
287 dict->GetString("password", &password);
288 password_ = base::UTF16ToASCII(password);
290 // When doing a SAML sign in, this email check may result in a false
291 // positive. This happens when the user types one email address in the
292 // gaia sign in page, but signs in to a different account in the SAML sign in
294 std::string default_email;
295 std::string validate_email;
296 if (net::GetValueForKeyInQuery(current_url, "email", &default_email) &&
297 net::GetValueForKeyInQuery(current_url, "validateEmail",
299 validate_email == "1") {
300 if (!gaia::AreEmailsSame(email_, default_email)) {
301 SyncStarterCallback(OneClickSigninSyncStarter::SYNC_SETUP_FAILURE);
306 base::string16 session_index;
307 dict->GetString("sessionIndex", &session_index);
308 session_index_ = base::UTF16ToASCII(session_index);
309 DCHECK(!session_index_.empty());
310 dict->GetBoolean("chooseWhatToSync", &choose_what_to_sync_);
312 signin::Source source = signin::GetSourceForPromoURL(current_url);
313 OneClickSigninHelper::LogHistogramValue(
314 source, one_click_signin::HISTOGRAM_ACCEPTED);
315 bool switch_to_advanced =
316 choose_what_to_sync_ && (source != signin::SOURCE_SETTINGS);
317 OneClickSigninHelper::LogHistogramValue(
319 switch_to_advanced ? one_click_signin::HISTOGRAM_WITH_ADVANCED :
320 one_click_signin::HISTOGRAM_WITH_DEFAULTS);
322 OneClickSigninHelper::CanOfferFor can_offer_for =
323 source == signin::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT ?
324 OneClickSigninHelper::CAN_OFFER_FOR_SECONDARY_ACCOUNT :
325 OneClickSigninHelper::CAN_OFFER_FOR_ALL;
326 std::string error_msg;
327 bool can_offer = OneClickSigninHelper::CanOffer(
328 contents, can_offer_for, email_, &error_msg);
330 HandleLoginError(error_msg);
334 AboutSigninInternals* about_signin_internals =
335 AboutSigninInternalsFactory::GetForProfile(Profile::FromWebUI(web_ui()));
336 about_signin_internals->OnAuthenticationResultReceived(
337 "GAIA Auth Successful");
339 content::StoragePartition* partition =
340 content::BrowserContext::GetStoragePartitionForSite(
341 contents->GetBrowserContext(),
342 GURL(chrome::kChromeUIChromeSigninURL));
344 // InlineSigninHelper will delete itself.
345 new InlineSigninHelper(GetWeakPtr(), partition->GetURLRequestContext(),
346 Profile::FromWebUI(web_ui()), current_url,
347 email_, password_, session_index_,
348 choose_what_to_sync_);
352 session_index_.clear();
353 web_ui()->CallJavascriptFunction("inline.login.closeDialog");
356 void InlineLoginHandlerImpl::HandleLoginError(const std::string& error_msg) {
357 SyncStarterCallback(OneClickSigninSyncStarter::SYNC_SETUP_FAILURE);
359 Browser* browser = GetDesktopBrowser();
360 if (browser && !error_msg.empty()) {
361 VLOG(1) << "InlineLoginHandlerImpl::HandleLoginError shows error message: "
363 OneClickSigninHelper::ShowSigninErrorBubble(browser, error_msg);
368 session_index_.clear();
371 Browser* InlineLoginHandlerImpl::GetDesktopBrowser() {
372 Browser* browser = chrome::FindBrowserWithWebContents(
373 web_ui()->GetWebContents());
375 browser = chrome::FindLastActiveWithProfile(
376 Profile::FromWebUI(web_ui()), chrome::GetActiveDesktop());
381 void InlineLoginHandlerImpl::SyncStarterCallback(
382 OneClickSigninSyncStarter::SyncSetupResult result) {
383 content::WebContents* contents = web_ui()->GetWebContents();
385 if (contents->GetController().GetPendingEntry()) {
386 // Do nothing if a navigation is pending, since this call can be triggered
387 // from DidStartLoading. This avoids deleting the pending entry while we are
388 // still navigating to it. See crbug/346632.
392 const GURL& current_url = contents->GetLastCommittedURL();
393 signin::Source source = signin::GetSourceForPromoURL(current_url);
394 bool auto_close = signin::IsAutoCloseEnabledInURL(current_url);
396 if (result == OneClickSigninSyncStarter::SYNC_SETUP_FAILURE) {
397 OneClickSigninHelper::RedirectToNtpOrAppsPage(contents, source);
398 } else if (auto_close) {
399 base::MessageLoop::current()->PostTask(
401 base::Bind(&InlineLoginHandlerImpl::CloseTab,
402 weak_factory_.GetWeakPtr()));
404 OneClickSigninHelper::RedirectToNtpOrAppsPageIfNecessary(contents, source);
408 void InlineLoginHandlerImpl::CloseTab() {
409 content::WebContents* tab = web_ui()->GetWebContents();
410 Browser* browser = chrome::FindBrowserWithWebContents(tab);
412 TabStripModel* tab_strip_model = browser->tab_strip_model();
413 if (tab_strip_model) {
414 int index = tab_strip_model->GetIndexOfWebContents(tab);
415 if (index != TabStripModel::kNoTab) {
416 tab_strip_model->ExecuteContextMenuCommand(
417 index, TabStripModel::CommandCloseTab);