// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/in_session_auth/in_session_auth_dialog_controller_impl.h"
#include <memory>
#include <optional>
#include <utility>
#include "ash/constants/ash_features.h"
#include "ash/in_session_auth/authentication_dialog.h"
#include "ash/in_session_auth/in_session_auth_dialog_contents_view.h"
#include "ash/public/cpp/auth/active_session_auth_controller.h"
#include "ash/public/cpp/in_session_auth_dialog_controller.h"
#include "ash/public/cpp/in_session_auth_token_provider.h"
#include "ash/public/cpp/webauthn_dialog_controller.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/notimplemented.h"
#include "base/time/time.h"
#include "chromeos/ash/components/auth_panel/impl/auth_factor_store.h"
#include "chromeos/ash/components/auth_panel/impl/auth_panel.h"
#include "chromeos/ash/components/auth_panel/public/shared_types.h"
#include "chromeos/ash/components/cryptohome/constants.h"
#include "chromeos/ash/components/dbus/userdataauth/userdataauth_client.h"
#include "chromeos/ash/components/login/auth/auth_performer.h"
#include "chromeos/ash/components/osauth/impl/legacy_auth_surface_registry.h"
#include "chromeos/ash/components/osauth/impl/request/password_manager_auth_request.h"
#include "chromeos/ash/components/osauth/impl/request/settings_auth_request.h"
#include "chromeos/ash/components/osauth/public/auth_factor_status_consumer.h"
#include "chromeos/ash/components/osauth/public/auth_hub.h"
#include "chromeos/ash/components/osauth/public/common_types.h"
#include "chromeos/components/webauthn/webauthn_request_registrar.h"
#include "ui/base/mojom/ui_base_types.mojom-shared.h"
#include "ui/base/ui_base_types.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget_delegate.h"
namespace ash {
namespace {
AuthPurpose InSessionAuthReasonToAuthPurpose(
InSessionAuthDialogController::Reason reason) {
switch (reason) {
case InSessionAuthDialogController::Reason::kAccessPasswordManager:
case InSessionAuthDialogController::Reason::kAccessMultideviceSettings:
return AuthPurpose::kUserVerification;
case InSessionAuthDialogController::Reason::kAccessAuthenticationSettings:
return AuthPurpose::kAuthSettings;
}
}
std::unique_ptr<views::Widget> CreateAuthDialogWidget(
std::unique_ptr<views::View> contents_view) {
views::Widget::InitParams params(
views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET,
views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
params.delegate = new views::WidgetDelegate();
params.show_state = ui::SHOW_STATE_NORMAL;
params.parent = nullptr;
params.name = "AuthDialogWidget";
params.delegate->SetInitiallyFocusedView(contents_view.get());
params.delegate->SetModalType(ui::mojom::ModalType::kSystem);
params.delegate->SetOwnedByWidget(true);
std::unique_ptr<views::Widget> widget = std::make_unique<views::Widget>();
widget->Init(std::move(params));
widget->SetVisibilityAnimationTransition(views::Widget::ANIMATE_NONE);
widget->SetContentsView(std::move(contents_view));
return widget;
}
// TODO(b/271248452): Subscribe to primary display changes, so that the
// authentication dialog correctly changes its location to center on new
// primary displays. We will need to also listen to `work_area` changes and
// reposition the dialog accordingly when that changes.
void CenterWidgetOnPrimaryDisplay(views::Widget* widget) {
auto bounds = display::Screen::GetScreen()->GetPrimaryDisplay().work_area();
bounds.ClampToCenteredSize(widget->GetContentsView()->GetPreferredSize());
widget->SetBounds(bounds);
}
} // namespace
InSessionAuthDialogControllerImpl::InSessionAuthDialogControllerImpl() =
default;
InSessionAuthDialogControllerImpl::~InSessionAuthDialogControllerImpl() =
default;
void InSessionAuthDialogControllerImpl::CreateAndShowAuthPanel(
const std::optional<std::string>& prompt,
auth_panel::AuthCompletionCallback on_auth_complete,
Reason reason,
const AccountId& account_id) {
state_ = State::kShowing;
on_auth_complete_ = std::move(on_auth_complete);
prompt_ = prompt;
auto* auth_hub = AuthHub::Get();
auto continuation = base::BindOnce(
&AuthHub::StartAuthentication, base::Unretained(auth_hub), account_id,
InSessionAuthReasonToAuthPurpose(reason), this);
auth_hub->EnsureInitialized(std::move(continuation));
}
void InSessionAuthDialogControllerImpl::ShowAuthDialog(
Reason reason,
const std::optional<std::string>& prompt,
auth_panel::AuthCompletionCallback on_auth_complete) {
if (state_ != State::kNotShown) {
LOG(ERROR) << "Trying to show authentication dialog in session while "
"another is currently active, returning";
std::move(on_auth_complete)
.Run(false, ash::AuthProofToken{}, base::TimeDelta{});
return;
}
auto account_id = Shell::Get()->session_controller()->GetActiveAccountId();
DCHECK(account_id.is_valid());
DCHECK_NE(auth_token_provider_, nullptr);
if (reason == Reason::kAccessPasswordManager &&
features::IsUseAuthPanelInSessionEnabled()) {
Shell::Get()->active_session_auth_controller()->ShowAuthDialog(
std::make_unique<PasswordManagerAuthRequest>(
std::move(on_auth_complete)));
} else if (reason == Reason::kAccessAuthenticationSettings &&
features::IsUseAuthPanelInSessionEnabled()) {
Shell::Get()->active_session_auth_controller()->ShowAuthDialog(
std::make_unique<SettingsAuthRequest>(std::move(on_auth_complete)));
} else {
// We don't manage the lifetime of `AuthenticationDialog` here.
// `AuthenticatonDialog` is-a View and it is instead owned by it's widget,
// which would properly delete it when the widget is closed.
(new AuthenticationDialog(
std::move(on_auth_complete), auth_token_provider_,
std::make_unique<AuthPerformer>(UserDataAuthClient::Get()),
account_id))
->Show();
}
}
void InSessionAuthDialogControllerImpl::ShowLegacyWebAuthnDialog(
const std::string& rp_id,
const std::string& window_id,
WebAuthNDialogController::FinishCallback on_auth_complete) {
aura::Window* source_window =
chromeos::webauthn::WebAuthnRequestRegistrar::Get()
->GetWindowForRequestId(window_id);
if (!source_window) {
LOG(ERROR) << "Can't find the window for the given window id.";
std::move(on_auth_complete).Run(false);
return;
}
WebAuthNDialogController::Get()->ShowAuthenticationDialog(
source_window, rp_id, std::move(on_auth_complete));
}
void InSessionAuthDialogControllerImpl::SetTokenProvider(
InSessionAuthTokenProvider* auth_token_provider) {
auth_token_provider_ = auth_token_provider;
}
void InSessionAuthDialogControllerImpl::OnUserAuthAttemptRejected() {
NOTIMPLEMENTED();
}
void InSessionAuthDialogControllerImpl::OnUserAuthAttemptConfirmed(
AuthHubConnector* connector,
raw_ptr<AuthFactorStatusConsumer>& out_consumer) {
CHECK_EQ(state_, State::kShowing);
CHECK_EQ(contents_view_, nullptr);
auto contents_view = std::make_unique<InSessionAuthDialogContentsView>(
prompt_,
base::BindOnce(&InSessionAuthDialogControllerImpl::OnEndAuthentication,
weak_factory_.GetWeakPtr()),
base::BindRepeating(
&InSessionAuthDialogControllerImpl::OnAuthPanelPreferredSizeChanged,
weak_factory_.GetWeakPtr()),
connector, AuthHub::Get());
contents_view_ = contents_view.get();
out_consumer = contents_view->GetAuthPanel();
dialog_ = CreateAuthDialogWidget(std::move(contents_view));
dialog_->Show();
state_ = State::kShown;
AuthParts::Get()
->GetLegacyAuthSurfaceRegistry()
->NotifyInSessionAuthDialogShown(connector);
}
void InSessionAuthDialogControllerImpl::OnAuthPanelPreferredSizeChanged() {
CenterWidgetOnPrimaryDisplay(dialog_.get());
}
void InSessionAuthDialogControllerImpl::OnAccountNotFound() {
NOTIMPLEMENTED();
}
void InSessionAuthDialogControllerImpl::OnUserAuthAttemptCancelled() {
NotifyFailure();
OnEndAuthentication();
}
void InSessionAuthDialogControllerImpl::OnFactorAttemptFailed(
AshAuthFactor factor) {
contents_view_->ShowAuthError(factor);
}
void InSessionAuthDialogControllerImpl::NotifySuccess(
const AuthProofToken& token) {
if (!on_auth_complete_) {
LOG(ERROR) << "Encountered null auth completion callback, possible double "
"invocation?";
return;
}
std::move(on_auth_complete_)
.Run(true, token, cryptohome::kAuthsessionInitialLifetime);
}
void InSessionAuthDialogControllerImpl::NotifyFailure() {
if (on_auth_complete_) {
std::move(on_auth_complete_)
.Run(false, /*token=*/{},
/*timeout=*/{});
}
}
void InSessionAuthDialogControllerImpl::OnUserAuthSuccess(
AshAuthFactor factor,
const AuthProofToken& token) {
NotifySuccess(token);
}
void InSessionAuthDialogControllerImpl::OnEndAuthentication() {
contents_view_ = nullptr;
dialog_.reset();
state_ = State::kNotShown;
}
} // namespace ash