blob: f485eeff4693f9e02d1f25ea372d5e78c70ca9f7 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/signin/process_dice_header_delegate_impl.h"
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/dice_tab_helper.h"
#include "chrome/browser/signin/dice_web_signin_interceptor.h"
#include "chrome/browser/signin/dice_web_signin_interceptor_factory.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/ui/webui/signin/signin_ui_error.h"
#include "components/signin/public/base/signin_metrics.h"
#include "components/signin/public/base/signin_switches.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/signin/public/identity_manager/primary_account_mutator.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/web_contents.h"
#include "google_apis/gaia/core_account_id.h"
#include "url/gurl.h"
namespace {
// Helper function similar to DiceTabHelper::FromWebContents(), but also handles
// the case where |contents| is nullptr.
DiceTabHelper* GetDiceTabHelperFromWebContents(content::WebContents* contents) {
if (!contents) {
return nullptr;
}
return DiceTabHelper::FromWebContents(contents);
}
// Should Sign in to Chrome for all access points when Uno is enabled. Except
// for Web Signin where we first check the user choice first on whether to
// automatically sign in or not.
void AttemptChromeSignin(CoreAccountId account_id,
Profile& profile,
signin_metrics::AccessPoint access_point) {
CHECK(!account_id.empty());
// For the non-ExplicitBrowserSignin equivalent counterpart, the code takes
// care of in `SigninManager::UpdateUnconsentedPrimaryAccount()`.
if (!switches::IsExplicitBrowserSigninUIOnDesktopEnabled()) {
return;
}
// Do not sign in if the access point is unknown.
if (access_point == signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN) {
return;
}
signin::IdentityManager* identity_manager =
IdentityManagerFactory::GetForProfile(&profile);
if (access_point == signin_metrics::AccessPoint::ACCESS_POINT_WEB_SIGNIN) {
if (switches::IsExplicitBrowserSigninUIOnDesktopEnabled()) {
AccountInfo account_info =
identity_manager->FindExtendedAccountInfoByAccountId(account_id);
// If the user did not choose the signin choice, do not proceed with a
// sign in from a Web Signin.
if (SigninPrefs(*profile.GetPrefs())
.GetChromeSigninInterceptionUserChoice(account_info.gaia) !=
ChromeSigninUserChoice::kSignin) {
return;
}
// Proceed with the access point as the choice remembered.
access_point =
signin_metrics::AccessPoint::ACCESS_POINT_SIGNIN_CHOICE_REMEMBERED;
}
}
// This access point should only be used as a result of a non Uno flow.
CHECK_NE(signin_metrics::AccessPoint::ACCESS_POINT_DESKTOP_SIGNIN_MANAGER,
access_point);
if (!identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSignin)) {
base::UmaHistogramEnumeration(
"Signin.SigninManager.SigninAccessPoint", access_point,
signin_metrics::AccessPoint::ACCESS_POINT_MAX);
identity_manager->GetPrimaryAccountMutator()->SetPrimaryAccount(
account_id, signin::ConsentLevel::kSignin, access_point);
}
}
} // namespace
// static
std::unique_ptr<ProcessDiceHeaderDelegateImpl>
ProcessDiceHeaderDelegateImpl::Create(content::WebContents* web_contents) {
bool is_sync_signin_tab = false;
signin_metrics::AccessPoint access_point =
signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN;
signin_metrics::PromoAction promo_action =
signin_metrics::PromoAction::PROMO_ACTION_NO_SIGNIN_PROMO;
GURL redirect_url;
EnableSyncCallback enable_sync_callback;
OnSigninHeaderReceived on_signin_header_received;
ShowSigninErrorCallback show_signin_error_callback;
DiceTabHelper* tab_helper = DiceTabHelper::FromWebContents(web_contents);
if (tab_helper) {
is_sync_signin_tab = tab_helper->IsSyncSigninInProgress();
redirect_url = tab_helper->redirect_url();
access_point = tab_helper->signin_access_point();
promo_action = tab_helper->signin_promo_action();
// `show_signin_error_callback` may be null if the `DiceTabHelper` was reset
// after completion of a signin flow.
show_signin_error_callback =
std::move(tab_helper->GetShowSigninErrorCallback());
if (is_sync_signin_tab) {
enable_sync_callback = tab_helper->GetEnableSyncCallback();
}
on_signin_header_received = tab_helper->GetOnSigninHeaderReceived();
} else {
access_point = signin_metrics::AccessPoint::ACCESS_POINT_WEB_SIGNIN;
}
// If there is no active `DiceTabHelper`, default to the in-browser error
// callback. This callback does nothing if there is no browser open.
if (!show_signin_error_callback) {
show_signin_error_callback =
DiceTabHelper::GetShowSigninErrorCallbackForBrowser();
}
return std::make_unique<ProcessDiceHeaderDelegateImpl>(
web_contents, is_sync_signin_tab, access_point, promo_action,
std::move(redirect_url), std::move(enable_sync_callback),
std::move(on_signin_header_received),
std::move(show_signin_error_callback));
}
ProcessDiceHeaderDelegateImpl::ProcessDiceHeaderDelegateImpl(
content::WebContents* web_contents,
bool is_sync_signin_tab,
signin_metrics::AccessPoint access_point,
signin_metrics::PromoAction promo_action,
GURL redirect_url,
EnableSyncCallback enable_sync_callback,
OnSigninHeaderReceived on_signin_header_received,
ShowSigninErrorCallback show_signin_error_callback)
: web_contents_(web_contents->GetWeakPtr()),
profile_(raw_ref<Profile>::from_ptr(
Profile::FromBrowserContext(web_contents->GetBrowserContext()))),
is_sync_signin_tab_(is_sync_signin_tab),
access_point_(access_point),
promo_action_(promo_action),
redirect_url_(std::move(redirect_url)),
enable_sync_callback_(std::move(enable_sync_callback)),
on_signin_header_received_(std::move(on_signin_header_received)),
show_signin_error_callback_(std::move(show_signin_error_callback)) {
DCHECK_EQ(!is_sync_signin_tab_, enable_sync_callback_.is_null());
DCHECK(show_signin_error_callback_);
}
ProcessDiceHeaderDelegateImpl::~ProcessDiceHeaderDelegateImpl() = default;
bool ProcessDiceHeaderDelegateImpl::ShouldEnableSync() {
if (IdentityManagerFactory::GetForProfile(&profile_.get())
->HasPrimaryAccount(signin::ConsentLevel::kSync)) {
VLOG(1) << "Do not start sync after web sign-in [already authenticated].";
return false;
}
if (!is_sync_signin_tab_) {
VLOG(1)
<< "Do not start sync after web sign-in [not a Chrome sign-in tab].";
return false;
}
if (!enable_sync_callback_) {
VLOG(1)
<< "Do not start sync after web sign-in [no sync flow in progress].";
return false;
}
return true;
}
void ProcessDiceHeaderDelegateImpl::HandleTokenExchangeSuccess(
CoreAccountId account_id,
bool is_new_account) {
AttemptChromeSignin(account_id, profile_.get(), access_point_);
// is_sync_signin_tab_ tells whether the current signin is happening in a tab
// that was opened from a "Enable Sync" Chrome UI. Usually this is indeed a
// sync signin, but it is not always the case: the user may abandon the sync
// signin and do a simple web signin in the same tab instead.
DiceWebSigninInterceptorFactory::GetForProfile(&profile_.get())
->MaybeInterceptWebSignin(web_contents_.get(), account_id, access_point_,
is_new_account, is_sync_signin_tab_);
}
void ProcessDiceHeaderDelegateImpl::EnableSync(
const CoreAccountInfo& account_info) {
content::WebContents* web_contents = web_contents_.get();
DiceTabHelper* tab_helper = GetDiceTabHelperFromWebContents(web_contents);
if (tab_helper) {
tab_helper->OnSyncSigninFlowComplete();
}
if (!ShouldEnableSync()) {
// No special treatment is needed if the user is not enabling sync.
return;
}
VLOG(1) << "Start sync after web sign-in.";
std::move(enable_sync_callback_)
.Run(&profile_.get(), access_point_, promo_action_, web_contents,
account_info);
Redirect();
}
void ProcessDiceHeaderDelegateImpl::HandleTokenExchangeFailure(
const std::string& email,
const GoogleServiceAuthError& error) {
DCHECK_NE(GoogleServiceAuthError::NONE, error.state());
content::WebContents* web_contents = web_contents_.get();
DiceTabHelper* tab_helper = GetDiceTabHelperFromWebContents(web_contents);
if (tab_helper) {
tab_helper->OnSyncSigninFlowComplete();
}
if (ShouldEnableSync()) {
Redirect();
}
// Show the error even if the WebContents was closed, because the user may be
// signed out of the web.
std::move(show_signin_error_callback_)
.Run(&profile_.get(), web_contents,
SigninUIError::FromGoogleServiceAuthError(email, error));
}
signin_metrics::AccessPoint ProcessDiceHeaderDelegateImpl::GetAccessPoint() {
return access_point_;
}
void ProcessDiceHeaderDelegateImpl::OnDiceSigninHeaderReceived() {
// TODO(b/303612320): The check for the `DiceTabHelper` here is needed since
// this is where we are getting the callback from and we will be redirected
// when calling it.
//
// We should cut down this dependency by not depending directly on the
// `DiceTabHelper` callback (this class receives a copy of the callback
// through the constructor) but rather providing an intermediate callback that
// would redirect to the proper one. This way the dependency would be direct
// with `ProcessDiceHeaderDelegateImpl` and then from
// `ProcessDiceHeaderDelegateImpl` to the `DiceTabHelper`.
//
// This should be done for the 3 callbacks in this class:
// `EnableSyncCallback`, `ShowSigninErrorCallback` and
// `OnSigninHeaderReceived`.
DiceTabHelper* tab_helper =
GetDiceTabHelperFromWebContents(web_contents_.get());
if (!tab_helper) {
return;
}
if (on_signin_header_received_) {
std::move(on_signin_header_received_).Run();
}
}
void ProcessDiceHeaderDelegateImpl::Redirect() {
content::WebContents* web_contents = web_contents_.get();
if (!web_contents || redirect_url_.is_empty()) {
return;
}
DCHECK(redirect_url_.is_valid()) << "Invalid redirect url: " << redirect_url_;
web_contents->GetController().LoadURL(redirect_url_, content::Referrer(),
ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
std::string());
}