// Copyright 2019 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/device_reauth/android/device_authenticator_android.h"
#include <memory>
#include <optional>
#include <utility>
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "base/time/time.h"
#include "chrome/browser/device_reauth/android/device_authenticator_bridge_impl.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/device_reauth/device_authenticator.h"
#include "components/password_manager/core/browser/origin_credential_store.h"
#include "components/password_manager/core/common/password_manager_features.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "ui/android/view_android.h"
using content::WebContents;
using device_reauth::BiometricsAvailability;
using device_reauth::DeviceAuthUIResult;
using password_manager::UiCredential;
namespace {
bool IsSuccessfulResult(DeviceAuthUIResult result) {
return result == DeviceAuthUIResult::kSuccessWithUnknownMethod ||
result == DeviceAuthUIResult::kSuccessWithBiometrics ||
result == DeviceAuthUIResult::kSuccessWithDeviceLock;
}
DeviceAuthFinalResult MapUIResultToFinal(DeviceAuthUIResult result) {
switch (result) {
case DeviceAuthUIResult::kSuccessWithUnknownMethod:
return DeviceAuthFinalResult::kSuccessWithUnknownMethod;
case DeviceAuthUIResult::kSuccessWithBiometrics:
return DeviceAuthFinalResult::kSuccessWithBiometrics;
case DeviceAuthUIResult::kSuccessWithDeviceLock:
return DeviceAuthFinalResult::kSuccessWithDeviceLock;
case DeviceAuthUIResult::kCanceledByUser:
return DeviceAuthFinalResult::kCanceledByUser;
case DeviceAuthUIResult::kFailed:
return DeviceAuthFinalResult::kFailed;
}
}
void LogAuthResult(device_reauth::DeviceAuthSource source,
DeviceAuthFinalResult result) {
if (device_reauth::DeviceAuthSource::kPasswordManager == source) {
base::UmaHistogramEnumeration(
"PasswordManager.BiometricAuthPwdFill.AuthResult", result);
} else if (device_reauth::DeviceAuthSource::kIncognito == source) {
base::UmaHistogramEnumeration("Android.IncognitoReauth.AuthResult", result);
}
}
void LogAuthSource(device_reauth::DeviceAuthSource source) {
base::UmaHistogramEnumeration("Android.DeviceAuthenticator.AuthSource",
source);
}
void LogCanAuthenticate(BiometricsAvailability availability) {
base::UmaHistogramEnumeration(
"Android.DeviceAuthenticator.CanAuthenticateWithBiometrics",
availability);
// TODO (crbug.com/350658581): Remove this histogram in favor of the above
// because its name is misleading. Keeping now to track
// `DeviceAuthenticatorAndroidx` experiment.
base::UmaHistogramEnumeration(
"PasswordManager.BiometricAuthPwdFill.CanAuthenticate", availability);
}
} // namespace
DeviceAuthenticatorAndroid::DeviceAuthenticatorAndroid(
std::unique_ptr<DeviceAuthenticatorBridge> bridge,
DeviceAuthenticatorProxy* proxy,
const device_reauth::DeviceAuthParams& params)
: DeviceAuthenticatorCommon(proxy,
params.GetAuthenticationValidityPeriod(),
params.GetAuthResultHistogram()),
bridge_(std::move(bridge)),
source_(params.GetDeviceAuthSource()) {}
DeviceAuthenticatorAndroid::~DeviceAuthenticatorAndroid() = default;
bool DeviceAuthenticatorAndroid::CanAuthenticateWithBiometrics() {
BiometricsAvailability availability = bridge_->CanAuthenticateWithBiometric();
LogCanAuthenticate(availability);
return availability == BiometricsAvailability::kAvailable;
}
bool DeviceAuthenticatorAndroid::CanAuthenticateWithBiometricOrScreenLock() {
return bridge_->CanAuthenticateWithBiometricOrScreenLock();
}
void DeviceAuthenticatorAndroid::AuthenticateWithMessage(
const std::u16string& message,
AuthenticateCallback callback) {
CHECK(message.empty())
<< "Android doesn't support messages for authentication dialog";
// Previous authentication is not yet completed, so return.
if (callback_) {
return;
}
callback_ = std::move(callback);
LogAuthSource(source_);
if (!NeedsToAuthenticate()) {
LogAuthResult(source_, DeviceAuthFinalResult::kAuthStillValid);
// No code should be run after the callback as the callback could already be
// destroying "this".
std::move(callback_).Run(/*success=*/true);
return;
}
// `this` owns the bridge so it's safe to use base::Unretained.
bridge_->Authenticate(
base::BindOnce(&DeviceAuthenticatorAndroid::OnAuthenticationCompleted,
base::Unretained(this)));
}
void DeviceAuthenticatorAndroid::Cancel() {
// There is no ongoing reauth to cancel.
if (!callback_) {
return;
}
LogAuthResult(source_, DeviceAuthFinalResult::kCanceledByChrome);
callback_.Reset();
bridge_->Cancel();
}
void DeviceAuthenticatorAndroid::OnAuthenticationCompleted(
DeviceAuthUIResult ui_result) {
// OnAuthenticationCompleted is called aysnchronously and by the time it's
// invoked Chrome can cancel the authentication via
// DeviceAuthenticatorAndroid::Cancel which resets the callback_.
if (callback_.is_null()) {
return;
}
bool success = IsSuccessfulResult(ui_result);
RecordAuthenticationTimeIfSuccessful(success);
LogAuthResult(source_, MapUIResultToFinal(ui_result));
// No code should be run after the callback as the callback could already be
// destroying "this".
std::move(callback_).Run(success);
}