| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/signin_reauth_view_controller.h" |
| |
| #include <memory> |
| #include <string> |
| |
| #include "base/feature_list.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/notreached.h" |
| #include "base/optional.h" |
| #include "base/task_runner.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/consent_auditor/consent_auditor_factory.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/signin/reauth_result.h" |
| #include "chrome/browser/signin/reauth_tab_helper.h" |
| #include "chrome/browser/signin/signin_features.h" |
| #include "chrome/browser/signin/signin_ui_util.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_navigator_params.h" |
| #include "chrome/browser/ui/webui/signin/signin_reauth_ui.h" |
| #include "components/consent_auditor/consent_auditor.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/site_instance.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_observer.h" |
| #include "content/public/common/web_preferences.h" |
| #include "google_apis/gaia/gaia_urls.h" |
| #include "third_party/blink/public/common/css/preferred_color_scheme.h" |
| |
| namespace { |
| |
| class ReauthWebContentsObserver : public content::WebContentsObserver { |
| public: |
| ReauthWebContentsObserver(content::WebContents* web_contents, |
| SigninReauthViewController* delegate); |
| |
| void DidFinishNavigation( |
| content::NavigationHandle* navigation_handle) override; |
| |
| private: |
| SigninReauthViewController* const delegate_; |
| }; |
| |
| ReauthWebContentsObserver::ReauthWebContentsObserver( |
| content::WebContents* web_contents, |
| SigninReauthViewController* delegate) |
| : WebContentsObserver(web_contents), delegate_(delegate) {} |
| |
| void ReauthWebContentsObserver::DidFinishNavigation( |
| content::NavigationHandle* navigation_handle) { |
| delegate_->OnGaiaReauthPageNavigated(); |
| } |
| |
| } // namespace |
| |
| SigninReauthViewController::SigninReauthViewController( |
| Browser* browser, |
| const CoreAccountId& account_id, |
| signin_metrics::ReauthAccessPoint access_point, |
| base::OnceCallback<void(signin::ReauthResult)> reauth_callback) |
| : browser_(browser), |
| account_id_(account_id), |
| access_point_(access_point), |
| reauth_callback_(std::move(reauth_callback)) { |
| // Show the confirmation dialog unconditionally for now. We may decide to only |
| // show it in some cases in the future. |
| ShowReauthConfirmationDialog(); |
| |
| // Navigate to the Gaia reauth challenge page in background. |
| reauth_web_contents_ = |
| content::WebContents::Create(content::WebContents::CreateParams( |
| browser_->profile(), |
| content::SiteInstance::Create(browser_->profile()))); |
| const GURL& reauth_url = GaiaUrls::GetInstance()->reauth_url(); |
| reauth_web_contents_->GetController().LoadURL( |
| reauth_url, content::Referrer(), ui::PAGE_TRANSITION_AUTO_TOPLEVEL, |
| std::string()); |
| signin::ReauthTabHelper::CreateForWebContents( |
| reauth_web_contents_.get(), reauth_url, false, |
| base::BindOnce(&SigninReauthViewController::OnGaiaReauthPageComplete, |
| weak_ptr_factory_.GetWeakPtr())); |
| reauth_web_contents_observer_ = std::make_unique<ReauthWebContentsObserver>( |
| reauth_web_contents_.get(), this); |
| } |
| |
| SigninReauthViewController::~SigninReauthViewController() { |
| for (auto& observer : observer_list_) |
| observer.OnReauthControllerDestroyed(); |
| } |
| |
| void SigninReauthViewController::CloseModalSignin() { |
| CompleteReauth(signin::ReauthResult::kCancelled); |
| } |
| |
| void SigninReauthViewController::ResizeNativeView(int height) { |
| NOTIMPLEMENTED(); |
| } |
| |
| content::WebContents* SigninReauthViewController::GetWebContents() { |
| // If the dialog is displayed, return its WebContents. |
| if (dialog_delegate_) |
| return dialog_delegate_->GetWebContents(); |
| |
| // Return contents of the SAML flow, if exist. |
| return raw_reauth_web_contents_; |
| } |
| |
| void SigninReauthViewController::SetWebContents( |
| content::WebContents* web_contents) { |
| NOTIMPLEMENTED(); |
| } |
| |
| void SigninReauthViewController::OnModalSigninClosed() { |
| dialog_delegate_observer_.Remove(dialog_delegate_); |
| dialog_delegate_ = nullptr; |
| |
| DCHECK(ui_state_ == UIState::kConfirmationDialog || |
| ui_state_ == UIState::kGaiaReauthDialog); |
| UserAction action = ui_state_ == UIState::kConfirmationDialog |
| ? UserAction::kCloseConfirmationDialog |
| : UserAction::kCloseGaiaReauthDialog; |
| signin_ui_util::RecordTransactionalReauthUserAction(access_point_, action); |
| |
| CompleteReauth(signin::ReauthResult::kDismissedByUser); |
| } |
| |
| void SigninReauthViewController::OnReauthConfirmed( |
| sync_pb::UserConsentTypes::AccountPasswordsConsent consent) { |
| if (user_confirmed_reauth_) |
| return; |
| |
| // Cache the consent. It will be actually recorded later, in CompleteReauth(), |
| // if the user successfully completed the reauth. |
| consent_ = consent; |
| |
| user_confirmed_reauth_ = true; |
| user_confirmed_reauth_time_ = base::TimeTicks::Now(); |
| OnStateChanged(); |
| } |
| |
| void SigninReauthViewController::OnReauthDismissed() { |
| RecordClickOnce(UserAction::kClickCancelButton); |
| CompleteReauth(signin::ReauthResult::kDismissedByUser); |
| } |
| |
| void SigninReauthViewController::OnGaiaReauthPageNavigated() { |
| if (gaia_reauth_page_state_ >= GaiaReauthPageState::kNavigated) |
| return; |
| |
| signin::ReauthTabHelper* tab_helper = GetReauthTabHelper(); |
| DCHECK(tab_helper); |
| OnGaiaReauthTypeDetermined(tab_helper->is_within_reauth_origin() |
| ? GaiaReauthType::kEmbeddedFlow |
| : GaiaReauthType::kSAMLFlow); |
| RecordGaiaNavigationDuration(); |
| gaia_reauth_page_state_ = GaiaReauthPageState::kNavigated; |
| OnStateChanged(); |
| } |
| |
| void SigninReauthViewController::OnGaiaReauthPageComplete( |
| signin::ReauthResult result) { |
| // Should be called only once. |
| DCHECK(gaia_reauth_page_state_ < GaiaReauthPageState::kDone); |
| DCHECK(!gaia_reauth_page_result_); |
| // |kNavigated| state will be skipped if the first navigation completes Gaia |
| // reauth. |
| if (gaia_reauth_page_state_ < GaiaReauthPageState::kNavigated) { |
| OnGaiaReauthTypeDetermined(GaiaReauthType::kAutoApproved); |
| RecordGaiaNavigationDuration(); |
| } |
| gaia_reauth_page_state_ = GaiaReauthPageState::kDone; |
| gaia_reauth_page_result_ = result; |
| |
| if (ui_state_ == UIState::kGaiaReauthDialog || |
| ui_state_ == UIState::kGaiaReauthTab) { |
| base::Optional<UserAction> action; |
| if (gaia_reauth_page_result_ == signin::ReauthResult::kSuccess) { |
| action = UserAction::kPassGaiaReauth; |
| } |
| if (gaia_reauth_page_result_ == signin::ReauthResult::kDismissedByUser) { |
| action = ui_state_ == UIState::kGaiaReauthDialog |
| ? UserAction::kCloseGaiaReauthDialog |
| : UserAction::kCloseGaiaReauthTab; |
| } |
| |
| if (action) { |
| signin_ui_util::RecordTransactionalReauthUserAction(access_point_, |
| *action); |
| } |
| } |
| |
| OnStateChanged(); |
| } |
| |
| void SigninReauthViewController::AddObserver(Observer* observer) { |
| observer_list_.AddObserver(observer); |
| } |
| |
| void SigninReauthViewController::RemoveObserver(Observer* observer) { |
| observer_list_.RemoveObserver(observer); |
| } |
| |
| void SigninReauthViewController::CompleteReauth(signin::ReauthResult result) { |
| signin::ReauthTabHelper* tab_helper = GetReauthTabHelper(); |
| if (tab_helper && tab_helper->has_last_committed_error_page() && |
| result != signin::ReauthResult::kSuccess && |
| (ui_state_ == UIState::kGaiaReauthDialog || |
| ui_state_ == UIState::kGaiaReauthTab)) { |
| // Override a non-successful result with |kLoadFailed| if the error page was |
| // last displayed to the user. |
| result = signin::ReauthResult::kLoadFailed; |
| } |
| |
| if (dialog_delegate_) { |
| dialog_delegate_observer_.Remove(dialog_delegate_); |
| dialog_delegate_->CloseModalSignin(); |
| dialog_delegate_ = nullptr; |
| } |
| |
| if (raw_reauth_web_contents_) { |
| if (!raw_reauth_web_contents_->IsBeingDestroyed()) |
| raw_reauth_web_contents_->ClosePage(); |
| raw_reauth_web_contents_ = nullptr; |
| } |
| |
| if (result == signin::ReauthResult::kSuccess) { |
| CHECK(consent_.has_value()); |
| ConsentAuditorFactory::GetForProfile(browser_->profile()) |
| ->RecordAccountPasswordsConsent(account_id_, *consent_); |
| } |
| |
| signin_ui_util::RecordTransactionalReauthResult(access_point_, result); |
| if (reauth_callback_) |
| std::move(reauth_callback_).Run(result); |
| |
| NotifyModalSigninClosed(); |
| |
| // Schedules an asynchronous deletion of the current instance. |
| // We cannot destroy |this| and in particular |reauth_web_contents_| right now |
| // because this function can be triggered from |reauth_web_contents_|'s |
| // observer method. |
| content::GetUIThreadTaskRunner({})->DeleteSoon(FROM_HERE, this); |
| } |
| |
| void SigninReauthViewController::OnStateChanged() { |
| if (user_confirmed_reauth_ && |
| gaia_reauth_page_state_ == GaiaReauthPageState::kNavigated) { |
| RecordClickOnce(UserAction::kClickNextButton); |
| ShowGaiaReauthPage(); |
| return; |
| } |
| |
| if (user_confirmed_reauth_ && |
| gaia_reauth_page_state_ == GaiaReauthPageState::kDone) { |
| DCHECK(gaia_reauth_page_result_); |
| RecordClickOnce(UserAction::kClickConfirmButton); |
| CompleteReauth(*gaia_reauth_page_result_); |
| return; |
| } |
| } |
| |
| void SigninReauthViewController::OnGaiaReauthTypeDetermined( |
| GaiaReauthType reauth_type) { |
| DCHECK_EQ(gaia_reauth_type_, GaiaReauthType::kUnknown); |
| DCHECK_NE(reauth_type, GaiaReauthType::kUnknown); |
| gaia_reauth_type_ = reauth_type; |
| for (auto& observer : observer_list_) |
| observer.OnGaiaReauthTypeDetermined(reauth_type); |
| } |
| |
| void SigninReauthViewController::RecordClickOnce(UserAction click_action) { |
| if (has_recorded_click_) |
| return; |
| |
| signin_ui_util::RecordTransactionalReauthUserAction(access_point_, |
| click_action); |
| has_recorded_click_ = true; |
| } |
| |
| signin::ReauthTabHelper* SigninReauthViewController::GetReauthTabHelper() { |
| content::WebContents* web_contents = reauth_web_contents_ |
| ? reauth_web_contents_.get() |
| : raw_reauth_web_contents_; |
| if (!web_contents) |
| return nullptr; |
| |
| return signin::ReauthTabHelper::FromWebContents(web_contents); |
| } |
| |
| void SigninReauthViewController::RecordGaiaNavigationDuration() { |
| base::TimeTicks navigation_time = base::TimeTicks::Now(); |
| |
| base::UmaHistogramTimes( |
| "Signin.TransactionalReauthGaiaNavigationDuration.FromReauthStart", |
| navigation_time - reauth_start_time_); |
| base::UmaHistogramTimes( |
| "Signin.TransactionalReauthGaiaNavigationDuration.FromConfirmClick", |
| navigation_time - user_confirmed_reauth_time_); |
| } |
| |
| void SigninReauthViewController::ShowReauthConfirmationDialog() { |
| DCHECK_EQ(ui_state_, UIState::kNone); |
| ui_state_ = UIState::kConfirmationDialog; |
| dialog_delegate_ = |
| SigninViewControllerDelegate::CreateReauthConfirmationDelegate( |
| browser_, account_id_, access_point_); |
| dialog_delegate_observer_.Add(dialog_delegate_); |
| |
| // Gaia Reauth page doesn't support dark mode. Force the confirmation dialog |
| // to use the light mode as well to match the style. |
| auto* web_contents = dialog_delegate_->GetWebContents(); |
| auto prefs = web_contents->GetOrCreateWebPreferences(); |
| prefs.preferred_color_scheme = blink::PreferredColorScheme::kLight; |
| web_contents->SetWebPreferences(prefs); |
| |
| SigninReauthUI* web_dialog_ui = |
| web_contents->GetWebUI()->GetController()->GetAs<SigninReauthUI>(); |
| web_dialog_ui->InitializeMessageHandlerWithReauthController(this); |
| } |
| |
| void SigninReauthViewController::ShowGaiaReauthPage() { |
| if (gaia_reauth_type_ == GaiaReauthType::kEmbeddedFlow) { |
| ShowGaiaReauthPageInDialog(); |
| } else { |
| // This corresponds to a SAML account. |
| DCHECK_EQ(gaia_reauth_type_, GaiaReauthType::kSAMLFlow); |
| ShowGaiaReauthPageInNewTab(); |
| } |
| |
| for (auto& observer : observer_list_) |
| observer.OnGaiaReauthPageShown(); |
| } |
| |
| void SigninReauthViewController::ShowGaiaReauthPageInDialog() { |
| DCHECK_EQ(ui_state_, UIState::kConfirmationDialog); |
| ui_state_ = UIState::kGaiaReauthDialog; |
| dialog_delegate_->SetWebContents(reauth_web_contents_.get()); |
| } |
| |
| void SigninReauthViewController::ShowGaiaReauthPageInNewTab() { |
| DCHECK_EQ(ui_state_, UIState::kConfirmationDialog); |
| ui_state_ = UIState::kGaiaReauthTab; |
| // Remove the observer to not trigger OnModalSigninClosed() that will abort |
| // the reauth flow. |
| dialog_delegate_observer_.Remove(dialog_delegate_); |
| dialog_delegate_->CloseModalSignin(); |
| dialog_delegate_ = nullptr; |
| |
| raw_reauth_web_contents_ = reauth_web_contents_.get(); |
| NavigateParams nav_params(browser_, std::move(reauth_web_contents_)); |
| nav_params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB; |
| nav_params.window_action = NavigateParams::SHOW_WINDOW; |
| nav_params.trusted_source = false; |
| nav_params.user_gesture = true; |
| nav_params.tabstrip_add_types |= TabStripModel::ADD_INHERIT_OPENER; |
| Navigate(&nav_params); |
| } |