Home | History | Annotate | Download | only in signin
      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 
     44 namespace {
     45 
     46 class 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 
     82 InlineSigninHelper::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 
    107 void 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 
    209 void 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 
    223 InlineLoginHandlerImpl::InlineLoginHandlerImpl()
    224       : confirm_untrusted_signin_(false),
    225         weak_factory_(this) {
    226 }
    227 
    228 InlineLoginHandlerImpl::~InlineLoginHandlerImpl() {}
    229 
    230 bool InlineLoginHandlerImpl::HandleContextMenu(
    231     const content::ContextMenuParams& params) {
    232 #ifndef NDEBUG
    233   return false;
    234 #else
    235   return true;
    236 #endif
    237 }
    238 
    239 void 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 
    266 void 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 
    283 void 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 
    392 void 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 
    402 Browser* 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 
    412 void 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 
    440 void 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