blob: a4a596887e80c9d227fe7df677622e3b09ebcacc [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/payments/content/web_payments_table.h"
#include <time.h>
#include <string>
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/time/time.h"
#include "components/payments/content/browser_binding/browser_bound_key_metadata.h"
#include "components/payments/core/secure_payment_confirmation_credential.h"
#include "components/webdata/common/web_database.h"
#include "content/public/common/content_features.h"
#include "sql/statement.h"
#include "sql/transaction.h"
namespace payments {
namespace {
// Data valid duration in seconds.
const time_t PAYMENT_METHOD_MANIFEST_VALID_TIME_IN_SECONDS = 90 * 24 * 60 * 60;
WebDatabaseTable::TypeKey GetWebPaymentsKey() {
// We just need a unique constant. Use the address of a static that
// COMDAT folding won't touch in an optimizing linker.
static int table_key = 0;
return reinterpret_cast<void*>(&table_key);
}
} // namespace
WebPaymentsTable::WebPaymentsTable() = default;
WebPaymentsTable::~WebPaymentsTable() = default;
WebPaymentsTable* WebPaymentsTable::FromWebDatabase(WebDatabase* db) {
return static_cast<WebPaymentsTable*>(db->GetTable(GetWebPaymentsKey()));
}
WebDatabaseTable::TypeKey WebPaymentsTable::GetTypeKey() const {
return GetWebPaymentsKey();
}
bool WebPaymentsTable::CreateTablesIfNecessary() {
if (!db()->Execute("CREATE TABLE IF NOT EXISTS payment_method_manifest ( "
"expire_date INTEGER NOT NULL DEFAULT 0, "
"method_name VARCHAR, "
"web_app_id VARCHAR)")) {
LOG(ERROR) << "Cannot create the payment_method_manifest table";
return false;
}
// TODO(crbug.com/384940851): Update secure_payment_confirmation_instrument's
// primary key to the pair of (credential_id, relying_party_id).
// The `credential_id` column is 20 bytes for UbiKey on Linux, but the size
// can vary for different authenticators. The relatively small sizes make it
// OK to make `credential_id` the primary key.
if (!db()->Execute(
"CREATE TABLE IF NOT EXISTS secure_payment_confirmation_instrument ( "
"credential_id BLOB NOT NULL PRIMARY KEY, "
"relying_party_id VARCHAR NOT NULL, "
"label VARCHAR NOT NULL, "
"icon BLOB NOT NULL)")) {
LOG(ERROR)
<< "Cannot create the secure_payment_confirmation_instrument table";
return false;
}
if (!db()->DoesColumnExist("secure_payment_confirmation_instrument",
"date_created")) {
if (!db()->Execute(
"ALTER TABLE secure_payment_confirmation_instrument ADD COLUMN "
"date_created INTEGER NOT NULL DEFAULT 0")) {
LOG(ERROR)
<< "Cannot alter the secure_payment_confirmation_instrument table";
return false;
}
}
if (!db()->DoesColumnExist("secure_payment_confirmation_instrument",
"user_id")) {
if (!db()->Execute(
"ALTER TABLE secure_payment_confirmation_instrument ADD COLUMN "
"user_id BLOB")) {
LOG(ERROR)
<< "Cannot alter the secure_payment_confirmation_instrument table";
return false;
}
}
if (!db()->Execute("CREATE TABLE IF NOT EXISTS "
"secure_payment_confirmation_browser_bound_key ( "
"credential_id BLOB NOT NULL, "
"relying_party_id TEXT NOT NULL, "
"browser_bound_key_id BLOB, "
"PRIMARY KEY (credential_id, relying_party_id))")) {
LOG(ERROR)
<< "Cannot create the secure_payment_confirmation_browser_bound_key "
<< "table";
return false;
}
return true;
}
bool WebPaymentsTable::MigrateToVersion(int version,
bool* update_compatible_version) {
return true;
}
void WebPaymentsTable::RemoveExpiredData() {
const base::Time now_date_in_seconds = base::Time::NowFromSystemTime();
sql::Statement s(db()->GetUniqueStatement(
"DELETE FROM payment_method_manifest WHERE expire_date < ?"));
s.BindTime(0, now_date_in_seconds);
s.Run();
}
bool WebPaymentsTable::ClearSecurePaymentConfirmationCredentials(
base::Time begin,
base::Time end) {
// TODO(crbug.com/384959121): Clear browser bound key identifiers along with
// the associated browser bound keys.
sql::Statement s(db()->GetUniqueStatement(
"DELETE FROM secure_payment_confirmation_instrument WHERE (date_created "
">= ? AND date_created < ?) OR (date_created = 0)"));
s.BindTime(0, begin);
s.BindTime(1, end);
return s.Run();
}
bool WebPaymentsTable::AddManifest(
const std::string& payment_method,
const std::vector<std::string>& web_app_ids) {
sql::Transaction transaction(db());
if (!transaction.Begin()) {
return false;
}
sql::Statement s1(db()->GetUniqueStatement(
"DELETE FROM payment_method_manifest WHERE method_name=?"));
s1.BindString(0, payment_method);
if (!s1.Run()) {
return false;
}
sql::Statement s2(
db()->GetUniqueStatement("INSERT INTO payment_method_manifest "
"(expire_date, method_name, web_app_id) "
"VALUES (?, ?, ?)"));
const base::Time expire_date =
base::Time::FromTimeT(base::Time::NowFromSystemTime().ToTimeT() +
PAYMENT_METHOD_MANIFEST_VALID_TIME_IN_SECONDS);
for (const auto& id : web_app_ids) {
int index = 0;
s2.BindTime(index++, expire_date);
s2.BindString(index++, payment_method);
s2.BindString(index, id);
if (!s2.Run()) {
return false;
}
s2.Reset(true);
}
if (!transaction.Commit()) {
return false;
}
return true;
}
std::vector<std::string> WebPaymentsTable::GetManifest(
const std::string& payment_method) {
std::vector<std::string> web_app_ids;
sql::Statement s(
db()->GetUniqueStatement("SELECT web_app_id "
"FROM payment_method_manifest "
"WHERE method_name=?"));
s.BindString(0, payment_method);
while (s.Step()) {
web_app_ids.emplace_back(s.ColumnString(0));
}
return web_app_ids;
}
bool WebPaymentsTable::AddSecurePaymentConfirmationCredential(
const SecurePaymentConfirmationCredential& credential) {
if (!credential.IsValidNewCredential()) {
return false;
}
sql::Transaction transaction(db());
if (!transaction.Begin()) {
return false;
}
{
// Check for credential identifier reuse by a different relying party.
sql::Statement s0(
db()->GetUniqueStatement("SELECT label "
"FROM secure_payment_confirmation_instrument "
"WHERE credential_id=? "
"AND relying_party_id<>?"));
int index = 0;
s0.BindBlob(index++, credential.credential_id);
s0.BindString(index++, credential.relying_party_id);
if (s0.Step()) {
return false;
}
}
{
sql::Statement s1(db()->GetUniqueStatement(
"DELETE FROM secure_payment_confirmation_instrument "
"WHERE credential_id=?"));
s1.BindBlob(0, credential.credential_id);
if (!s1.Run()) {
return false;
}
}
{
// The system authenticator will overwrite a discoverable credential with
// the same relying party and user ID, so we also clear any such credential.
sql::Statement s2(db()->GetUniqueStatement(
"DELETE FROM secure_payment_confirmation_instrument "
"WHERE relying_party_id=? "
"AND user_id=?"));
int index = 0;
s2.BindString(index++, credential.relying_party_id);
s2.BindBlob(index++, credential.user_id);
if (!s2.Run()) {
return false;
}
}
{
sql::Statement s3(db()->GetUniqueStatement(
"INSERT INTO secure_payment_confirmation_instrument "
"(credential_id, relying_party_id, user_id, label, icon, date_created) "
"VALUES (?, ?, ?, ?, ?, ?)"));
int index = 0;
s3.BindBlob(index++, credential.credential_id);
s3.BindString(index++, credential.relying_party_id);
s3.BindBlob(index++, credential.user_id);
s3.BindString(index++, std::string());
s3.BindBlob(index++, std::vector<uint8_t>());
s3.BindTime(index++, base::Time::Now());
if (!s3.Run()) {
return false;
}
}
if (!transaction.Commit()) {
return false;
}
return true;
}
std::vector<std::unique_ptr<SecurePaymentConfirmationCredential>>
WebPaymentsTable::GetSecurePaymentConfirmationCredentials(
std::vector<std::vector<uint8_t>> credential_ids,
const std::string& relying_party_id) {
std::vector<std::unique_ptr<SecurePaymentConfirmationCredential>> credentials;
sql::Statement s(
db()->GetUniqueStatement("SELECT relying_party_id, user_id "
"FROM secure_payment_confirmation_instrument "
"WHERE credential_id=? "
"AND relying_party_id=?"));
// The `credential_id` temporary variable is not `const` because it is
// std::move()'d into the credential below.
for (auto& credential_id : credential_ids) {
s.Reset(true);
if (credential_id.empty()) {
continue;
}
s.BindBlob(0, credential_id);
s.BindString(1, relying_party_id);
if (!s.Step()) {
continue;
}
auto credential = std::make_unique<SecurePaymentConfirmationCredential>();
credential->credential_id = std::move(credential_id);
int index = 0;
credential->relying_party_id = s.ColumnString(index++);
s.ColumnBlobAsVector(index++, &(credential->user_id));
if (!credential->IsValid()) {
continue;
}
credentials.push_back(std::move(credential));
}
return credentials;
}
bool WebPaymentsTable::SetBrowserBoundKey(
std::vector<uint8_t> credential_id,
std::string_view relying_party_id,
std::vector<uint8_t> browser_bound_key_id) {
if (credential_id.empty() || relying_party_id.empty() ||
browser_bound_key_id.empty()) {
return false;
}
sql::Statement s(db()->GetUniqueStatement(
"INSERT INTO secure_payment_confirmation_browser_bound_key ( "
"credential_id, relying_party_id, browser_bound_key_id) "
"VALUES (?, ?, ?)"));
int index = 0;
s.BindBlob(index++, std::move(credential_id));
s.BindString(index++, relying_party_id);
s.BindBlob(index++, browser_bound_key_id);
return s.Run();
}
std::optional<std::vector<uint8_t>> WebPaymentsTable::GetBrowserBoundKey(
std::vector<uint8_t> credential_id,
std::string_view relying_party_id) {
sql::Statement s(db()->GetUniqueStatement(
"SELECT browser_bound_key_id "
"FROM secure_payment_confirmation_browser_bound_key "
"WHERE credential_id = ? AND relying_party_id = ?"));
int index = 0;
s.BindBlob(index++, std::move(credential_id));
s.BindString(index++, relying_party_id);
if (!s.Step()) {
return std::nullopt;
}
if (s.GetColumnType(0) != sql::ColumnType::kBlob) {
return std::nullopt;
}
base::span<const uint8_t> browser_bound_key_span = s.ColumnBlob(0);
return std::vector<uint8_t>(browser_bound_key_span.begin(),
browser_bound_key_span.end());
}
std::vector<BrowserBoundKeyMetadata>
WebPaymentsTable::GetAllBrowserBoundKeys() {
sql::Statement s(db()->GetUniqueStatement(
"SELECT relying_party_id, credential_id, browser_bound_key_id "
"FROM secure_payment_confirmation_browser_bound_key"));
std::vector<BrowserBoundKeyMetadata> browser_bound_keys;
while (s.Step()) {
BrowserBoundKeyMetadata& entry = browser_bound_keys.emplace_back();
entry.passkey.relying_party_id = s.ColumnString(0);
s.ColumnBlobAsVector(1, &entry.passkey.credential_id);
s.ColumnBlobAsVector(2, &entry.browser_bound_key_id);
}
return browser_bound_keys;
}
bool WebPaymentsTable::DeleteBrowserBoundKeys(
std::vector<BrowserBoundKeyMetadata::RelyingPartyAndCredentialId>
passkeys) {
for (auto& passkey : passkeys) {
sql::Statement s(db()->GetUniqueStatement(
"DELETE FROM secure_payment_confirmation_browser_bound_key "
"WHERE relying_party_id = ? AND credential_id = ?"));
s.BindString(0, passkey.relying_party_id);
s.BindBlob(1, std::move(passkey.credential_id));
if (!s.Run()) {
return false;
}
}
return true;
}
bool WebPaymentsTable::ExecuteForTest(const base::cstring_view sql) {
return db()->Execute(sql);
}
bool WebPaymentsTable::RazeForTest() {
return db()->Raze();
}
bool WebPaymentsTable::DoesColumnExistForTest(
const base::cstring_view table_name,
const base::cstring_view column_name) {
return db()->DoesColumnExist(table_name, column_name);
}
} // namespace payments