gcm_account_mapper.cc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  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. #include "components/gcm_driver/gcm_account_mapper.h"
  5. #include <utility>
  6. #include "base/bind.h"
  7. #include "base/guid.h"
  8. #include "base/metrics/histogram_functions.h"
  9. #include "base/time/clock.h"
  10. #include "base/time/default_clock.h"
  11. #include "components/gcm_driver/gcm_driver_desktop.h"
  12. #include "google_apis/gcm/engine/gcm_store.h"
  13. namespace gcm {
  14. namespace {
  15. const char kGCMAccountMapperSenderId[] = "745476177629";
  16. const char kGCMAccountMapperSendTo[] = "google.com";
  17. const int kGCMAddMappingMessageTTL = 30 * 60; // 0.5 hours in seconds.
  18. const int kGCMRemoveMappingMessageTTL = 24 * 60 * 60; // 1 day in seconds.
  19. const int kGCMUpdateIntervalHours = 24;
  20. // Because adding an account mapping dependents on a fresh OAuth2 token, we
  21. // allow the update to happen earlier than update due time, if it is within
  22. // the early start time to take advantage of that token.
  23. const int kGCMUpdateEarlyStartHours = 6;
  24. const char kRegistrationIdMessgaeKey[] = "id";
  25. const char kTokenMessageKey[] = "t";
  26. const char kAccountMessageKey[] = "a";
  27. const char kRemoveAccountKey[] = "r";
  28. const char kRemoveAccountValue[] = "1";
  29. // Use to handle send to Gaia ID scenario:
  30. const char kGCMSendToGaiaIdAppIdKey[] = "gcmb";
  31. std::string GenerateMessageID() {
  32. return base::GenerateGUID();
  33. }
  34. } // namespace
  35. const char kGCMAccountMapperAppId[] = "com.google.android.gms";
  36. GCMAccountMapper::GCMAccountMapper(GCMDriver* gcm_driver)
  37. : gcm_driver_(gcm_driver),
  38. clock_(base::DefaultClock::GetInstance()),
  39. initialized_(false) {}
  40. GCMAccountMapper::~GCMAccountMapper() = default;
  41. void GCMAccountMapper::Initialize(const AccountMappings& account_mappings,
  42. const DispatchMessageCallback& callback) {
  43. DCHECK(!initialized_);
  44. initialized_ = true;
  45. accounts_ = account_mappings;
  46. dispatch_message_callback_ = callback;
  47. GetRegistration();
  48. }
  49. void GCMAccountMapper::SetAccountTokens(
  50. const std::vector<GCMClient::AccountTokenInfo>& account_tokens) {
  51. DVLOG(1) << "GCMAccountMapper::SetAccountTokens called with "
  52. << account_tokens.size() << " accounts.";
  53. // If account mapper is not ready to handle tasks yet, save the latest
  54. // account tokens and return.
  55. if (!IsReady()) {
  56. pending_account_tokens_ = account_tokens;
  57. // If mapper is initialized, but still does not have registration ID,
  58. // maybe the registration gave up. Retrying in case.
  59. if (initialized_ && gcm_driver_->IsStarted())
  60. GetRegistration();
  61. return;
  62. }
  63. // Start from removing the old tokens, from all of the known accounts.
  64. for (auto iter = accounts_.begin(); iter != accounts_.end(); ++iter) {
  65. iter->access_token.clear();
  66. }
  67. // Update the internal collection of mappings with the new tokens.
  68. for (auto token_iter = account_tokens.begin();
  69. token_iter != account_tokens.end(); ++token_iter) {
  70. AccountMapping* account_mapping =
  71. FindMappingByAccountId(token_iter->account_id);
  72. if (!account_mapping) {
  73. AccountMapping new_mapping;
  74. new_mapping.status = AccountMapping::NEW;
  75. new_mapping.account_id = token_iter->account_id;
  76. new_mapping.access_token = token_iter->access_token;
  77. new_mapping.email = token_iter->email;
  78. accounts_.push_back(new_mapping);
  79. } else {
  80. // Since we got a token for an account, drop the remove message and treat
  81. // it as mapped.
  82. if (account_mapping->status == AccountMapping::REMOVING) {
  83. account_mapping->status = AccountMapping::MAPPED;
  84. account_mapping->status_change_timestamp = base::Time();
  85. account_mapping->last_message_id.clear();
  86. }
  87. account_mapping->email = token_iter->email;
  88. account_mapping->access_token = token_iter->access_token;
  89. }
  90. }
  91. // Decide what to do with each account (either start mapping, or start
  92. // removing).
  93. for (auto mappings_iter = accounts_.begin(); mappings_iter != accounts_.end();
  94. ++mappings_iter) {
  95. if (mappings_iter->access_token.empty()) {
  96. // Send a remove message if the account was not previously being removed,
  97. // or it doesn't have a pending message, or the pending message is
  98. // already expired, but OnSendError event was lost.
  99. if (mappings_iter->status != AccountMapping::REMOVING ||
  100. mappings_iter->last_message_id.empty() ||
  101. IsLastStatusChangeOlderThanTTL(*mappings_iter)) {
  102. SendRemoveMappingMessage(*mappings_iter);
  103. }
  104. } else {
  105. // A message is sent for all of the mappings considered NEW, or mappings
  106. // that are ADDING, but have expired message (OnSendError event lost), or
  107. // for those mapped accounts that can be refreshed.
  108. if (mappings_iter->status == AccountMapping::NEW ||
  109. (mappings_iter->status == AccountMapping::ADDING &&
  110. IsLastStatusChangeOlderThanTTL(*mappings_iter)) ||
  111. (mappings_iter->status == AccountMapping::MAPPED &&
  112. CanTriggerUpdate(mappings_iter->status_change_timestamp))) {
  113. mappings_iter->last_message_id.clear();
  114. SendAddMappingMessage(*mappings_iter);
  115. }
  116. }
  117. }
  118. }
  119. void GCMAccountMapper::ShutdownHandler() {
  120. initialized_ = false;
  121. accounts_.clear();
  122. registration_id_.clear();
  123. dispatch_message_callback_.Reset();
  124. }
  125. void GCMAccountMapper::OnStoreReset() {
  126. // TODO(crbug.com/661660): Tell server to remove the mapping. But can't use
  127. // upstream GCM send for that since the store got reset.
  128. ShutdownHandler();
  129. }
  130. void GCMAccountMapper::OnMessage(const std::string& app_id,
  131. const IncomingMessage& message) {
  132. DCHECK_EQ(app_id, kGCMAccountMapperAppId);
  133. // TODO(fgorski): Report Send to Gaia ID failures using UMA.
  134. base::UmaHistogramBoolean("GCM.AccountMappingMessageReceived", true);
  135. if (dispatch_message_callback_.is_null()) {
  136. DVLOG(1) << "dispatch_message_callback_ missing in GCMAccountMapper";
  137. return;
  138. }
  139. auto it = message.data.find(kGCMSendToGaiaIdAppIdKey);
  140. if (it == message.data.end()) {
  141. DVLOG(1) << "Send to Gaia ID failure: Embedded app ID missing.";
  142. return;
  143. }
  144. std::string embedded_app_id = it->second;
  145. if (embedded_app_id.empty()) {
  146. DVLOG(1) << "Send to Gaia ID failure: Embedded app ID is empty.";
  147. return;
  148. }
  149. // Ensuring the message does not carry the embedded app ID.
  150. IncomingMessage new_message = message;
  151. new_message.data.erase(new_message.data.find(kGCMSendToGaiaIdAppIdKey));
  152. dispatch_message_callback_.Run(embedded_app_id, new_message);
  153. }
  154. void GCMAccountMapper::OnMessagesDeleted(const std::string& app_id) {
  155. // Account message does not expect messages right now.
  156. }
  157. void GCMAccountMapper::OnSendError(
  158. const std::string& app_id,
  159. const GCMClient::SendErrorDetails& send_error_details) {
  160. DCHECK_EQ(app_id, kGCMAccountMapperAppId);
  161. auto account_mapping_it =
  162. FindMappingByMessageId(send_error_details.message_id);
  163. if (account_mapping_it == accounts_.end())
  164. return;
  165. if (send_error_details.result != GCMClient::TTL_EXCEEDED) {
  166. DVLOG(1) << "Send error result different than TTL EXCEEDED: "
  167. << send_error_details.result << ". "
  168. << "Postponing the retry until a new batch of tokens arrives.";
  169. return;
  170. }
  171. if (account_mapping_it->status == AccountMapping::REMOVING) {
  172. // Another message to remove mapping can be sent immediately, because TTL
  173. // for those is one day. No need to back off.
  174. SendRemoveMappingMessage(*account_mapping_it);
  175. } else {
  176. if (account_mapping_it->status == AccountMapping::ADDING) {
  177. // There is no mapping established, so we can remove the entry.
  178. // Getting a fresh token will trigger a new attempt.
  179. gcm_driver_->RemoveAccountMapping(account_mapping_it->account_id);
  180. accounts_.erase(account_mapping_it);
  181. } else {
  182. // Account is already MAPPED, we have to wait for another token.
  183. account_mapping_it->last_message_id.clear();
  184. gcm_driver_->UpdateAccountMapping(*account_mapping_it);
  185. }
  186. }
  187. }
  188. void GCMAccountMapper::OnSendAcknowledged(const std::string& app_id,
  189. const std::string& message_id) {
  190. DCHECK_EQ(app_id, kGCMAccountMapperAppId);
  191. auto account_mapping_it = FindMappingByMessageId(message_id);
  192. DVLOG(1) << "OnSendAcknowledged with message ID: " << message_id;
  193. if (account_mapping_it == accounts_.end())
  194. return;
  195. // Here is where we advance a status of a mapping and persist or remove.
  196. if (account_mapping_it->status == AccountMapping::REMOVING) {
  197. // Message removing the account has been confirmed by the GCM, we can remove
  198. // all the information related to the account (from memory and store).
  199. gcm_driver_->RemoveAccountMapping(account_mapping_it->account_id);
  200. accounts_.erase(account_mapping_it);
  201. } else {
  202. // Mapping status is ADDING only when it is a first time mapping.
  203. DCHECK(account_mapping_it->status == AccountMapping::ADDING ||
  204. account_mapping_it->status == AccountMapping::MAPPED);
  205. // Account is marked as mapped with the current time.
  206. account_mapping_it->status = AccountMapping::MAPPED;
  207. account_mapping_it->status_change_timestamp = clock_->Now();
  208. // There is no pending message for the account.
  209. account_mapping_it->last_message_id.clear();
  210. gcm_driver_->UpdateAccountMapping(*account_mapping_it);
  211. }
  212. }
  213. bool GCMAccountMapper::CanHandle(const std::string& app_id) const {
  214. return app_id.compare(kGCMAccountMapperAppId) == 0;
  215. }
  216. bool GCMAccountMapper::IsReady() {
  217. return initialized_ && gcm_driver_->IsStarted() && !registration_id_.empty();
  218. }
  219. void GCMAccountMapper::SendAddMappingMessage(AccountMapping& account_mapping) {
  220. CreateAndSendMessage(account_mapping);
  221. }
  222. void GCMAccountMapper::SendRemoveMappingMessage(
  223. AccountMapping& account_mapping) {
  224. // We want to persist an account that is being removed as quickly as possible
  225. // as well as clean up the last message information.
  226. if (account_mapping.status != AccountMapping::REMOVING) {
  227. account_mapping.status = AccountMapping::REMOVING;
  228. account_mapping.status_change_timestamp = clock_->Now();
  229. }
  230. account_mapping.last_message_id.clear();
  231. gcm_driver_->UpdateAccountMapping(account_mapping);
  232. CreateAndSendMessage(account_mapping);
  233. }
  234. void GCMAccountMapper::CreateAndSendMessage(
  235. const AccountMapping& account_mapping) {
  236. OutgoingMessage outgoing_message;
  237. outgoing_message.id = GenerateMessageID();
  238. outgoing_message.data[kRegistrationIdMessgaeKey] = registration_id_;
  239. outgoing_message.data[kAccountMessageKey] = account_mapping.email;
  240. if (account_mapping.status == AccountMapping::REMOVING) {
  241. outgoing_message.time_to_live = kGCMRemoveMappingMessageTTL;
  242. outgoing_message.data[kRemoveAccountKey] = kRemoveAccountValue;
  243. } else {
  244. outgoing_message.data[kTokenMessageKey] = account_mapping.access_token;
  245. outgoing_message.time_to_live = kGCMAddMappingMessageTTL;
  246. }
  247. gcm_driver_->Send(kGCMAccountMapperAppId, kGCMAccountMapperSendTo,
  248. outgoing_message,
  249. base::BindOnce(&GCMAccountMapper::OnSendFinished,
  250. weak_ptr_factory_.GetWeakPtr(),
  251. account_mapping.account_id));
  252. }
  253. void GCMAccountMapper::OnSendFinished(const CoreAccountId& account_id,
  254. const std::string& message_id,
  255. GCMClient::Result result) {
  256. // TODO(fgorski): Add another attempt, in case the QUEUE is not full.
  257. if (result != GCMClient::SUCCESS)
  258. return;
  259. AccountMapping* account_mapping = FindMappingByAccountId(account_id);
  260. DCHECK(account_mapping);
  261. // If we are dealing with account with status NEW, it is the first time
  262. // mapping, and we should mark it as ADDING.
  263. if (account_mapping->status == AccountMapping::NEW) {
  264. account_mapping->status = AccountMapping::ADDING;
  265. account_mapping->status_change_timestamp = clock_->Now();
  266. }
  267. account_mapping->last_message_id = message_id;
  268. gcm_driver_->UpdateAccountMapping(*account_mapping);
  269. }
  270. void GCMAccountMapper::GetRegistration() {
  271. DCHECK(registration_id_.empty());
  272. std::vector<std::string> sender_ids;
  273. sender_ids.push_back(kGCMAccountMapperSenderId);
  274. gcm_driver_->Register(kGCMAccountMapperAppId, sender_ids,
  275. base::BindOnce(&GCMAccountMapper::OnRegisterFinished,
  276. weak_ptr_factory_.GetWeakPtr()));
  277. }
  278. void GCMAccountMapper::OnRegisterFinished(const std::string& registration_id,
  279. GCMClient::Result result) {
  280. if (result == GCMClient::SUCCESS)
  281. registration_id_ = registration_id;
  282. if (IsReady()) {
  283. if (!pending_account_tokens_.empty()) {
  284. SetAccountTokens(pending_account_tokens_);
  285. pending_account_tokens_.clear();
  286. }
  287. }
  288. }
  289. bool GCMAccountMapper::CanTriggerUpdate(
  290. const base::Time& last_update_time) const {
  291. return last_update_time +
  292. base::Hours(kGCMUpdateIntervalHours - kGCMUpdateEarlyStartHours) <
  293. clock_->Now();
  294. }
  295. bool GCMAccountMapper::IsLastStatusChangeOlderThanTTL(
  296. const AccountMapping& account_mapping) const {
  297. int ttl_seconds = account_mapping.status == AccountMapping::REMOVING ?
  298. kGCMRemoveMappingMessageTTL : kGCMAddMappingMessageTTL;
  299. return account_mapping.status_change_timestamp + base::Seconds(ttl_seconds) <
  300. clock_->Now();
  301. }
  302. AccountMapping* GCMAccountMapper::FindMappingByAccountId(
  303. const CoreAccountId& account_id) {
  304. for (auto iter = accounts_.begin(); iter != accounts_.end(); ++iter) {
  305. if (iter->account_id == account_id)
  306. return &*iter;
  307. }
  308. return nullptr;
  309. }
  310. GCMAccountMapper::AccountMappings::iterator
  311. GCMAccountMapper::FindMappingByMessageId(const std::string& message_id) {
  312. for (auto iter = accounts_.begin(); iter != accounts_.end(); ++iter) {
  313. if (iter->last_message_id == message_id)
  314. return iter;
  315. }
  316. return accounts_.end();
  317. }
  318. void GCMAccountMapper::SetClockForTesting(base::Clock* clock) {
  319. clock_ = clock;
  320. }
  321. } // namespace gcm