chromium/device/fido/win/util.cc

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "device/fido/win/util.h"

#include <windows.foundation.h>
#include <windows.security.credentials.ui.h>

#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/scoped_refptr.h"
#include "base/synchronization/atomic_flag.h"
#include "base/task/bind_post_task.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_thread_priority.h"
#include "base/win/core_winrt_util.h"
#include "base/win/post_async_results.h"

namespace device::fido::win {

namespace {

using ABI::Windows::Foundation::IAsyncOperation;
using ABI::Windows::Security::Credentials::UI::IUserConsentVerifierStatics;
using ABI::Windows::Security::Credentials::UI::UserConsentVerifierAvailability;
using enum ABI::Windows::Security::Credentials::UI::
    UserConsentVerifierAvailability;
using Microsoft::WRL::ComPtr;

enum BiometricAvailability {
  kUnknown = 0,
  kAvailable = 1,
  kUnavailable = 2,
};

void DeviceHasBiometricsAvailableInternal(
    base::OnceCallback<void(bool)> callback) {
  SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
  // Lookup requires an asynchronous system API call, so cache the value.
  static std::atomic<BiometricAvailability> availability =
      BiometricAvailability::kUnknown;

  // Read once from the atomic to ensure consistency.
  const BiometricAvailability current_availability = availability;
  if (current_availability != BiometricAvailability::kUnknown) {
    std::move(callback).Run(current_availability ==
                            BiometricAvailability::kAvailable);
    return;
  }

  ComPtr<IUserConsentVerifierStatics> factory;
  HRESULT hr = base::win::GetActivationFactory<
      IUserConsentVerifierStatics,
      RuntimeClass_Windows_Security_Credentials_UI_UserConsentVerifier>(
      &factory);
  if (FAILED(hr)) {
    std::move(callback).Run(false);
    return;
  }
  ComPtr<IAsyncOperation<UserConsentVerifierAvailability>> async_op;
  hr = factory->CheckAvailabilityAsync(&async_op);
  if (FAILED(hr)) {
    std::move(callback).Run(false);
    return;
  }

  // The OnceCallback has to be split three ways here, because there are three
  // paths to resolution: success, asynchronous failure (the operation reports
  // an error), synchronous failure (PostAsyncHandlers returns an error).
  auto first_split = base::SplitOnceCallback(std::move(callback));
  auto second_split = base::SplitOnceCallback(std::move(first_split.first));
  hr = base::win::PostAsyncHandlers(
      async_op.Get(),
      base::BindOnce(
          [](base::OnceCallback<void(bool)> callback,
             std::atomic<BiometricAvailability>& availability,
             UserConsentVerifierAvailability result) {
            boolean available =
                result == UserConsentVerifierAvailability_Available;
            if (result != UserConsentVerifierAvailability_DeviceBusy) {
              availability = available ? BiometricAvailability::kAvailable
                                       : BiometricAvailability::kUnavailable;
            }
            std::move(callback).Run(available);
          },
          std::move(first_split.second), std::ref(availability)),
      base::BindOnce([](base::OnceCallback<void(bool)> callback,
                        HRESULT) { std::move(callback).Run(false); },
                     std::move(second_split.first)));
  if (FAILED(hr)) {
    std::move(second_split.second).Run(false);
    return;
  }
}

std::optional<bool>& GetBiometricOverride() {
  static std::optional<bool> flag;
  return flag;
}

}  // namespace

ScopedBiometricsOverride::ScopedBiometricsOverride(bool has_biometrics) {
  std::optional<bool>& flag = GetBiometricOverride();
  // Overrides don't nest.
  CHECK(!flag.has_value());
  flag = has_biometrics;
}

ScopedBiometricsOverride::~ScopedBiometricsOverride() {
  std::optional<bool>& flag = GetBiometricOverride();
  CHECK(flag.has_value());
  flag.reset();
}

void DeviceHasBiometricsAvailable(base::OnceCallback<void(bool)> callback) {
  std::optional<bool>& flag = GetBiometricOverride();
  if (flag.has_value()) {
    std::move(callback).Run(*flag);
    return;
  }

  scoped_refptr<base::SequencedTaskRunner> task_runner =
      base::ThreadPool::CreateSequencedTaskRunner(
          {base::MayBlock(), base::TaskPriority::USER_BLOCKING});
  task_runner->PostTask(
      FROM_HERE,
      base::BindOnce(&DeviceHasBiometricsAvailableInternal,
                     base::BindPostTaskToCurrentDefault(std::move(callback))));
}

}  // namespace device::fido::win