Commit 6b8c8108 authored by Matthias Körber's avatar Matthias Körber Committed by Commit Bot

[Autofill] Crowdsourcing for CVC fields.

With this feature, votes for the |CREDIT_CARD_VERIFICATION_CODE|-type
are generated for fields which either have the CVC value, which was used
to unlock the server-cards, or are heuristically detected to be most
likely the CVC field of a credit-card form.

Bug: 
Change-Id: If3b54afb801dec4aec5856a5f97a68cee56870fb
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1762215
Commit-Queue: Matthias Körber <koerber@google.com>
Reviewed-by: default avatarMaxim Kolosovskiy <kolos@chromium.org>
Reviewed-by: default avatarChristos Froussios <cfroussios@chromium.org>
Cr-Commit-Position: refs/heads/master@{#692111}
parent fda83664
......@@ -57,8 +57,6 @@
#include "components/autofill/core/browser/geo/country_names.h"
#include "components/autofill/core/browser/geo/phone_number_i18n.h"
#include "components/autofill/core/browser/logging/log_manager.h"
#include "components/autofill/core/browser/metrics/address_form_event_logger.h"
#include "components/autofill/core/browser/metrics/credit_card_form_event_logger.h"
#include "components/autofill/core/browser/metrics/form_events.h"
#include "components/autofill/core/browser/payments/credit_card_access_manager.h"
#include "components/autofill/core/browser/payments/payments_client.h"
......@@ -255,6 +253,93 @@ void LogAutofillTypePredictionsAvailable(
<< std::move(buffer);
}
// Finds the first field in |form_structure| with |field.value|=|value|.
AutofillField* FindFirstFieldWithValue(const FormStructure& form_structure,
const base::string16& value) {
for (const auto& field : form_structure) {
base::string16 trimmed_value;
base::TrimWhitespace(field->value, base::TRIM_ALL, &trimmed_value);
if (trimmed_value == value)
return field.get();
}
return nullptr;
}
// Heuristically identifies all possible credit card verification fields.
AutofillField* HeuristicallyFindCVCField(const FormStructure& form_structure) {
// Stores a pointer to the explicitly found expiration year.
bool found_explicit_expiration_year_field = false;
// The first pass checks the existence of an explicitly marked field for the
// credit card expiration year.
for (const auto& field : form_structure) {
const ServerFieldTypeSet& type_set = field->possible_types();
if (type_set.find(CREDIT_CARD_EXP_2_DIGIT_YEAR) != type_set.end() ||
type_set.find(CREDIT_CARD_EXP_4_DIGIT_YEAR) != type_set.end()) {
found_explicit_expiration_year_field = true;
break;
}
}
// Keeps track if a credit card number field was found.
bool credit_card_number_found = false;
// In the second pass, the CVC field is heuristically searched for.
// A field is considered a CVC field, iff:
// * it appears after the credit card number field;
// * it has the |UNKNOWN_TYPE| prediction;
// * it does not look like an expiration year or an expiration year was
// already found;
// * it is filled with a 3-4 digit number;
for (const auto& field : form_structure) {
const ServerFieldTypeSet& type_set = field->possible_types();
// Checks if the field is of |CREDIT_CARD_NUMBER| type.
if (type_set.find(CREDIT_CARD_NUMBER) != type_set.end()) {
credit_card_number_found = true;
continue;
}
// Skip the field if no credit card number was found yet.
if (!credit_card_number_found) {
continue;
}
// Don't consider fields that already have any prediction.
if (type_set.find(UNKNOWN_TYPE) == type_set.end())
continue;
// |UNKNOWN_TYPE| should come alone.
DCHECK_EQ(1u, type_set.size());
base::string16 trimmed_value;
base::TrimWhitespace(field->value, base::TRIM_ALL, &trimmed_value);
// Skip the field if it can be confused with a expiration year.
if (!found_explicit_expiration_year_field &&
IsPlausible4DigitExpirationYear(trimmed_value)) {
continue;
}
// Skip the field if its value does not like a CVC value.
if (!IsPlausibleCreditCardCVCNumber(trimmed_value))
continue;
return field.get();
}
return nullptr;
}
// Iff the CVC of the credit card is known, find the first field with this
// value. Otherwise, heuristically search for the CVC field if any.
AutofillField* FindBestPossibleCVCField(
const FormStructure& form_structure,
base::string16 last_unlocked_credit_card_cvc) {
if (!last_unlocked_credit_card_cvc.empty())
return FindFirstFieldWithValue(form_structure,
last_unlocked_credit_card_cvc);
return HeuristicallyFindCVCField(form_structure);
}
} // namespace
AutofillManager::FillingContext::FillingContext() = default;
......@@ -565,8 +650,8 @@ bool AutofillManager::MaybeStartVoteUploadProcess(
// number of outstanding tasks. https://crbug.com/974249
{base::ThreadPool(), base::MayBlock(), base::TaskPriority::USER_VISIBLE},
base::BindOnce(&AutofillManager::DeterminePossibleFieldTypesForUpload,
copied_profiles, copied_credit_cards, app_locale_,
raw_form),
copied_profiles, copied_credit_cards,
last_unlocked_credit_card_cvc_, app_locale_, raw_form),
base::BindOnce(&AutofillManager::UploadFormDataAsyncCallback,
weak_ptr_factory_.GetWeakPtr(),
base::Owned(form_structure.release()),
......@@ -1209,6 +1294,8 @@ void AutofillManager::OnCreditCardFetched(bool did_succeed,
return;
}
last_unlocked_credit_card_cvc_ = cvc;
FormStructure* form_structure = nullptr;
AutofillField* autofill_field = nullptr;
if (!GetCachedFormAndField(credit_card_form_, credit_card_field_,
......@@ -1304,6 +1391,11 @@ void AutofillManager::UploadFormData(const FormStructure& submitted_form,
ServerFieldTypeSet non_empty_types;
personal_data_->GetNonEmptyTypes(&non_empty_types);
// AS CVC is not stored, treat it separately.
if (!last_unlocked_credit_card_cvc_.empty() ||
non_empty_types.find(CREDIT_CARD_NUMBER) != non_empty_types.end()) {
non_empty_types.insert(CREDIT_CARD_VERIFICATION_CODE);
}
download_manager_->StartUploadRequest(
submitted_form, was_autofilled, non_empty_types,
......@@ -1341,6 +1433,7 @@ void AutofillManager::Reset() {
credit_card_query_id_ = -1;
credit_card_form_ = FormData();
credit_card_field_ = FormFieldData();
last_unlocked_credit_card_cvc_.clear();
credit_card_action_ = AutofillDriver::FORM_DATA_ACTION_PREVIEW;
initial_interaction_timestamp_ = TimeTicks();
external_delegate_->Reset();
......@@ -1472,9 +1565,9 @@ void AutofillManager::FillOrPreviewDataModelForm(
filling_contexts_map_.find(form_structure->GetIdentifierForRefill());
if (itr != filling_contexts_map_.end())
filling_context = itr->second.get();
bool could_attempt_refill =
filling_context != nullptr && !filling_context->attempted_refill &&
!is_refill && !is_credit_card;
bool could_attempt_refill = filling_context != nullptr &&
!filling_context->attempted_refill &&
!is_refill && !is_credit_card;
for (size_t i = 0; i < form_structure->field_count(); ++i) {
// On the renderer, the section is used regardless of the autofill status.
......@@ -1863,13 +1956,13 @@ void AutofillManager::UpdateInitialInteractionTimestamp(
void AutofillManager::DeterminePossibleFieldTypesForUpload(
const std::vector<AutofillProfile>& profiles,
const std::vector<CreditCard>& credit_cards,
const base::string16& last_unlocked_credit_card_cvc,
const std::string& app_locale,
FormStructure* submitted_form) {
// For each field in the |submitted_form|, extract the value. Then for each
// profile or credit card, identify any stored types that match the value.
for (size_t i = 0; i < submitted_form->field_count(); ++i) {
AutofillField* field = submitted_form->field(i);
if (!field->possible_types().empty() && field->IsEmpty()) {
// This is a password field in a sign-in form. Skip checking its type
// since |field->value| is not set.
......@@ -1907,6 +2000,16 @@ void AutofillManager::DeterminePossibleFieldTypesForUpload(
field->set_possible_types(matching_types);
}
// As CVCs are not stored, run special heuristics to detect CVC-like values.
AutofillField* cvc_field =
FindBestPossibleCVCField(*submitted_form, last_unlocked_credit_card_cvc);
if (cvc_field) {
ServerFieldTypeSet possible_types = cvc_field->possible_types();
possible_types.erase(UNKNOWN_TYPE);
possible_types.insert(CREDIT_CARD_VERIFICATION_CODE);
cvc_field->set_possible_types(possible_types);
}
AutofillManager::DisambiguateUploadTypes(submitted_form);
}
......
......@@ -7,6 +7,7 @@
#include <map>
#include <memory>
#include <set>
#include <string>
#include <vector>
......@@ -473,6 +474,7 @@ class AutofillManager : public AutofillHandler,
static void DeterminePossibleFieldTypesForUpload(
const std::vector<AutofillProfile>& profiles,
const std::vector<CreditCard>& credit_cards,
const base::string16& last_unlocked_credit_card_cvc,
const std::string& app_locale,
FormStructure* submitted_form);
......@@ -591,6 +593,7 @@ class AutofillManager : public AutofillHandler,
FormData credit_card_form_;
FormFieldData credit_card_field_;
CreditCard credit_card_;
base::string16 last_unlocked_credit_card_cvc_;
// Ablation experiment turns off autofill, but logging still has to be kept
// for metrics analysis.
......@@ -641,6 +644,19 @@ class AutofillManager : public AutofillHandler,
DeterminePossibleFieldTypesForUploadStressTest);
FRIEND_TEST_ALL_PREFIXES(AutofillManagerTest, DisambiguateUploadTypes);
FRIEND_TEST_ALL_PREFIXES(AutofillManagerTest, CrowdsourceUPIVPA);
FRIEND_TEST_ALL_PREFIXES(AutofillManagerTest, CrowdsourceCVCFieldByValue);
FRIEND_TEST_ALL_PREFIXES(AutofillManagerTest,
CrowdsourceCVCFieldAfterExpDateByHeuristics);
FRIEND_TEST_ALL_PREFIXES(AutofillManagerTest,
CrowdsourceCVCFieldDisableHeurisitcs);
FRIEND_TEST_ALL_PREFIXES(AutofillManagerTest,
CrowdsourceNoCVCDueToInvalidCandidateValue);
FRIEND_TEST_ALL_PREFIXES(AutofillManagerTest,
CrowdsourceNoCVCFieldDueToMissingCreditCardNumber);
FRIEND_TEST_ALL_PREFIXES(AutofillManagerTest,
CrowdsourceCVCFieldAfterInvalidExpDateByHeuristics);
FRIEND_TEST_ALL_PREFIXES(AutofillManagerTest,
CrowdsourceCVCFieldBeforeExpDateByHeuristics);
FRIEND_TEST_ALL_PREFIXES(AutofillManagerTest,
DisabledAutofillDispatchesError);
FRIEND_TEST_ALL_PREFIXES(AutofillManagerTest,
......
......@@ -20,6 +20,7 @@
#include "base/logging.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram_macros.h"
#include "base/no_destructor.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
......@@ -73,8 +74,8 @@ const char kShippingMode[] = "shipping";
const int kCommonNamePrefixRemovalFieldThreshold = 3;
const int kMinCommonNamePrefixLength = 16;
// Returns true if the scheme given by |url| is one for which autfill is allowed
// to activate. By default this only returns true for HTTP and HTTPS.
// Returns true if the scheme given by |url| is one for which autofill is
// allowed to activate. By default this only returns true for HTTP and HTTPS.
bool HasAllowedScheme(const GURL& url) {
return url.SchemeIsHTTPOrHTTPS() ||
base::FeatureList::IsEnabled(
......
......@@ -91,7 +91,7 @@ bool HasCorrectLength(const base::string16& number) {
return true;
}
// TODO (crbug.com/927767): Add unit tests for this function.
// TODO(crbug.com/927767): Add unit tests for this function.
bool PassesLuhnCheck(const base::string16& number) {
// Use the Luhn formula [3] to validate the number.
// [3] http://en.wikipedia.org/wiki/Luhn_algorithm
......@@ -351,4 +351,12 @@ bool IsInternationalBankAccountNumber(const base::string16& value) {
base::ASCIIToUTF16(kInternationalBankAccountNumberRe));
}
bool IsPlausibleCreditCardCVCNumber(const base::string16& value) {
return MatchesPattern(value, base::ASCIIToUTF16(kCreditCardCVCPattern));
}
bool IsPlausible4DigitExpirationYear(const base::string16& value) {
return MatchesPattern(value,
base::ASCIIToUTF16(kCreditCard4DigitExpYearPattern));
}
} // namespace autofill
......@@ -93,6 +93,12 @@ bool IsUPIVirtualPaymentAddress(const base::string16& value);
// (IBAN). See https://en.wikipedia.org/wiki/International_Bank_Account_Number
bool IsInternationalBankAccountNumber(const base::string16& value);
// Return true if |value| is a 3 or 4 digit number.
bool IsPlausibleCreditCardCVCNumber(const base::string16& value);
// Returns true if the value is a 4 digit year in this century.
bool IsPlausible4DigitExpirationYear(const base::string16& value);
} // namespace autofill
#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_VALIDATION_H_
......@@ -38,26 +38,17 @@ struct SecurityCodeCardTypePair {
// From https://www.paypalobjects.com/en_US/vhelp/paypalmanager_help/credit_card_numbers.htm
const char* const kValidNumbers[] = {
"378282246310005",
"3714 4963 5398 431",
"3787-3449-3671-000",
"5610591081018250",
"3056 9309 0259 04",
"3852-0000-0232-37",
"6011111111111117",
"6011 0009 9013 9424",
"3530-1113-3330-0000",
"378282246310005", "3714 4963 5398 431", "3787-3449-3671-000",
"5610591081018250", "3056 9309 0259 04", "3852-0000-0232-37",
"6011111111111117", "6011 0009 9013 9424", "3530-1113-3330-0000",
"3566002020360505",
"5555 5555 5555 4444", // Mastercard.
"5105-1051-0510-5100",
"4111111111111111", // Visa.
"4012 8888 8888 1881",
"4222-2222-2222-2",
"5019717010103742",
"6331101999990016",
"6247130048162403",
"4532261615476013542", // Visa, 19 digits.
"6362970000457013", // Elo
"4012 8888 8888 1881", "4222-2222-2222-2", "5019717010103742",
"6331101999990016", "6247130048162403",
"4532261615476013542", // Visa, 19 digits.
"6362970000457013", // Elo
};
const char* const kInvalidNumbers[] = {
"4111 1111 112", /* too short */
......@@ -78,15 +69,15 @@ const IntExpirationDate kInvalidCreditCardIntExpirationDate[] = {
{ 2015, 0 }, // Zero is legal in the CC class but is not a valid date.
};
const SecurityCodeCardTypePair kValidSecurityCodeCardTypePairs[] = {
{ "323", kGenericCard }, // 3-digit CSC.
{ "3234", kAmericanExpressCard }, // 4-digit CSC.
{"323", kGenericCard}, // 3-digit CSC.
{"3234", kAmericanExpressCard}, // 4-digit CSC.
};
const SecurityCodeCardTypePair kInvalidSecurityCodeCardTypePairs[] = {
{ "32", kGenericCard }, // CSC too short.
{ "323", kAmericanExpressCard }, // CSC too short.
{ "3234", kGenericCard }, // CSC too long.
{ "12345", kAmericanExpressCard }, // CSC too long.
{ "asd", kGenericCard }, // non-numeric CSC.
{"32", kGenericCard}, // CSC too short.
{"323", kAmericanExpressCard}, // CSC too short.
{"3234", kGenericCard}, // CSC too long.
{"12345", kAmericanExpressCard}, // CSC too long.
{"asd", kGenericCard}, // non-numeric CSC.
};
const char* const kValidEmailAddress[] = {
"user@example",
......@@ -100,6 +91,14 @@ const char* const kInvalidEmailAddress[] = {
"user@",
"user@=example.com"
};
const char* const kUnplausibleCreditCardExpirationYears[] = {
"2009", "2134", "1111", "abcd", "2101"};
const char* const kPlausibleCreditCardExpirationYears[] = {"2018", "2099",
"2010", "2050"};
const char* const kUnplausibleCreditCardCVCNumbers[] = {"abc", "21", "11111",
"21a1"};
const char* const kPlausibleCreditCardCVCNumbers[] = {"1234", "2099", "111",
"982"};
} // namespace
TEST(AutofillValidation, IsValidCreditCardNumber) {
......@@ -113,6 +112,36 @@ TEST(AutofillValidation, IsValidCreditCardNumber) {
}
}
// Tests the plausibility of supplied credit card expiration years.
TEST(AutofillValidation, IsPlausibleCreditCardExparationYear) {
for (const char* plausible_year : kPlausibleCreditCardExpirationYears) {
EXPECT_TRUE(
IsPlausible4DigitExpirationYear(base::ASCIIToUTF16(plausible_year)))
<< plausible_year;
}
for (const char* unplausible_year : kUnplausibleCreditCardExpirationYears) {
EXPECT_FALSE(
IsPlausible4DigitExpirationYear(base::ASCIIToUTF16(unplausible_year)))
<< unplausible_year;
}
}
// Test the plausibility of supplied CVC numbers.
TEST(AutofillValidation, IsPlausibleCreditCardCVCNumber) {
for (const char* plausible_cvc : kPlausibleCreditCardCVCNumbers) {
EXPECT_TRUE(
IsPlausibleCreditCardCVCNumber(base::ASCIIToUTF16(plausible_cvc)))
<< plausible_cvc;
}
for (const char* unplausible_cvc : kUnplausibleCreditCardCVCNumbers) {
EXPECT_FALSE(
IsPlausibleCreditCardCVCNumber(base::ASCIIToUTF16(unplausible_cvc)))
<< unplausible_cvc;
}
}
TEST(AutofillValidation, IsValidCreditCardIntExpirationDate) {
base::Time now;
ASSERT_TRUE(base::Time::FromString(kCurrentDate, &now));
......@@ -347,8 +376,8 @@ TEST_P(AutofillCCNumberValidationTest, IsValidCreditCardNumber) {
}
}
const static std::set<std::string> kAllBasicCardNetworks{
"amex", "discover", "diners", "elo", "jcb",
static const std::set<std::string> kAllBasicCardNetworks{
"amex", "discover", "diners", "elo", "jcb",
"mastercard", "mir", "unionpay", "visa"};
INSTANTIATE_TEST_SUITE_P(
......
......@@ -501,6 +501,12 @@ const char kUPIVirtualPaymentAddressRe[] =
const char kInternationalBankAccountNumberRe[] =
"^[a-zA-Z]{2}[0-9]{2}[a-zA-Z0-9]{4}[0-9]{7}([a-zA-Z0-9]?){0,16}$";
// Matches all 3 and 4 digit numbers.
const char kCreditCardCVCPattern[] = "^\\d{3,4}$";
// Matches numbers in the range [2010-2099].
const char kCreditCard4DigitExpYearPattern[] = "^[2][0][1-9][0-9]$";
/////////////////////////////////////////////////////////////////////////////
// form_structure.cc
/////////////////////////////////////////////////////////////////////////////
......
......@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_REGEX_CONSTANTS_H_
#define COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_REGEX_CONSTANTS_H_
#ifndef COMPONENTS_AUTOFILL_CORE_COMMON_AUTOFILL_REGEX_CONSTANTS_H_
#define COMPONENTS_AUTOFILL_CORE_COMMON_AUTOFILL_REGEX_CONSTANTS_H_
namespace autofill {
......@@ -61,6 +61,8 @@ extern const char kTravelOriginRe[];
extern const char kTravelDestinationRe[];
extern const char kFlightRe[];
extern const char kPriceRe[];
extern const char kCreditCardCVCPattern[];
extern const char kCreditCard4DigitExpYearPattern[];
// Used to match field data that might be a UPI Virtual Payment Address.
// See:
......@@ -85,4 +87,4 @@ extern const char kUrlSearchActionRe[];
} // namespace autofill
#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_REGEX_CONSTANTS_H_
#endif // COMPONENTS_AUTOFILL_CORE_COMMON_AUTOFILL_REGEX_CONSTANTS_H_
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