[WebAuthn] Record times for fetching conditional UI credentials
This adds a metric for recording the duration of platform credential list requests to platforms. It splits the histograms for desktop, Android GMS Core, and Android CredMan. It is only emitted when at least one credential is found. Low-Coverage-Reason: Only adding metrics to existing code paths. Bug: 355006315 Change-Id: Idf6870600caafe435c6da1a0a02893451112bf1c Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5738911 Code-Coverage: findit-for-me@appspot.gserviceaccount.com <findit-for-me@appspot.gserviceaccount.com> Commit-Queue: Ken Buchanan <kenrb@chromium.org> Reviewed-by: Martin Kreichgauer <martinkr@google.com> Cr-Commit-Position: refs/heads/main@{#1334382}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
e3fcfdcbd7
commit
6260d24243
components/webauthn/android
java
src
org
chromium
components
junit
src
org
chromium
components
webauthn
device/fido
fido_request_handler_base.ccfido_request_handler_base.hfido_request_handler_unittest.ccget_assertion_request_handler.cc
tools/metrics/histograms/metadata/webauthn
15
components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2CredentialRequest.java
15
components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2CredentialRequest.java
@ -17,6 +17,7 @@ import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Parcel;
|
||||
import android.os.ResultReceiver;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
@ -30,6 +31,7 @@ import org.jni_zero.NativeMethods;
|
||||
|
||||
import org.chromium.base.Callback;
|
||||
import org.chromium.base.Log;
|
||||
import org.chromium.base.metrics.RecordHistogram;
|
||||
import org.chromium.blink.mojom.AuthenticatorStatus;
|
||||
import org.chromium.blink.mojom.AuthenticatorTransport;
|
||||
import org.chromium.blink.mojom.GetAssertionAuthenticatorResponse;
|
||||
@ -533,6 +535,7 @@ public class Fido2CredentialRequest
|
||||
mBarrier.resetAndSetWaitStatus(Barrier.Mode.ONLY_FIDO_2_API);
|
||||
}
|
||||
mConditionalUiState = ConditionalUiState.WAITING_FOR_CREDENTIAL_LIST;
|
||||
long conditionalUiCredentialListInitialTimeMs = SystemClock.elapsedRealtime();
|
||||
Fido2ApiCallHelper.getInstance()
|
||||
.invokeFido2GetCredentials(
|
||||
mAuthenticationContextProvider,
|
||||
@ -544,7 +547,8 @@ public class Fido2CredentialRequest
|
||||
options,
|
||||
callerOriginString,
|
||||
finalClientDataHash,
|
||||
credentials)),
|
||||
credentials,
|
||||
conditionalUiCredentialListInitialTimeMs)),
|
||||
(e) ->
|
||||
mBarrier.onFido2ApiFailed(
|
||||
AuthenticatorStatus.NOT_ALLOWED_ERROR));
|
||||
@ -703,7 +707,8 @@ public class Fido2CredentialRequest
|
||||
PublicKeyCredentialRequestOptions options,
|
||||
String callerOriginString,
|
||||
byte[] clientDataHash,
|
||||
List<WebauthnCredentialDetails> credentials) {
|
||||
List<WebauthnCredentialDetails> credentials,
|
||||
long conditionalUiCredentialListInitialTimeMs) {
|
||||
assert mConditionalUiState == ConditionalUiState.WAITING_FOR_CREDENTIAL_LIST
|
||||
|| mConditionalUiState == ConditionalUiState.CANCEL_PENDING;
|
||||
|
||||
@ -712,6 +717,12 @@ public class Fido2CredentialRequest
|
||||
boolean isConditionalRequest = options.isConditional;
|
||||
assert isConditionalRequest || !hasAllowCredentials;
|
||||
|
||||
if (!credentials.isEmpty()) {
|
||||
RecordHistogram.recordTimesHistogram(
|
||||
"WebAuthentication.CredentialFetchDuration.GmsCore",
|
||||
SystemClock.elapsedRealtime() - conditionalUiCredentialListInitialTimeMs);
|
||||
}
|
||||
|
||||
if (mConditionalUiState == ConditionalUiState.CANCEL_PENDING) {
|
||||
// The request was completed synchronously when the cancellation was received,
|
||||
// so no need to return an error to the renderer.
|
||||
|
@ -285,7 +285,8 @@ public class CredManHelper {
|
||||
? CredManPrepareRequestEnum.SUCCESS_HAS_RESULTS
|
||||
: CredManPrepareRequestEnum.SUCCESS_NO_RESULTS);
|
||||
mMetricsHelper.recordCredmanPrepareRequestDuration(
|
||||
SystemClock.elapsedRealtime() - startTimeMs);
|
||||
SystemClock.elapsedRealtime() - startTimeMs,
|
||||
hasPublicKeyCredentials);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -92,9 +92,13 @@ public class CredManMetricsHelper {
|
||||
CredManPrepareRequestEnum.NUM_ENTRIES);
|
||||
}
|
||||
|
||||
public void recordCredmanPrepareRequestDuration(long durationMs) {
|
||||
public void recordCredmanPrepareRequestDuration(long durationMs, boolean credentialsFound) {
|
||||
RecordHistogram.recordTimesHistogram(
|
||||
"WebAuthentication.Android.CredManPrepareRequestDuration", durationMs);
|
||||
if (credentialsFound) {
|
||||
RecordHistogram.recordTimesHistogram(
|
||||
"WebAuthentication.CredentialFetchDuration.CredMan", durationMs);
|
||||
}
|
||||
}
|
||||
|
||||
public void reportGetCredentialMetrics(
|
||||
|
@ -511,7 +511,8 @@ public class CredManHelperRobolectricTest {
|
||||
.recordCredmanPrepareRequestHistogram(eq(CredManPrepareRequestEnum.SENT_REQUEST));
|
||||
verify(mMetricsHelper, times(1))
|
||||
.recordCredmanPrepareRequestHistogram(eq(CredManPrepareRequestEnum.FAILURE));
|
||||
verify(mMetricsHelper, times(0)).recordCredmanPrepareRequestDuration(anyLong());
|
||||
verify(mMetricsHelper, times(0))
|
||||
.recordCredmanPrepareRequestDuration(anyLong(), anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -579,7 +580,8 @@ public class CredManHelperRobolectricTest {
|
||||
verify(mBarrier).onCredManSuccessful(credManCallSuccessfulRunback.capture());
|
||||
credManCallSuccessfulRunback.getValue().run();
|
||||
|
||||
verify(mMetricsHelper, times(1)).recordCredmanPrepareRequestDuration(anyLong());
|
||||
verify(mMetricsHelper, times(1))
|
||||
.recordCredmanPrepareRequestDuration(anyLong(), anyBoolean());
|
||||
|
||||
// Setup the test for startGetRequest:
|
||||
verify(mBrowserBridge, times(1))
|
||||
@ -631,7 +633,8 @@ public class CredManHelperRobolectricTest {
|
||||
verify(mBarrier).onCredManSuccessful(credManCallSuccessfulRunback.capture());
|
||||
credManCallSuccessfulRunback.getValue().run();
|
||||
|
||||
verify(mMetricsHelper, times(1)).recordCredmanPrepareRequestDuration(anyLong());
|
||||
verify(mMetricsHelper, times(1))
|
||||
.recordCredmanPrepareRequestDuration(anyLong(), anyBoolean());
|
||||
verify(mBrowserBridge, times(1))
|
||||
.onCredManConditionalRequestPending(any(), anyBoolean(), callbackCaptor.capture());
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "base/functional/callback.h"
|
||||
#include "base/location.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/metrics/histogram_functions.h"
|
||||
#include "base/notreached.h"
|
||||
#include "base/task/sequenced_task_runner.h"
|
||||
#include "base/time/time.h"
|
||||
@ -42,6 +43,7 @@
|
||||
namespace device {
|
||||
|
||||
namespace {
|
||||
|
||||
bool IsGpmPasskeyAuthenticator(const FidoAuthenticator& authenticator) {
|
||||
switch (authenticator.GetType()) {
|
||||
case AuthenticatorType::kWinNative:
|
||||
@ -57,6 +59,31 @@ bool IsGpmPasskeyAuthenticator(const FidoAuthenticator& authenticator) {
|
||||
}
|
||||
NOTREACHED_NORETURN();
|
||||
}
|
||||
|
||||
void MaybeRecordPlatformCredentialStatus(AuthenticatorType type,
|
||||
base::TimeDelta elapsed_time) {
|
||||
std::string metric_name;
|
||||
|
||||
switch (type) {
|
||||
case AuthenticatorType::kWinNative:
|
||||
metric_name = "WebAuthentication.CredentialFetchDuration.WinHello";
|
||||
break;
|
||||
case AuthenticatorType::kTouchID:
|
||||
metric_name = "WebAuthentication.CredentialFetchDuration.TouchId";
|
||||
break;
|
||||
case AuthenticatorType::kChromeOS:
|
||||
metric_name = "WebAuthentication.CredentialFetchDuration.ChromeOS";
|
||||
break;
|
||||
case AuthenticatorType::kICloudKeychain:
|
||||
metric_name = "WebAuthentication.CredentialFetchDuration.ICloudKeychain";
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
base::UmaHistogramTimes(metric_name, elapsed_time);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// TransportAvailabilityCallbackReadiness stores state that tracks whether
|
||||
@ -532,8 +559,13 @@ void FidoRequestHandlerBase::GetPlatformCredentialStatus(
|
||||
|
||||
void FidoRequestHandlerBase::OnHavePlatformCredentialStatus(
|
||||
AuthenticatorType authenticator_type,
|
||||
std::optional<base::ElapsedTimer> timer,
|
||||
std::vector<DiscoverableCredentialMetadata> creds,
|
||||
RecognizedCredential has_credentials) {
|
||||
if (creds.size() > 0 && timer.has_value()) {
|
||||
MaybeRecordPlatformCredentialStatus(authenticator_type, timer->Elapsed());
|
||||
}
|
||||
|
||||
if (authenticator_type == AuthenticatorType::kICloudKeychain) {
|
||||
// iCloud Keychain is the second platform authenticator on the system and
|
||||
// its status is reported via a different field.
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "base/functional/callback_forward.h"
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "base/timer/elapsed_timer.h"
|
||||
#include "build/build_config.h"
|
||||
#include "device/fido/fido_constants.h"
|
||||
#include "device/fido/fido_discovery_base.h"
|
||||
@ -408,11 +409,13 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoRequestHandlerBase
|
||||
FidoAuthenticator* platform_authenticator);
|
||||
|
||||
// OnHavePlatformCredentialStatus is called by subclasses (after
|
||||
// |GetPlatformCredentialStatus| has been called) to report on whether the
|
||||
// `GetPlatformCredentialStatus` has been called) to report on whether the
|
||||
// platform authenticator whether it has responsive discoverable credentials
|
||||
// and whether it has responsive credentials at all.
|
||||
// `timer` allows recording metrics with the wait time for this callback.
|
||||
void OnHavePlatformCredentialStatus(
|
||||
AuthenticatorType authenticator_type,
|
||||
std::optional<base::ElapsedTimer> timer,
|
||||
std::vector<DiscoverableCredentialMetadata> user_entities,
|
||||
RecognizedCredential has_credentials);
|
||||
|
||||
|
@ -235,7 +235,7 @@ class FakeFidoRequestHandler : public FidoRequestHandlerBase {
|
||||
|
||||
void GetPlatformCredentialStatus(
|
||||
FidoAuthenticator* platform_authenticator) override {
|
||||
OnHavePlatformCredentialStatus(AuthenticatorType::kOther,
|
||||
OnHavePlatformCredentialStatus(AuthenticatorType::kOther, std::nullopt,
|
||||
/*user_entities=*/{},
|
||||
has_platform_credential_);
|
||||
}
|
||||
|
@ -516,7 +516,8 @@ void GetAssertionRequestHandler::GetPlatformCredentialStatus(
|
||||
request_, options_,
|
||||
base::BindOnce(
|
||||
&GetAssertionRequestHandler::OnHavePlatformCredentialStatus,
|
||||
weak_factory_.GetWeakPtr(), platform_authenticator->GetType()));
|
||||
weak_factory_.GetWeakPtr(), platform_authenticator->GetType(),
|
||||
base::ElapsedTimer()));
|
||||
}
|
||||
|
||||
bool GetAssertionRequestHandler::AuthenticatorSelectedForPINUVAuthToken(
|
||||
|
@ -196,6 +196,29 @@ chromium-metrics-reviews@google.com.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram name="WebAuthentication.CredentialFetchDuration.{ApiUsed}"
|
||||
units="ms" expires_after="2024-12-31">
|
||||
<owner>kenrb@chromium.org</owner>
|
||||
<owner>chrome-webauthn@google.com</owner>
|
||||
<summary>
|
||||
The duration of a request to obtain a list of credentials for a WebAuthn
|
||||
conditional UI Get Assertion request, when using the {ApiUsed}. This is only
|
||||
recorded when at least one credential is available in the results. This
|
||||
complements the duration recorded by
|
||||
WebAuthentication.Android.CredManPrepareRequestDuration, which only applies
|
||||
to the CredMan API but emits on all requests including when there are no
|
||||
results present.
|
||||
</summary>
|
||||
<token key="ApiUsed">
|
||||
<variant name="ChromeOS" summary="ChromeOS platform API"/>
|
||||
<variant name="CredMan" summary="Credential Manager API on Android"/>
|
||||
<variant name="GmsCore" summary="GMS Core API on Android"/>
|
||||
<variant name="ICloudKeychain" summary="ICloud Keychain API"/>
|
||||
<variant name="TouchId" summary="Chrome-native Touch ID authenticator"/>
|
||||
<variant name="WinHello" summary="Windows Hello API"/>
|
||||
</token>
|
||||
</histogram>
|
||||
|
||||
<histogram name="WebAuthentication.Enclave.ChangePinEvents"
|
||||
enum="ChangePinEvent" expires_after="2024-12-31">
|
||||
<owner>derinel@google.com</owner>
|
||||
|
Reference in New Issue
Block a user