chromium/ash/in_session_auth/webauthn_dialog_controller_impl.cc

// 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 "ash/in_session_auth/webauthn_dialog_controller_impl.h"

#include <cstdint>
#include <memory>
#include <string>
#include <utility>

#include "ash/in_session_auth/auth_dialog_contents_view.h"
#include "ash/in_session_auth/in_session_auth_dialog.h"
#include "ash/in_session_auth/webauthn_request_registrar_impl.h"
#include "ash/public/cpp/in_session_auth_dialog_client.h"
#include "ash/public/cpp/login_types.h"
#include "ash/public/cpp/session/session_types.h"
#include "ash/public/cpp/session/user_info.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "components/user_manager/known_user.h"
#include "ui/aura/window.h"

namespace ash {

WebAuthNDialogControllerImpl::WebAuthNDialogControllerImpl()
    : webauthn_request_registrar_(
          std::make_unique<WebAuthnRequestRegistrarImpl>()) {}

WebAuthNDialogControllerImpl::~WebAuthNDialogControllerImpl() = default;

void WebAuthNDialogControllerImpl::SetClient(
    InSessionAuthDialogClient* client) {
  client_ = client;
}

void WebAuthNDialogControllerImpl::ShowAuthenticationDialog(
    aura::Window* source_window,
    const std::string& origin_name,
    FinishCallback finish_callback) {
  DCHECK(client_);
  // Concurrent requests are not supported.
  DCHECK(!dialog_);

  source_window_tracker_.Add(source_window);
  finish_callback_ = std::move(finish_callback);

  AccountId account_id =
      Shell::Get()->session_controller()->GetActiveAccountId();
  // GAIA password option is not offered.
  uint32_t auth_methods = AuthDialogContentsView::kAuthPassword;

  base::OnceClosure continuation =
      base::BindOnce(&WebAuthNDialogControllerImpl::CheckAuthFactorAvailability,
                     weak_factory_.GetWeakPtr(), account_id, origin_name,
                     auth_methods, source_window);

  auto on_auth_session_started = [](base::OnceClosure continuation,
                                    bool is_auth_session_started) {
    if (!is_auth_session_started) {
      LOG(ERROR)
          << "Failed to start cryptohome auth session, exiting dialog early";
      return;
    }
    std::move(continuation).Run();
  };

  client_->StartAuthSession(
      base::BindOnce(on_auth_session_started, std::move(continuation)));
  return;
}

void WebAuthNDialogControllerImpl::CheckAuthFactorAvailability(
    const AccountId& account_id,
    const std::string& origin_name,
    uint32_t auth_methods,
    aura::Window* source_window) {
  if (client_->IsFingerprintAuthAvailable(account_id)) {
    client_->StartFingerprintAuthSession(
        account_id,
        base::BindOnce(
            &WebAuthNDialogControllerImpl::OnStartFingerprintAuthSession,
            weak_factory_.GetWeakPtr(), account_id, auth_methods, source_window,
            origin_name));
    // OnStartFingerprintAuthSession checks PIN availability.
    return;
  }

  client_->CheckPinAuthAvailability(
      account_id,
      base::BindOnce(&WebAuthNDialogControllerImpl::OnPinCanAuthenticate,
                     weak_factory_.GetWeakPtr(), auth_methods, source_window,
                     origin_name));
}

void WebAuthNDialogControllerImpl::OnStartFingerprintAuthSession(
    AccountId account_id,
    uint32_t auth_methods,
    aura::Window* source_window,
    const std::string& origin_name,
    bool success) {
  if (success)
    auth_methods |= AuthDialogContentsView::kAuthFingerprint;

  client_->CheckPinAuthAvailability(
      account_id,
      base::BindOnce(&WebAuthNDialogControllerImpl::OnPinCanAuthenticate,
                     weak_factory_.GetWeakPtr(), auth_methods, source_window,
                     origin_name));
}

void WebAuthNDialogControllerImpl::OnPinCanAuthenticate(
    uint32_t auth_methods,
    aura::Window* source_window,
    const std::string& origin_name,
    bool pin_auth_available) {
  if (pin_auth_available)
    auth_methods |= AuthDialogContentsView::kAuthPin;

  if (auth_methods == 0) {
    // If neither fingerprint nor PIN is available, we shouldn't receive the
    // request.
    LOG(ERROR) << "Neither fingerprint nor PIN is available.";
    Cancel();
    return;
  }

  if (!source_window_tracker_.Contains(source_window)) {
    LOG(ERROR) << "Source window is no longer available.";
    Cancel();
    return;
  }

  Shell* shell = Shell::Get();
  AccountId account_id = shell->session_controller()->GetActiveAccountId();
  const UserSession* session =
      shell->session_controller()->GetUserSessionByAccountId(account_id);
  DCHECK(session);
  UserAvatar avatar = session->user_info.avatar;

  // TODO(b/156258540): move UserSelectionScreen::BuildAshUserAvatarForUser to
  // somewhere that UserToUserSession could call, to support animated avatars.

  AuthDialogContentsView::AuthMethodsMetadata auth_metadata;
  auth_metadata.autosubmit_pin_length =
      user_manager::KnownUser(shell->local_state())
          .GetUserPinLength(account_id);
  source_window_tracker_.Remove(source_window);
  dialog_ = std::make_unique<InSessionAuthDialog>(
      auth_methods, source_window, origin_name, auth_metadata, avatar);
}

void WebAuthNDialogControllerImpl::DestroyAuthenticationDialog() {
  DCHECK(client_);
  if (!dialog_)
    return;

  if (dialog_->GetAuthMethods() & AuthDialogContentsView::kAuthFingerprint) {
    client_->EndFingerprintAuthSession(
        base::BindOnce(&WebAuthNDialogControllerImpl::ProcessFinalCleanups,
                       weak_factory_.GetWeakPtr()));
    return;
  }

  ProcessFinalCleanups();
}

void WebAuthNDialogControllerImpl::ProcessFinalCleanups() {
  client_->InvalidateAuthSession();
  dialog_.reset();
  source_window_tracker_.RemoveAll();
}

void WebAuthNDialogControllerImpl::AuthenticateUserWithPasswordOrPin(
    const std::string& password,
    bool authenticated_by_pin,
    OnAuthenticateCallback callback) {
  DCHECK(client_);

  // TODO(b/156258540): Check that PIN is enabled / set up for this user.
  if (authenticated_by_pin &&
      !base::ContainsOnlyChars(password, "0123456789")) {
    OnAuthenticateComplete(std::move(callback), false);
    return;
  }

  client_->AuthenticateUserWithPasswordOrPin(
      password, authenticated_by_pin,
      base::BindOnce(&WebAuthNDialogControllerImpl::OnAuthenticateComplete,
                     weak_factory_.GetWeakPtr(), std::move(callback)));
}

void WebAuthNDialogControllerImpl::AuthenticateUserWithFingerprint(
    base::OnceCallback<void(bool, FingerprintState)> views_callback) {
  DCHECK(client_);

  client_->AuthenticateUserWithFingerprint(
      base::BindOnce(&WebAuthNDialogControllerImpl::OnFingerprintAuthComplete,
                     weak_factory_.GetWeakPtr(), std::move(views_callback)));
}

void WebAuthNDialogControllerImpl::OnAuthenticateComplete(
    OnAuthenticateCallback callback,
    bool success) {
  if (success) {
    std::move(callback).Run(/*success=*/true, /*can_use_pin=*/true);
    OnAuthSuccess();
    return;
  }

  // PIN might be locked out after an unsuccessful authentication, check if it's
  // still available so the UI can be updated.
  AccountId account_id =
      Shell::Get()->session_controller()->GetActiveAccountId();
  client_->CheckPinAuthAvailability(
      account_id, base::BindOnce(std::move(callback), /*success=*/false));
}

void WebAuthNDialogControllerImpl::OnFingerprintAuthComplete(
    base::OnceCallback<void(bool, FingerprintState)> views_callback,
    bool success,
    FingerprintState fingerprint_state) {
  // If success is false and retry is allowed, the view will start another
  // fingerprint check.
  std::move(views_callback).Run(success, fingerprint_state);

  if (success)
    OnAuthSuccess();
}

void WebAuthNDialogControllerImpl::OnAuthSuccess() {
  DestroyAuthenticationDialog();
  if (finish_callback_)
    std::move(finish_callback_).Run(true);
}

void WebAuthNDialogControllerImpl::Cancel() {
  DestroyAuthenticationDialog();
  if (finish_callback_)
    std::move(finish_callback_).Run(false);
}

void WebAuthNDialogControllerImpl::OpenInSessionAuthHelpPage() {
  DCHECK(client_);
  client_->OpenInSessionAuthHelpPage();
}

void WebAuthNDialogControllerImpl::CheckAvailability(
    FinishCallback on_availability_checked) const {
  // Assumes the requests are for the active user (no teleported window).
  AccountId account_id =
      Shell::Get()->session_controller()->GetActiveAccountId();

  if (client_->IsFingerprintAuthAvailable(account_id)) {
    std::move(on_availability_checked).Run(true);
    return;
  }

  client_->CheckPinAuthAvailability(account_id,
                                    std::move(on_availability_checked));
}

}  // namespace ash