1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/signin/public/webdata/token_service_table.h"
6
7 #include <map>
8 #include <string>
9
10 #include "base/logging.h"
11 #include "base/metrics/histogram_macros.h"
12 #include "components/os_crypt/os_crypt.h"
13 #include "components/webdata/common/web_database.h"
14 #include "sql/statement.h"
15
16 namespace {
17
GetKey()18 WebDatabaseTable::TypeKey GetKey() {
19 // We just need a unique constant. Use the address of a static that
20 // COMDAT folding won't touch in an optimizing linker.
21 static int table_key = 0;
22 return reinterpret_cast<void*>(&table_key);
23 }
24
25 // Entries in the |Signin.TokenTable.ReadTokenFromDBResult| histogram.
26 enum ReadOneTokenResult {
27 READ_ONE_TOKEN_SUCCESS,
28 READ_ONE_TOKEN_DB_SUCCESS_DECRYPT_FAILED,
29 READ_ONE_TOKEN_DB_FAILED_BAD_ENTRY,
30 READ_ONE_TOKEN_MAX_VALUE
31 };
32
33 } // namespace
34
FromWebDatabase(WebDatabase * db)35 TokenServiceTable* TokenServiceTable::FromWebDatabase(WebDatabase* db) {
36 return static_cast<TokenServiceTable*>(db->GetTable(GetKey()));
37 }
38
GetTypeKey() const39 WebDatabaseTable::TypeKey TokenServiceTable::GetTypeKey() const {
40 return GetKey();
41 }
42
CreateTablesIfNecessary()43 bool TokenServiceTable::CreateTablesIfNecessary() {
44 if (!db_->DoesTableExist("token_service")) {
45 if (!db_->Execute("CREATE TABLE token_service ("
46 "service VARCHAR PRIMARY KEY NOT NULL,"
47 "encrypted_token BLOB)")) {
48 NOTREACHED();
49 LOG(ERROR) << "Failed creating token_service table";
50 return false;
51 }
52 }
53 return true;
54 }
55
IsSyncable()56 bool TokenServiceTable::IsSyncable() {
57 return true;
58 }
59
MigrateToVersion(int version,bool * update_compatible_version)60 bool TokenServiceTable::MigrateToVersion(int version,
61 bool* update_compatible_version) {
62 return true;
63 }
64
RemoveAllTokens()65 bool TokenServiceTable::RemoveAllTokens() {
66 VLOG(1) << "Remove all tokens";
67 sql::Statement s(db_->GetUniqueStatement("DELETE FROM token_service"));
68
69 bool result = s.Run();
70 LOG_IF(ERROR, !result) << "Failed to remove all tokens";
71 return result;
72 }
73
RemoveTokenForService(const std::string & service)74 bool TokenServiceTable::RemoveTokenForService(const std::string& service) {
75 sql::Statement s(
76 db_->GetUniqueStatement("DELETE FROM token_service WHERE service = ?"));
77 s.BindString(0, service);
78
79 bool result = s.Run();
80 LOG_IF(ERROR, !result) << "Failed to remove token for " << service;
81 return result;
82 }
83
SetTokenForService(const std::string & service,const std::string & token)84 bool TokenServiceTable::SetTokenForService(const std::string& service,
85 const std::string& token) {
86 std::string encrypted_token;
87 bool encrypted = OSCrypt::EncryptString(token, &encrypted_token);
88 if (!encrypted) {
89 LOG(ERROR) << "Failed to encrypt token (token will not be saved to DB).";
90 return false;
91 }
92
93 // Don't bother with a cached statement since this will be a relatively
94 // infrequent operation.
95 sql::Statement s(
96 db_->GetUniqueStatement("INSERT OR REPLACE INTO token_service "
97 "(service, encrypted_token) VALUES (?, ?)"));
98 s.BindString(0, service);
99 s.BindBlob(1, encrypted_token.data(),
100 static_cast<int>(encrypted_token.length()));
101
102 bool result = s.Run();
103 LOG_IF(ERROR, !result) << "Failed to insert or replace token for " << service;
104 return result;
105 }
106
GetAllTokens(std::map<std::string,std::string> * tokens)107 TokenServiceTable::Result TokenServiceTable::GetAllTokens(
108 std::map<std::string, std::string>* tokens) {
109 sql::Statement s(db_->GetUniqueStatement(
110 "SELECT service, encrypted_token FROM token_service"));
111
112 UMA_HISTOGRAM_BOOLEAN("Signin.TokenTable.GetAllTokensSqlStatementValidity",
113 s.is_valid());
114
115 if (!s.is_valid()) {
116 LOG(ERROR) << "Failed to load tokens (invalid SQL statement).";
117 return TOKEN_DB_RESULT_SQL_INVALID_STATEMENT;
118 }
119
120 int number_of_tokens_loaded = 0;
121
122 Result read_all_tokens_result = TOKEN_DB_RESULT_SUCCESS;
123 while (s.Step()) {
124 ReadOneTokenResult read_token_result = READ_ONE_TOKEN_MAX_VALUE;
125
126 std::string encrypted_token;
127 std::string decrypted_token;
128 std::string service;
129 service = s.ColumnString(0);
130 bool entry_ok =
131 !service.empty() && s.ColumnBlobAsString(1, &encrypted_token);
132 if (entry_ok) {
133 if (OSCrypt::DecryptString(encrypted_token, &decrypted_token)) {
134 (*tokens)[service] = decrypted_token;
135 read_token_result = READ_ONE_TOKEN_SUCCESS;
136 number_of_tokens_loaded++;
137 } else {
138 // Chrome relies on native APIs to encrypt and decrypt the tokens which
139 // may fail (see http://crbug.com/686485).
140 LOG(ERROR) << "Failed to decrypt token for service " << service;
141 read_token_result = READ_ONE_TOKEN_DB_SUCCESS_DECRYPT_FAILED;
142 read_all_tokens_result = TOKEN_DB_RESULT_DECRYPT_ERROR;
143 }
144 } else {
145 LOG(ERROR) << "Bad token entry for service " << service;
146 read_token_result = READ_ONE_TOKEN_DB_FAILED_BAD_ENTRY;
147 read_all_tokens_result = TOKEN_DB_RESULT_BAD_ENTRY;
148 }
149 DCHECK_LT(read_token_result, READ_ONE_TOKEN_MAX_VALUE);
150 UMA_HISTOGRAM_ENUMERATION("Signin.TokenTable.ReadTokenFromDBResult",
151 read_token_result, READ_ONE_TOKEN_MAX_VALUE);
152 }
153 VLOG(1) << "Loaded tokens: result = " << read_all_tokens_result
154 << " ; number of tokens loaded = " << number_of_tokens_loaded;
155 return read_all_tokens_result;
156 }
157