Commit 838e616d authored by Sujie Zhu's avatar Sujie Zhu Committed by Commit Bot

[Autofill Auth][Clank] Update opt-in from checkout flow & register new

card for Android

On Android, we will return RequestOption in GetRealPan response if user
check the Opt-in checkbox on unmask dialog. When requestOption is
returned, we will call FIDOAuthenticator Authorize. We also store the
cvc and card info to delay the form filling until verification
finishes.

For registering new card, we did not add this flow for Clank yet. We
do it similarly as Desktop.

Note that this CL is not specific for settings page, this is shared
between checkout flow and settings page. For settings page, we will
have a follow up CL to hide the checkbox when intent to opt-in.

Updated corresponding unittest, but could not manual test locally
because of no enrolled key yet.

Bug: 949269
Change-Id: I44f9585a77c19bf33f5d791f616b9756944ddc69
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2044649
Commit-Queue: Sujie Zhu <sujiezhu@google.com>
Reviewed-by: default avatarManas Verma <manasverma@google.com>
Reviewed-by: default avatarJared Saul <jsaul@google.com>
Cr-Commit-Position: refs/heads/master@{#741525}
parent c8ecb2ae
...@@ -466,60 +466,91 @@ void CreditCardAccessManager::OnCVCAuthenticationComplete( ...@@ -466,60 +466,91 @@ void CreditCardAccessManager::OnCVCAuthenticationComplete(
unmask_auth_flow_type_); unmask_auth_flow_type_);
} }
// If user is not opted-in for FIDO authentication, fill the form immediately // Store request options temporarily if given. They will be used for
// -- a CVC check is adequate for an opted-out user. An opted-in user, // AdditionallyPerformFidoAuth.
// however, will require an additional WebAuthn check before the form can be base::Optional<base::Value> request_options = base::nullopt;
// filled. If CVC authentication failed, report error immediately. if (unmask_details_.fido_request_options.has_value()) {
if (unmask_auth_flow_type_ != UnmaskAuthFlowType::kCvcThenFido || // For opted-in user (CVC then FIDO case), request options are returned in
!response.did_succeed) { // unmask detail response.
accessor_->OnCreditCardFetched(response.did_succeed, response.card, request_options = unmask_details_.fido_request_options->Clone();
response.cvc); } else if (response.request_options.has_value()) {
unmask_auth_flow_type_ = UnmaskAuthFlowType::kNone; // For Android users, request_options are provided from GetRealPan if the
// user has chosen to opt-in.
request_options = response.request_options->Clone();
} }
if (!response.did_succeed || response.card_authorization_token.empty()) // Local boolean denotes whether to fill the form immediately. If CVC
return; // authentication failed, report error immediately. If GetRealPan did not
// return card authorization token (we can't call any FIDO-related flows,
// either opt-in or register new card, without token), fill the form
// immediately.
bool should_respond_immediately =
!response.did_succeed || response.card_authorization_token.empty();
#if defined(OS_ANDROID)
// GetRealPan did not return RequestOptions (user did not specify intent to
// opt-in) AND flow is not registering a new card, also fill the form
// directly.
should_respond_immediately |=
(!response.request_options.has_value() &&
unmask_auth_flow_type_ != UnmaskAuthFlowType::kCvcThenFido);
#else
// On desktop, if flow is not kCvcThenFido, it means it is not registering a
// new card, we can fill the form immediately.
should_respond_immediately |=
unmask_auth_flow_type_ != UnmaskAuthFlowType::kCvcThenFido;
#endif
// Now that unmask flow is complete and form is filled, the remaining flows // Local boolean denotes whether to call AdditionallyPerformFidoAuth which
// will be completely handed over to CreditCardFIDOAuthenticator. // delays the form filling and invokes an Authorization flow. If
// |unmask_auth_flow_type_| is kCvcThenFido, then the user is already opted-in
// and the new card must additionally be authorized through WebAuthn. Note
// that this and |should_respond_immediately| are mutually exclusive (can not
// both be true).
bool should_authorize_with_fido =
unmask_auth_flow_type_ == UnmaskAuthFlowType::kCvcThenFido;
#if defined(OS_ANDROID) #if defined(OS_ANDROID)
// If the GetRealPan response includes |creation_options| or // For Android, we will delay the form filling for both intent-to-opt-in user
// |request_options|, that means the user showed intention to opt-in while // opting in and opted-in user registering a new card (kCvcThenFido). So we
// unmasking (this can only happen on Android) and must complete the challenge // check one more scenario for Android here. If the GetRealPan response
// before successfully opting-in. // includes |request_options|, that means the user showed intention to opt-in
if (response.creation_options.has_value()) { // while unmasking and must complete the challenge before successfully
DCHECK(response.creation_options->is_dict()); // opting-in and filling the form.
GetOrCreateFIDOAuthenticator()->Register( should_authorize_with_fido |= response.request_options.has_value();
response.card_authorization_token, response.creation_options->Clone()); #endif
} else if (response.request_options.has_value()) { // Card authorization token is required in order to call
DCHECK(response.request_options->is_dict()); // AdditionallyPerformFidoAuth.
GetOrCreateFIDOAuthenticator()->Authorize( should_authorize_with_fido &= !response.card_authorization_token.empty();
weak_ptr_factory_.GetWeakPtr(), response.card_authorization_token,
response.request_options->Clone()); // Local boolean denotes whether to show the dialog that offers opting-in to
// FIDO authentication after the CVC check. Note that this and
// |should_respond_immediately| are NOT mutually exclusive. If both are true,
// it represents the Desktop opt-in flow (fill the form first, and prompt the
// opt-in dialog).
bool should_offer_fido_auth = false;
// For iOS, FIDO auth is not supported yet. For Android, users have already
// been offered opt-in at this point.
#if !defined(OS_IOS) && !defined(OS_ANDROID)
should_offer_fido_auth = unmask_details_.offer_fido_opt_in &&
!response.card_authorization_token.empty() &&
!GetOrCreateFIDOAuthenticator()
->GetOrCreateFidoAuthenticationStrikeDatabase()
->IsMaxStrikesLimitReached();
#endif
// Ensure that |should_respond_immediately| and |should_authorize_with_fido|
// can't be true at the same time
DCHECK(!(should_respond_immediately && should_authorize_with_fido));
if (should_respond_immediately) {
accessor_->OnCreditCardFetched(response.did_succeed, response.card,
response.cvc);
unmask_auth_flow_type_ = UnmaskAuthFlowType::kNone;
} else if (should_authorize_with_fido) {
AdditionallyPerformFidoAuth(response, request_options->Clone());
} }
#elif !defined(OS_IOS)
// If on Desktop and eligible, show the dialog that offers opting-in to FIDO
// authentication in the future.
// If |unmask_auth_flow_type_| is kCvcThenFido, then the user is already
// opted-in and the card must additionally be authorized through WebAuthn.
bool should_offer_fido_auth =
unmask_details_.offer_fido_opt_in &&
!GetOrCreateFIDOAuthenticator()
->GetOrCreateFidoAuthenticationStrikeDatabase()
->IsMaxStrikesLimitReached();
if (should_offer_fido_auth) { if (should_offer_fido_auth) {
// CreditCardFIDOAuthenticator will handle enrollment completely.
ShowWebauthnOfferDialog(response.card_authorization_token); ShowWebauthnOfferDialog(response.card_authorization_token);
} else if (unmask_auth_flow_type_ == UnmaskAuthFlowType::kCvcThenFido) {
DCHECK(unmask_details_.fido_request_options.has_value());
// Save credit card for after authorization.
card_ = std::make_unique<CreditCard>(*(response.card));
cvc_ = response.cvc;
GetOrCreateFIDOAuthenticator()->Authorize(
weak_ptr_factory_.GetWeakPtr(), response.card_authorization_token,
std::move(unmask_details_.fido_request_options.value()));
} }
#endif
} }
#if !defined(OS_IOS) #if !defined(OS_IOS)
...@@ -635,4 +666,17 @@ void CreditCardAccessManager::SignalCanFetchUnmaskDetails() { ...@@ -635,4 +666,17 @@ void CreditCardAccessManager::SignalCanFetchUnmaskDetails() {
can_fetch_unmask_details_.Signal(); can_fetch_unmask_details_.Signal();
} }
void CreditCardAccessManager::AdditionallyPerformFidoAuth(
const CreditCardCVCAuthenticator::CVCAuthenticationResponse& response,
base::Value request_options) {
#if !defined(OS_IOS)
// Save credit card for after authorization.
card_ = std::make_unique<CreditCard>(*(response.card));
cvc_ = response.cvc;
GetOrCreateFIDOAuthenticator()->Authorize(weak_ptr_factory_.GetWeakPtr(),
response.card_authorization_token,
request_options.Clone());
#endif
}
} // namespace autofill } // namespace autofill
...@@ -204,6 +204,13 @@ class CreditCardAccessManager : public CreditCardCVCAuthenticator::Requester, ...@@ -204,6 +204,13 @@ class CreditCardAccessManager : public CreditCardCVCAuthenticator::Requester,
// after a timeout. // after a timeout.
void SignalCanFetchUnmaskDetails(); void SignalCanFetchUnmaskDetails();
// Additionlly authorizes the card with FIDO. It also delays the form filling.
// It should only be called when registering a new card or opting-in from
// Android.
void AdditionallyPerformFidoAuth(
const CreditCardCVCAuthenticator::CVCAuthenticationResponse& response,
base::Value request_options);
// The current form of authentication in progress. // The current form of authentication in progress.
UnmaskAuthFlowType unmask_auth_flow_type_ = UnmaskAuthFlowType::kNone; UnmaskAuthFlowType unmask_auth_flow_type_ = UnmaskAuthFlowType::kNone;
......
...@@ -363,6 +363,11 @@ void CreditCardFIDOAuthenticator::OnDidGetAssertion( ...@@ -363,6 +363,11 @@ void CreditCardFIDOAuthenticator::OnDidGetAssertion(
// Treat failure to perform user verification as a strong signal not to // Treat failure to perform user verification as a strong signal not to
// offer opt-in in the future. // offer opt-in in the future.
if (current_flow_ == OPT_IN_WITH_CHALLENGE_FLOW) { if (current_flow_ == OPT_IN_WITH_CHALLENGE_FLOW) {
#if defined(OS_ANDROID)
// For Android, even if GetAssertion fails for opting-in, we still report
// success to |requester_| to fill the form with the fetched card info.
requester_->OnFidoAuthorizationComplete(/*did_succeed=*/true);
#endif // defined(OS_ANDROID)
GetOrCreateFidoAuthenticationStrikeDatabase()->AddStrikes( GetOrCreateFidoAuthenticationStrikeDatabase()->AddStrikes(
FidoAuthenticationStrikeDatabase:: FidoAuthenticationStrikeDatabase::
kStrikesToAddWhenUserVerificationFailsOnOptInAttempt); kStrikesToAddWhenUserVerificationFailsOnOptInAttempt);
...@@ -386,10 +391,17 @@ void CreditCardFIDOAuthenticator::OnDidGetAssertion( ...@@ -386,10 +391,17 @@ void CreditCardFIDOAuthenticator::OnDidGetAssertion(
} else { } else {
DCHECK(current_flow_ == FOLLOWUP_AFTER_CVC_AUTH_FLOW || DCHECK(current_flow_ == FOLLOWUP_AFTER_CVC_AUTH_FLOW ||
current_flow_ == OPT_IN_WITH_CHALLENGE_FLOW); current_flow_ == OPT_IN_WITH_CHALLENGE_FLOW);
// The user facing portion of the authorization is complete, which should be // The user-facing portion of the authorization is complete, which should be
// reported so that the form can be filled if in the FOLLOWUP_AFTER_CVC // reported so that the form can be filled.
// flow. bool should_respond_to_requester =
if (current_flow_ == FOLLOWUP_AFTER_CVC_AUTH_FLOW) (current_flow_ == FOLLOWUP_AFTER_CVC_AUTH_FLOW);
#if defined(OS_ANDROID)
// For Android, opt-in flow (OPT_IN_WITH_CHALLENGE_FLOW) also delays form
// filling.
should_respond_to_requester |=
(current_flow_ == OPT_IN_WITH_CHALLENGE_FLOW);
#endif
if (should_respond_to_requester)
requester_->OnFidoAuthorizationComplete(/*did_succeed=*/true); requester_->OnFidoAuthorizationComplete(/*did_succeed=*/true);
base::Value response = base::Value(base::Value::Type::DICTIONARY); base::Value response = base::Value(base::Value::Type::DICTIONARY);
......
...@@ -564,6 +564,8 @@ TEST_F(CreditCardFIDOAuthenticatorTest, ...@@ -564,6 +564,8 @@ TEST_F(CreditCardFIDOAuthenticatorTest,
AutofillMetrics::WebauthnOptInParameters::kWithCreationChallenge, 1); AutofillMetrics::WebauthnOptInParameters::kWithCreationChallenge, 1);
} }
#if !defined(OS_ANDROID)
// This test is not applicable for Android (we won't opt-in with Register).
TEST_F(CreditCardFIDOAuthenticatorTest, TEST_F(CreditCardFIDOAuthenticatorTest,
Register_OptInAttemptReturnsRequestOptions) { Register_OptInAttemptReturnsRequestOptions) {
scoped_feature_list_.InitAndEnableFeature( scoped_feature_list_.InitAndEnableFeature(
...@@ -589,6 +591,7 @@ TEST_F(CreditCardFIDOAuthenticatorTest, ...@@ -589,6 +591,7 @@ TEST_F(CreditCardFIDOAuthenticatorTest,
/*user_is_opted_in=*/true); /*user_is_opted_in=*/true);
EXPECT_TRUE(fido_authenticator_->IsUserOptedIn()); EXPECT_TRUE(fido_authenticator_->IsUserOptedIn());
} }
#endif
TEST_F(CreditCardFIDOAuthenticatorTest, Register_NewCardAuthorization) { TEST_F(CreditCardFIDOAuthenticatorTest, Register_NewCardAuthorization) {
scoped_feature_list_.InitAndEnableFeature( scoped_feature_list_.InitAndEnableFeature(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment