chromium/ash/auth/active_session_auth_metrics_recorder.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 "ash/auth/active_session_auth_metrics_recorder.h"

#include <optional>

#include "ash/ash_export.h"
#include "ash/auth/views/auth_common.h"
#include "ash/public/cpp/auth/active_session_auth_controller.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/timer/elapsed_timer.h"

namespace ash {

namespace {

// Constants for histograms.
constexpr char kShowReasonHistogram[] = "Ash.Auth.ActiveSessionShowReason";
constexpr char kAuthStartedHistogram[] = "Ash.Auth.ActiveSessionAuthStart";
constexpr char kAuthFailedHistogram[] = "Ash.Auth.ActiveSessionAuthFailed";
constexpr char kAuthSucceededHistogram[] =
    "Ash.Auth.ActiveSessionAuthSucceeded";
constexpr char kClosedWithSuccessHistogram[] =
    "Ash.Auth.ActiveSessionAuthClosedWithSuccess";
constexpr char kClosedDuringAuthHistogram[] =
    "Ash.Auth.ActiveSessionAuthClosedDuringAuth";
constexpr char kOpenDurationHistogram[] =
    "Ash.Auth.ActiveSessionAuthOpenDuration";
constexpr char kNumberOfPinAttemptHistogram[] =
    "Ash.Auth.ActiveSessionAuthPinAttempt";
constexpr char kNumberOfPasswordAttemptHistogram[] =
    "Ash.Auth.ActiveSessionAuthPasswordAttempt";
constexpr char kNumberOfFingerprintAttemptHistogram[] =
    "Ash.Auth.ActiveSessionAuthFingerprintAttempt";

// The ceiling to use when clamping the number of PIN attempts that can be
// recorded for UMA collection.
constexpr int kMaxRecordedPinAttempts = 20;

// The ceiling to use when clamping the number of Password attempts that can be
// recorded for UMA collection.
constexpr int kMaxRecordedPasswordAttempts = 20;

// The ceiling to use when clamping the number of Fingerprint attempts that can
// be recorded for UMA collection.
constexpr int kMaxRecordedFingerprintAttempts = 20;

}  // namespace

ActiveSessionAuthMetricsRecorder::ActiveSessionAuthMetricsRecorder() = default;

ActiveSessionAuthMetricsRecorder::~ActiveSessionAuthMetricsRecorder() = default;

void ActiveSessionAuthMetricsRecorder::RecordShow(AuthRequest::Reason reason) {
  CHECK(!open_reason_.has_value());
  CHECK(!open_timer_.has_value());

  // Record to metric the reason when the ActiveSessionAuthWidget is shown.
  base::UmaHistogramEnumeration(kShowReasonHistogram, reason);

  open_reason_ = reason;
  open_timer_.emplace(base::ElapsedTimer());
}

void ActiveSessionAuthMetricsRecorder::RecordClose() {
  CHECK(open_reason_.has_value());
  CHECK(open_timer_.has_value());

  // Record to metric the dialog was closed after authentication succeeded or
  // not.
  base::UmaHistogramBoolean(kClosedWithSuccessHistogram, auth_succeeded_);

  // Record to metric the dialog was closed during authentication or not.
  base::UmaHistogramBoolean(kClosedDuringAuthHistogram,
                            started_auth_type_.has_value());

  // Record to metric how long was the dialog opened.
  base::UmaHistogramMediumTimes(kOpenDurationHistogram, open_timer_->Elapsed());

  // Record to metric the number of pin attempts.
  base::UmaHistogramExactLinear(kNumberOfPinAttemptHistogram,
                                pin_attempt_counter_, kMaxRecordedPinAttempts);

  // Record to metric the number of password attempts.
  base::UmaHistogramExactLinear(kNumberOfPasswordAttemptHistogram,
                                password_attempt_counter_,
                                kMaxRecordedPasswordAttempts);

  // Record to metric the number of fingerprint attempts.
  base::UmaHistogramExactLinear(kNumberOfFingerprintAttemptHistogram,
                                fingerprint_attempt_counter_,
                                kMaxRecordedFingerprintAttempts);

  // Reset the state.
  auth_succeeded_ = false;
  pin_attempt_counter_ = 0;
  password_attempt_counter_ = 0;
  fingerprint_attempt_counter_ = 0;
  started_auth_type_.reset();
  open_reason_.reset();
  open_timer_.reset();
}

void ActiveSessionAuthMetricsRecorder::RecordAuthStarted(
    AuthInputType input_type) {
  CHECK(!started_auth_type_.has_value());
  switch (input_type) {
    case AuthInputType::kPassword:
      ++password_attempt_counter_;
      break;
    case AuthInputType::kPin:
      ++pin_attempt_counter_;
      break;
    case AuthInputType::kFingerprint:
      // Fingerprint authentication begins when the authentication dialog is
      // shown and the user has fingerprint record(s) and the policy doesn't
      // prevent its use. The fingerprint session remains active until
      // explicitly terminated, so even after a failed scan attempt, there's no
      // need to restart it. This session runs concurrently with password/PIN
      // authentication attempts. Therefore, we are not tracking "Authentication
      // Started" events for fingerprint specifically, but rather focusing on
      // the count of successful and failed attempts.
      NOTREACHED();
    default:
      NOTREACHED();
  }
  started_auth_type_ = input_type;

  // Record to metric the auth input type when an authentication started.
  base::UmaHistogramEnumeration(kAuthStartedHistogram, input_type);
}

void ActiveSessionAuthMetricsRecorder::RecordAuthFailed(
    AuthInputType input_type) {
  // Fingerprint authentication can occur concurrently with other
  // AuthInputType's.
  if (input_type == AuthInputType::kFingerprint) {
    ++fingerprint_attempt_counter_;
  } else {
    CHECK(started_auth_type_.has_value());
    CHECK_EQ(started_auth_type_.value(), input_type);

    started_auth_type_.reset();
  }

  // Record to metric the failed authentication type.
  base::UmaHistogramEnumeration(kAuthFailedHistogram, input_type);
}

void ActiveSessionAuthMetricsRecorder::RecordAuthSucceeded(
    AuthInputType input_type) {
  // Fingerprint authentication can occur concurrently with other
  // AuthInputType's.
  if (input_type == AuthInputType::kFingerprint) {
    ++fingerprint_attempt_counter_;
  } else {
    CHECK(started_auth_type_.has_value());
    CHECK_EQ(started_auth_type_.value(), input_type);
  }

  // Record to metric the succeeded authentication type.
  base::UmaHistogramEnumeration(kAuthSucceededHistogram, input_type);

  started_auth_type_.reset();
  auth_succeeded_ = true;
}

}  // namespace ash