Commit 46a57310 authored by Manas Verma's avatar Manas Verma Committed by Commit Bot

[Autofill Auth] Updating GetRealPan and OptChange request/response

Adding an authorization token in the GetRealPan response and the OptChange
request in order to allow new cards to be authorized for unmasking through
WebAuthn.

GetRealPan and OptChange response will also include |request_options| for
authorization and opt-in scenarios.


Bug: 949269
Change-Id: I3e35afa4dc39e9222e0f49368df231b73f6a88de
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1761655
Commit-Queue: Manas Verma <manasverma@google.com>
Reviewed-by: default avatarJared Saul <jsaul@google.com>
Cr-Commit-Position: refs/heads/master@{#695878}
parent 4dd5a83f
......@@ -228,12 +228,7 @@ class CreditCardAccessManagerTest : public testing::Test {
payments::PaymentsClient::UnmaskResponseDetails response;
#if !defined(OS_IOS)
if (fido_opt_in) {
response.fido_creation_options =
base::Value(base::Value::Type::DICTIONARY);
response.fido_creation_options->SetKey("relying_party_id",
base::Value(kGooglePaymentsRpid));
response.fido_creation_options->SetKey("challenge",
base::Value(kTestChallenge));
response.fido_creation_options = GetTestCreationOptions();
}
#endif
full_card_request->OnDidGetRealPan(result,
......@@ -242,6 +237,29 @@ class CreditCardAccessManagerTest : public testing::Test {
}
#if !defined(OS_IOS)
base::Value GetTestRequestOptions() {
base::Value request_options = base::Value(base::Value::Type::DICTIONARY);
request_options.SetKey("challenge", base::Value(kTestChallenge));
request_options.SetKey("relying_party_id",
base::Value(kGooglePaymentsRpid));
base::Value key_info(base::Value::Type::DICTIONARY);
key_info.SetKey("credential_id", base::Value(kCredentialId));
request_options.SetKey("key_info", base::Value(base::Value::Type::LIST));
request_options.FindKeyOfType("key_info", base::Value::Type::LIST)
->GetList()
.push_back(std::move(key_info));
return request_options;
}
base::Value GetTestCreationOptions() {
base::Value creation_options = base::Value(base::Value::Type::DICTIONARY);
creation_options.SetKey("challenge", base::Value(kTestChallenge));
creation_options.SetKey("relying_party_id",
base::Value(kGooglePaymentsRpid));
return creation_options;
}
void SetUserOptedIn(bool user_is_opted_in) {
scoped_feature_list_.InitAndEnableFeature(
features::kAutofillCreditCardAuthentication);
......@@ -267,9 +285,17 @@ class CreditCardAccessManagerTest : public testing::Test {
// Mocks an OptChange response from Payments Client.
void OptChange(AutofillClient::PaymentsRpcResult result,
bool user_is_opted_in,
base::Value creation_options = base::Value()) {
GetFIDOAuthenticator()->OnDidGetOptChangeResult(
result, user_is_opted_in, std::move(creation_options));
bool include_creation_options = false,
bool include_request_options = false) {
payments::PaymentsClient::OptChangeResponseDetails response;
response.user_is_opted_in = user_is_opted_in;
if (include_creation_options) {
response.fido_creation_options = GetTestCreationOptions();
}
if (include_request_options) {
response.fido_request_options = GetTestRequestOptions();
}
GetFIDOAuthenticator()->OnDidGetOptChangeResult(result, response);
}
TestCreditCardFIDOAuthenticator* GetFIDOAuthenticator() {
......
......@@ -42,10 +42,16 @@ class CreditCardCVCAuthenticator
creation_options = std::move(v);
return *this;
}
CVCAuthenticationResponse& with_request_options(
base::Optional<base::Value> v) {
request_options = std::move(v);
return *this;
}
bool did_succeed = false;
const CreditCard* card = nullptr;
base::string16 cvc = base::string16();
base::Optional<base::Value> creation_options = base::nullopt;
base::Optional<base::Value> request_options = base::nullopt;
};
class Requester {
public:
......
......@@ -238,8 +238,7 @@ void CreditCardFIDOAuthenticator::OnDidMakeCredential(
void CreditCardFIDOAuthenticator::OnDidGetOptChangeResult(
AutofillClient::PaymentsRpcResult result,
bool user_is_opted_in,
base::Value creation_options) {
payments::PaymentsClient::OptChangeResponseDetails& response) {
DCHECK(current_flow_ == OPT_IN_WITHOUT_CHALLENGE_FLOW ||
current_flow_ == OPT_OUT_FLOW ||
current_flow_ == OPT_IN_WITH_CHALLENGE_FLOW);
......@@ -250,15 +249,19 @@ void CreditCardFIDOAuthenticator::OnDidGetOptChangeResult(
return;
}
// Update user preference to keep in sync with server.
if (response.user_is_opted_in.has_value()) {
::autofill::prefs::SetCreditCardFIDOAuthEnabled(
autofill_client_->GetPrefs(), response.user_is_opted_in.value());
}
// If response contains |creation_options| and the last opt-in attempt did not
// include a challenge, then invoke WebAuthn registration prompt. Otherwise,
// set pref to enable FIDO Authentication for card unmask and end the flow.
if (creation_options.is_dict() &&
// include a challenge, then invoke WebAuthn registration prompt. Otherwise
// end the flow.
if (response.fido_creation_options.has_value() &&
current_flow_ == OPT_IN_WITHOUT_CHALLENGE_FLOW) {
Register(std::move(creation_options));
Register(std::move(response.fido_creation_options.value()));
} else {
::autofill::prefs::SetCreditCardFIDOAuthEnabled(
autofill_client_->GetPrefs(), user_is_opted_in);
current_flow_ = NONE_FLOW;
}
}
......
......@@ -141,9 +141,9 @@ class CreditCardFIDOAuthenticator
MakeCredentialAuthenticatorResponsePtr attestation_response);
// Sets prefstore to enable credit card authentication if rpc was successful.
void OnDidGetOptChangeResult(AutofillClient::PaymentsRpcResult result,
bool user_is_opted_in,
base::Value creation_options = base::Value());
void OnDidGetOptChangeResult(
AutofillClient::PaymentsRpcResult result,
payments::PaymentsClient::OptChangeResponseDetails& response);
// The callback invoked from the WebAuthn offer dialog when it is accepted or
// declined/cancelled.
......
......@@ -218,9 +218,19 @@ class CreditCardFIDOAuthenticatorTest : public testing::Test {
// Mocks an OptChange response from Payments Client.
void OptChange(AutofillClient::PaymentsRpcResult result,
bool user_is_opted_in,
base::Value creation_options = base::Value()) {
fido_authenticator_->OnDidGetOptChangeResult(result, user_is_opted_in,
std::move(creation_options));
bool include_creation_options = false,
bool include_request_options = false) {
payments::PaymentsClient::OptChangeResponseDetails response;
response.user_is_opted_in = user_is_opted_in;
if (include_creation_options) {
response.fido_creation_options =
GetTestCreationOptions(kTestChallenge, kTestRelyingPartyId);
}
if (include_request_options) {
response.fido_request_options = GetTestRequestOptions(
kTestChallenge, kTestRelyingPartyId, kTestCredentialId);
}
fido_authenticator_->OnDidGetOptChangeResult(result, response);
}
protected:
......@@ -494,8 +504,7 @@ TEST_F(CreditCardFIDOAuthenticatorTest,
// Mock payments response with challenge to invoke enrollment flow.
OptChange(AutofillClient::PaymentsRpcResult::SUCCESS,
/*user_is_opted_in=*/false,
GetTestCreationOptions(kTestChallenge, kTestRelyingPartyId));
/*user_is_opted_in=*/false, /*include_creation_options=*/true);
EXPECT_EQ(CreditCardFIDOAuthenticator::Flow::OPT_IN_WITH_CHALLENGE_FLOW,
fido_authenticator_->current_flow());
EXPECT_FALSE(fido_authenticator_->IsUserOptedIn());
......
......@@ -418,6 +418,14 @@ class UnmaskCardRequest : public PaymentsRequest {
"fido_creation_options", base::Value::Type::DICTIONARY);
if (creation_options)
response_details_.fido_creation_options = creation_options->Clone();
const auto* request_options = response.FindKeyOfType(
"fido_request_options", base::Value::Type::DICTIONARY);
if (request_options)
response_details_.fido_request_options = request_options->Clone();
const auto* token = response.FindStringKey("card_authorization_token");
response_details_.card_authorization_token = token ? *token : std::string();
}
bool IsResponseComplete() override {
......@@ -443,7 +451,9 @@ class OptChangeRequest : public PaymentsRequest {
public:
OptChangeRequest(
const PaymentsClient::OptChangeRequestDetails& request_details,
OptChangeCallback callback,
base::OnceCallback<void(AutofillClient::PaymentsRpcResult,
PaymentsClient::OptChangeResponseDetails&)>
callback,
const bool full_sync_enabled)
: request_details_(request_details),
callback_(std::move(callback)),
......@@ -478,6 +488,12 @@ class OptChangeRequest : public PaymentsRequest {
std::move(request_details_.fido_authenticator_response));
}
if (!request_details_.card_authorization_token.empty()) {
request_dict.SetKey(
"card_authorization_token",
base::Value(request_details_.card_authorization_token));
}
std::string request_content;
base::JSONWriter::Write(request_dict, &request_content);
VLOG(3) << "autofillauthoptchange request body: " << request_content;
......@@ -488,28 +504,34 @@ class OptChangeRequest : public PaymentsRequest {
const auto* user_is_opted_in =
response.FindKeyOfType("user_is_opted_in", base::Value::Type::BOOLEAN);
if (user_is_opted_in)
user_is_opted_in_ = user_is_opted_in->GetBool();
response_details_.user_is_opted_in = user_is_opted_in->GetBool();
const auto* fido_creation_options = response.FindKeyOfType(
"fido_creation_options", base::Value::Type::DICTIONARY);
if (fido_creation_options)
fido_creation_options_ = fido_creation_options->Clone();
response_details_.fido_creation_options = fido_creation_options->Clone();
const auto* fido_request_options = response.FindKeyOfType(
"fido_request_options", base::Value::Type::DICTIONARY);
if (fido_request_options)
response_details_.fido_request_options = fido_request_options->Clone();
}
bool IsResponseComplete() override { return user_is_opted_in_.has_value(); }
bool IsResponseComplete() override {
return response_details_.user_is_opted_in.has_value();
}
void RespondToDelegate(AutofillClient::PaymentsRpcResult result) override {
std::move(callback_).Run(
result, user_is_opted_in_.value_or(!request_details_.opt_in),
std::move(fido_creation_options_));
std::move(callback_).Run(result, response_details_);
}
private:
PaymentsClient::OptChangeRequestDetails request_details_;
OptChangeCallback callback_;
base::OnceCallback<void(AutofillClient::PaymentsRpcResult,
PaymentsClient::OptChangeResponseDetails&)>
callback_;
const bool full_sync_enabled_;
base::Optional<bool> user_is_opted_in_;
base::Value fido_creation_options_;
PaymentsClient::OptChangeResponseDetails response_details_;
DISALLOW_COPY_AND_ASSIGN(OptChangeRequest);
};
......@@ -966,6 +988,12 @@ operator=(const PaymentsClient::UnmaskResponseDetails& other) {
} else {
fido_creation_options.reset();
}
if (other.fido_request_options.has_value()) {
fido_request_options = other.fido_request_options->Clone();
} else {
fido_request_options.reset();
}
card_authorization_token = other.card_authorization_token;
return *this;
}
......@@ -975,9 +1003,28 @@ PaymentsClient::OptChangeRequestDetails::OptChangeRequestDetails(
app_locale = other.app_locale;
opt_in = other.opt_in;
fido_authenticator_response = other.fido_authenticator_response.Clone();
card_authorization_token = other.card_authorization_token;
}
PaymentsClient::OptChangeRequestDetails::~OptChangeRequestDetails() {}
PaymentsClient::OptChangeResponseDetails::OptChangeResponseDetails() {}
PaymentsClient::OptChangeResponseDetails::OptChangeResponseDetails(
const OptChangeResponseDetails& other) {
user_is_opted_in = other.user_is_opted_in;
if (other.fido_creation_options.has_value()) {
fido_creation_options = other.fido_creation_options->Clone();
} else {
fido_creation_options.reset();
}
if (other.fido_request_options.has_value()) {
fido_request_options = other.fido_request_options->Clone();
} else {
fido_request_options.reset();
}
}
PaymentsClient::OptChangeResponseDetails::~OptChangeResponseDetails() {}
PaymentsClient::UploadRequestDetails::UploadRequestDetails() {}
PaymentsClient::UploadRequestDetails::UploadRequestDetails(
const UploadRequestDetails& other) = default;
......@@ -1025,8 +1072,11 @@ void PaymentsClient::UnmaskCard(
/*authenticate=*/true);
}
void PaymentsClient::OptChange(const OptChangeRequestDetails request_details,
OptChangeCallback callback) {
void PaymentsClient::OptChange(
const OptChangeRequestDetails request_details,
base::OnceCallback<void(AutofillClient::PaymentsRpcResult,
PaymentsClient::OptChangeResponseDetails&)>
callback) {
IssueRequest(std::make_unique<OptChangeRequest>(
request_details, std::move(callback),
account_info_getter_->IsSyncFeatureEnabled()),
......
......@@ -53,11 +53,6 @@ typedef base::OnceCallback<void(AutofillClient::PaymentsRpcResult,
AutofillClient::UnmaskDetails&)>
GetUnmaskDetailsCallback;
// Callback type for OptChange callback.
typedef base::OnceCallback<
void(AutofillClient::PaymentsRpcResult, bool, base::Value)>
OptChangeCallback;
// Billable service number is defined in Payments server to distinguish
// different requests.
const int kUnmaskCardBillableServiceNumber = 70154;
......@@ -107,7 +102,15 @@ class PaymentsClient {
}
std::string real_pan;
base::Optional<base::Value> fido_creation_options;
// Challenge required for enrolling user into FIDO authentication for future
// card unmasking.
base::Optional<base::Value> fido_creation_options = base::nullopt;
// Challenge required for authorizing user for FIDO authentication for
// future card unmasking.
base::Optional<base::Value> fido_request_options = base::nullopt;
// An opaque token used to logically chain consecutive UnmaskCard and
// OptChange calls together.
std::string card_authorization_token = std::string();
};
// Information required to either opt-in or opt-out a user for FIDO
......@@ -118,8 +121,32 @@ class PaymentsClient {
~OptChangeRequestDetails();
std::string app_locale;
// Set to true if user will be opted-in for FIDO authentication for card
// unmasking. False otherwise.
bool opt_in;
// Signature required for enrolling user into FIDO authentication for future
// card unmasking.
base::Value fido_authenticator_response;
// An opaque token used to logically chain consecutive UnmaskCard and
// OptChange calls together.
std::string card_authorization_token = std::string();
};
// Information retrieved from an OptChange request.
struct OptChangeResponseDetails {
OptChangeResponseDetails();
OptChangeResponseDetails(const OptChangeResponseDetails& other);
~OptChangeResponseDetails();
// Unset if response failed. True if user is opted-in for FIDO
// authentication for card unmasking. False otherwise.
base::Optional<bool> user_is_opted_in;
// Challenge required for enrolling user into FIDO authentication for future
// card unmasking.
base::Optional<base::Value> fido_creation_options;
// Challenge required for authorizing user for FIDO authentication for
// future card unmasking.
base::Optional<base::Value> fido_request_options;
};
// A collection of the information required to make a credit card upload
......@@ -206,8 +233,11 @@ class PaymentsClient {
// Opts-in or opts-out the user to use FIDO authentication for card unmasking
// on this device.
void OptChange(const OptChangeRequestDetails request_details,
OptChangeCallback callback);
void OptChange(
const OptChangeRequestDetails request_details,
base::OnceCallback<void(AutofillClient::PaymentsRpcResult,
PaymentsClient::OptChangeResponseDetails&)>
callback);
// Determine if the user meets the Payments service's conditions for upload.
// The service uses |addresses| (from which names and phone numbers are
......
......@@ -140,13 +140,15 @@ class PaymentsClientTest : public testing::Test {
unmask_response_details_ = &response;
}
void OnDidGetOptChangeResult(AutofillClient::PaymentsRpcResult result,
bool user_is_opted_in,
base::Value fido_creation_options) {
void OnDidGetOptChangeResult(
AutofillClient::PaymentsRpcResult result,
PaymentsClient::OptChangeResponseDetails& response) {
result_ = result;
user_is_opted_in_ = user_is_opted_in;
if (fido_creation_options.is_dict())
fido_creation_options_ = fido_creation_options.Clone();
opt_change_response_.user_is_opted_in = response.user_is_opted_in;
opt_change_response_.fido_creation_options =
std::move(response.fido_creation_options);
opt_change_response_.fido_request_options =
std::move(response.fido_request_options);
}
void OnDidGetUploadDetails(
......@@ -299,10 +301,8 @@ class PaymentsClientTest : public testing::Test {
// Server ID of a saved card via credit card upload save.
std::string server_id_;
// Status of the user's FIDO auth opt-in; returned from an OptChange call.
base::Optional<bool> user_is_opted_in_;
// FIDO auth enrollment creation options; returned from an OptChange call.
base::Value fido_creation_options_;
// The OptChangeResponseDetails retrieved from an OptChangeRequest.
PaymentsClient::OptChangeResponseDetails opt_change_response_;
// The UnmaskResponseDetails retrieved from an UnmaskRequest. Includes PAN.
PaymentsClient::UnmaskResponseDetails* unmask_response_details_ = nullptr;
// The legal message returned from a GetDetails upload save preflight call.
......@@ -530,7 +530,7 @@ TEST_F(PaymentsClientTest, OptInSuccess) {
IssueOAuthToken();
ReturnResponse(net::HTTP_OK, "{ \"user_is_opted_in\": true }");
EXPECT_EQ(AutofillClient::SUCCESS, result_);
EXPECT_TRUE(user_is_opted_in_.value());
EXPECT_TRUE(opt_change_response_.user_is_opted_in.value());
}
TEST_F(PaymentsClientTest, OptInServerUnresponsive) {
......@@ -538,7 +538,7 @@ TEST_F(PaymentsClientTest, OptInServerUnresponsive) {
IssueOAuthToken();
ReturnResponse(net::HTTP_REQUEST_TIMEOUT, "");
EXPECT_EQ(AutofillClient::NETWORK_ERROR, result_);
EXPECT_FALSE(user_is_opted_in_.value());
EXPECT_FALSE(opt_change_response_.user_is_opted_in.has_value());
}
TEST_F(PaymentsClientTest, OptOutSuccess) {
......@@ -546,7 +546,7 @@ TEST_F(PaymentsClientTest, OptOutSuccess) {
IssueOAuthToken();
ReturnResponse(net::HTTP_OK, "{ \"user_is_opted_in\": false }");
EXPECT_EQ(AutofillClient::SUCCESS, result_);
EXPECT_FALSE(user_is_opted_in_.value());
EXPECT_FALSE(opt_change_response_.user_is_opted_in.value());
}
TEST_F(PaymentsClientTest, EnrollAttemptReturnsCreationOptions) {
......@@ -556,9 +556,10 @@ TEST_F(PaymentsClientTest, EnrollAttemptReturnsCreationOptions) {
"{ \"user_is_opted_in\": false, \"fido_creation_options\": { "
"\"relying_party_id\": \"google.com\"} }");
EXPECT_EQ(AutofillClient::SUCCESS, result_);
EXPECT_FALSE(user_is_opted_in_.value());
EXPECT_FALSE(opt_change_response_.user_is_opted_in.value());
EXPECT_EQ("google.com",
*fido_creation_options_.FindStringKey("relying_party_id"));
*opt_change_response_.fido_creation_options->FindStringKey(
"relying_party_id"));
}
TEST_F(PaymentsClientTest, GetDetailsSuccess) {
......
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