// Copyright 2020 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/ui/ash/in_session_auth/in_session_auth_dialog_client.h"
#include <utility>
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/webauthn_dialog_controller.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ash/auth/cryptohome_pin_engine.h"
#include "chrome/browser/ash/auth/legacy_fingerprint_engine.h"
#include "chrome/browser/ash/login/quick_unlock/fingerprint_storage.h"
#include "chrome/browser/ash/login/quick_unlock/pin_backend.h"
#include "chrome/browser/ash/login/quick_unlock/quick_unlock_factory.h"
#include "chrome/browser/ash/login/quick_unlock/quick_unlock_storage.h"
#include "chrome/browser/ash/login/quick_unlock/quick_unlock_utils.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/browser_navigator_params.h"
#include "chrome/browser/ui/browser_window.h"
#include "chromeos/ash/components/cryptohome/common_types.h"
#include "chromeos/ash/components/dbus/userdataauth/userdataauth_client.h"
#include "chromeos/ash/components/login/auth/auth_performer.h"
#include "chromeos/ash/components/login/auth/public/auth_session_intent.h"
#include "components/account_id/account_id.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
using ::ash::AuthenticationError;
using ::ash::AuthStatusConsumer;
using ::ash::Key;
using ::ash::UserContext;
namespace {
const char kInSessionAuthHelpPageUrl[] =
"https://support.google.com/chromebook?p=WebAuthn";
InSessionAuthDialogClient* g_auth_dialog_client_instance = nullptr;
} // namespace
InSessionAuthDialogClient::InSessionAuthDialogClient()
: auth_performer_(ash::UserDataAuthClient::Get()) {
ash::WebAuthNDialogController::Get()->SetClient(this);
DCHECK(!g_auth_dialog_client_instance);
g_auth_dialog_client_instance = this;
}
InSessionAuthDialogClient::~InSessionAuthDialogClient() {
ash::WebAuthNDialogController::Get()->SetClient(nullptr);
DCHECK_EQ(this, g_auth_dialog_client_instance);
g_auth_dialog_client_instance = nullptr;
}
// static
bool InSessionAuthDialogClient::HasInstance() {
return !!g_auth_dialog_client_instance;
}
// static
InSessionAuthDialogClient* InSessionAuthDialogClient::Get() {
DCHECK(g_auth_dialog_client_instance);
return g_auth_dialog_client_instance;
}
bool InSessionAuthDialogClient::IsFingerprintAuthAvailable(
const AccountId& account_id) {
return legacy_fingerprint_engine_->IsFingerprintAvailable(
ash::LegacyFingerprintEngine::Purpose::kWebAuthn,
user_context_->GetAccountId());
}
void InSessionAuthDialogClient::StartFingerprintAuthSession(
const AccountId& account_id,
base::OnceCallback<void(bool)> callback) {
legacy_fingerprint_engine_->PrepareLegacyFingerprintFactor(
std::move(user_context_),
base::BindOnce(
&InSessionAuthDialogClient::OnPrepareLegacyFingerprintFactor,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void InSessionAuthDialogClient::OnPrepareLegacyFingerprintFactor(
base::OnceCallback<void(bool)> callback,
std::unique_ptr<UserContext> user_context,
std::optional<AuthenticationError> error) {
user_context_ = std::move(user_context);
if (error.has_value()) {
LOG(ERROR) << "Could not prepare legacy fingerprint auth factor, code: "
<< error->get_cryptohome_code();
std::move(callback).Run(false);
return;
}
observation_.Observe(ash::UserDataAuthClient::Get());
std::move(callback).Run(true);
}
void InSessionAuthDialogClient::EndFingerprintAuthSession(
base::OnceClosure callback) {
if (legacy_fingerprint_engine_.has_value()) {
legacy_fingerprint_engine_->TerminateLegacyFingerprintFactor(
std::move(user_context_),
base::BindOnce(
&InSessionAuthDialogClient::OnTerminateLegacyFingerprintFactor,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
}
void InSessionAuthDialogClient::OnTerminateLegacyFingerprintFactor(
base::OnceClosure callback,
std::unique_ptr<UserContext> user_context,
std::optional<AuthenticationError> error) {
// Proceed to updating the state and running the callback.
// We need this regardless of whether an error occurred.
if (error.has_value()) {
LOG(ERROR) << "Error terminating legacy fingerprint auth factor, code: "
<< error->get_cryptohome_code();
}
user_context_ = std::move(user_context);
observation_.Reset();
std::move(callback).Run();
}
void InSessionAuthDialogClient::CheckPinAuthAvailability(
const AccountId& account_id,
base::OnceCallback<void(bool)> callback) {
auto on_pin_availability_checked =
base::BindOnce(&InSessionAuthDialogClient::OnCheckPinAuthAvailability,
weak_factory_.GetWeakPtr(), std::move(callback));
CHECK(pin_engine_.has_value());
pin_engine_->IsPinAuthAvailable(
ash::legacy::CryptohomePinEngine::Purpose::kWebAuthn,
std::move(user_context_), std::move(on_pin_availability_checked));
}
void InSessionAuthDialogClient::OnCheckPinAuthAvailability(
base::OnceCallback<void(bool)> callback,
bool is_pin_auth_available,
std::unique_ptr<UserContext> user_context) {
user_context_ = std::move(user_context);
std::move(callback).Run(is_pin_auth_available);
}
void InSessionAuthDialogClient::StartAuthSession(
base::OnceCallback<void(bool)> callback) {
auto* user_manager = user_manager::UserManager::Get();
const user_manager::User* const user = user_manager->GetActiveUser();
const bool ephemeral =
user_manager->IsUserCryptohomeDataEphemeral(user->GetAccountId());
auto user_context = std::make_unique<UserContext>(*user);
auto on_auth_session_started =
base::BindOnce(&InSessionAuthDialogClient::OnAuthSessionStarted,
weak_factory_.GetWeakPtr(), std::move(callback));
auth_performer_.StartAuthSession(std::move(user_context), ephemeral,
ash::AuthSessionIntent::kWebAuthn,
std::move(on_auth_session_started));
}
void InSessionAuthDialogClient::InvalidateAuthSession() {
if (user_context_) {
auth_performer_.InvalidateAuthSession(std::move(user_context_),
base::DoNothing());
pin_engine_.reset();
}
}
void InSessionAuthDialogClient::AuthenticateUserWithPasswordOrPin(
const std::string& secret,
bool authenticated_by_pin,
base::OnceCallback<void(bool)> callback) {
// TODO(b/156258540): Pick/validate the correct user.
const user_manager::User* const user =
user_manager::UserManager::Get()->GetActiveUser();
DCHECK(user);
auto user_context = std::make_unique<UserContext>(*user);
Key key(Key::KEY_TYPE_PASSWORD_PLAIN, std::string(), secret);
user_context->SetIsUsingPin(authenticated_by_pin);
user_context->SetSyncPasswordData(password_manager::PasswordHashData(
user->GetAccountId().GetUserEmail(), base::UTF8ToUTF16(secret),
false /*force_update*/));
if (user->GetAccountId().GetAccountType() == AccountType::ACTIVE_DIRECTORY) {
LOG(FATAL) << "Incorrect Active Directory user type "
<< user_context->GetUserType();
}
DCHECK(!pending_auth_state_);
pending_auth_state_.emplace(std::move(callback));
if (!authenticated_by_pin) {
// TODO(yichengli): If user type is SUPERVISED, use supervised
// authenticator?
user_context->SetKey(std::move(key));
AuthenticateWithPassword(std::move(user_context), secret);
return;
}
pin_engine_->Authenticate(
cryptohome::RawPin(secret), std::move(user_context_),
base::BindOnce(&InSessionAuthDialogClient::OnAuthVerified,
weak_factory_.GetWeakPtr(),
/*authenticated_by_password=*/false));
}
void InSessionAuthDialogClient::OnPinAttemptDone(
std::unique_ptr<UserContext> user_context,
std::optional<AuthenticationError> error) {
if (!error.has_value()) {
OnAuthSuccess(std::move(*user_context));
} else {
// Do not try submitting as password.
if (pending_auth_state_) {
std::move(pending_auth_state_->callback).Run(false);
pending_auth_state_.reset();
}
}
}
void InSessionAuthDialogClient::AuthenticateWithPassword(
std::unique_ptr<UserContext> user_context,
const std::string& password) {
// Check that we have a valid `user_context_`, provided by a prior
// `StartAuthSession`, this also nicely asserts that we are not waiting
// on other UserDataAuth dbus calls that involve the auth_session_id stored
// in this `user_context`.
CHECK(user_context_);
const auto* password_factor =
user_context_->GetAuthFactorsData().FindAnyPasswordFactor();
if (!password_factor) {
LOG(ERROR) << "Could not find password key";
std::move(pending_auth_state_->callback).Run(false);
return;
}
auto on_authenticated = base::BindOnce(
&InSessionAuthDialogClient::OnAuthVerified, weak_factory_.GetWeakPtr(),
/*authenticated_by_password=*/true);
auth_performer_.AuthenticateWithPassword(*(password_factor->ref().label()),
password, std::move(user_context_),
std::move(on_authenticated));
}
void InSessionAuthDialogClient::OnAuthSessionStarted(
base::OnceCallback<void(bool)> callback,
bool user_exists,
std::unique_ptr<UserContext> user_context,
std::optional<AuthenticationError> error) {
if (error.has_value()) {
LOG(ERROR) << "Failed to start auth session, code "
<< error->get_cryptohome_code();
std::move(callback).Run(false);
return;
}
if (!user_exists) {
LOG(ERROR) << "User does not exist";
// Invalidate the auth session that started, do not leave orphans behind.
auth_performer_.InvalidateAuthSession(std::move(user_context),
base::DoNothing());
std::move(callback).Run(false);
return;
}
// Take temporary ownership of user_context to pass on later.
user_context_ = std::move(user_context);
pin_engine_.emplace(&auth_performer_);
legacy_fingerprint_engine_.emplace(&auth_performer_);
std::move(callback).Run(true);
}
void InSessionAuthDialogClient::OnAuthVerified(
bool authenticated_by_password,
std::unique_ptr<UserContext> user_context,
std::optional<AuthenticationError> error) {
// Take back ownership of user_context for future auth attempts.
user_context_ = std::move(user_context);
if (error.has_value()) {
LOG(ERROR) << "Failed to authenticate, code "
<< error->get_cryptohome_code();
std::move(pending_auth_state_->callback).Run(false);
} else {
// TODO(b:241256423): Tell cryptohome to release WebAuthN secret.
if (authenticated_by_password)
OnPasswordAuthSuccess(*user_context_);
std::move(pending_auth_state_->callback).Run(true);
}
pending_auth_state_.reset();
}
void InSessionAuthDialogClient::OnPasswordAuthSuccess(
const UserContext& user_context) {
ash::quick_unlock::QuickUnlockStorage* quick_unlock_storage =
ash::quick_unlock::QuickUnlockFactory::GetForAccountId(
user_context.GetAccountId());
if (quick_unlock_storage)
quick_unlock_storage->MarkStrongAuth();
}
void InSessionAuthDialogClient::AuthenticateUserWithFingerprint(
base::OnceCallback<void(bool, ash::FingerprintState)> callback) {
DCHECK(!fingerprint_scan_done_callback_);
fingerprint_scan_done_callback_ = std::move(callback);
}
void InSessionAuthDialogClient::OnFingerprintScan(
const ::user_data_auth::FingerprintScanResult& result) {
if (!fingerprint_scan_done_callback_)
return;
switch (result) {
case user_data_auth::FINGERPRINT_SCAN_RESULT_SUCCESS:
std::move(fingerprint_scan_done_callback_)
.Run(true, ash::FingerprintState::AVAILABLE_DEFAULT);
break;
case user_data_auth::FINGERPRINT_SCAN_RESULT_RETRY:
std::move(fingerprint_scan_done_callback_)
.Run(false, ash::FingerprintState::AVAILABLE_DEFAULT);
break;
case user_data_auth::FINGERPRINT_SCAN_RESULT_LOCKOUT:
std::move(fingerprint_scan_done_callback_)
.Run(false, ash::FingerprintState::DISABLED_FROM_ATTEMPTS);
break;
case user_data_auth::FINGERPRINT_SCAN_RESULT_FATAL_ERROR:
std::move(fingerprint_scan_done_callback_)
.Run(false, ash::FingerprintState::UNAVAILABLE);
break;
default:
std::move(fingerprint_scan_done_callback_)
.Run(false, ash::FingerprintState::UNAVAILABLE);
}
}
aura::Window* InSessionAuthDialogClient::OpenInSessionAuthHelpPage() const {
// TODO(b/156258540): Use the profile of the source browser window.
Profile* profile = ProfileManager::GetActiveUserProfile();
// Create new browser window because the auth dialog is a child of the
// existing one.
NavigateParams params(profile, GURL(kInSessionAuthHelpPageUrl),
ui::PAGE_TRANSITION_AUTO_BOOKMARK);
params.disposition = WindowOpenDisposition::NEW_POPUP;
params.trusted_source = true;
params.window_action = NavigateParams::SHOW_WINDOW;
params.user_gesture = true;
params.path_behavior = NavigateParams::IGNORE_AND_NAVIGATE;
Navigate(¶ms);
return params.browser->window()->GetNativeWindow();
}
// AuthStatusConsumer:
void InSessionAuthDialogClient::OnAuthFailure(const ash::AuthFailure& error) {
if (pending_auth_state_) {
std::move(pending_auth_state_->callback).Run(false);
pending_auth_state_.reset();
}
}
void InSessionAuthDialogClient::OnAuthSuccess(const UserContext& user_context) {
if (pending_auth_state_) {
std::move(pending_auth_state_->callback).Run(true);
pending_auth_state_.reset();
}
}
InSessionAuthDialogClient::AuthState::AuthState(
base::OnceCallback<void(bool)> callback)
: callback(std::move(callback)) {}
InSessionAuthDialogClient::AuthState::~AuthState() = default;