blob: e947157e801a9066752f63d4ad934dc5c04bab67 [file] [log] [blame]
Alex Vakulenko3cb466c2014-04-15 11:36:32 -07001// Copyright 2014 The Chromium OS 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 "buffet/device_registration_info.h"
6
Christopher Wiley006e94e2014-05-02 13:44:48 -07007#include <memory>
Alex Vakulenkob3aac252014-05-07 17:35:24 -07008#include <utility>
9#include <vector>
Christopher Wiley006e94e2014-05-02 13:44:48 -070010
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070011#include <base/json/json_writer.h>
Anton Muhinac661ab2014-10-03 20:29:48 +040012#include <base/message_loop/message_loop.h>
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070013#include <base/values.h>
Anton Muhinac661ab2014-10-03 20:29:48 +040014#include <chromeos/bind_lambda.h>
Alex Vakulenkoe4879a22014-08-20 15:47:36 -070015#include <chromeos/data_encoding.h>
Anton Muhin233d2ee2014-10-22 15:16:24 +040016#include <chromeos/errors/error_codes.h>
Alex Vakulenkoa8b95bc2014-08-27 11:00:57 -070017#include <chromeos/http/http_utils.h>
Anton Muhin332df192014-11-22 05:59:14 +040018#include <chromeos/key_value_store.h>
Alex Vakulenko3aeea1c2014-08-20 16:33:12 -070019#include <chromeos/mime_utils.h>
Alex Vakulenkoa8b95bc2014-08-27 11:00:57 -070020#include <chromeos/strings/string_utils.h>
Alex Vakulenkobd5b5442014-08-20 16:16:34 -070021#include <chromeos/url_utils.h>
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070022
Anton Muhin59755522014-11-05 21:30:12 +040023#include "buffet/commands/cloud_command_proxy.h"
Alex Vakulenko45109442014-07-29 11:07:10 -070024#include "buffet/commands/command_definition.h"
25#include "buffet/commands/command_manager.h"
Alex Vakulenko8e34d392014-04-29 11:02:56 -070026#include "buffet/device_registration_storage_keys.h"
Alex Vakulenko07216fe2014-09-19 15:31:09 -070027#include "buffet/states/state_manager.h"
Alex Vakulenko5841c302014-07-23 10:49:49 -070028#include "buffet/storage_impls.h"
Alex Vakulenkob04936f2014-09-19 14:53:58 -070029#include "buffet/utils.h"
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070030
Alex Vakulenkob3aac252014-05-07 17:35:24 -070031const char buffet::kErrorDomainOAuth2[] = "oauth2";
32const char buffet::kErrorDomainGCD[] = "gcd";
33const char buffet::kErrorDomainGCDServer[] = "gcd_server";
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070034
Alex Vakulenko8e34d392014-04-29 11:02:56 -070035namespace buffet {
36namespace storage_keys {
37
Christopher Wileyab8af682015-02-08 09:25:34 -080038// Statically configured fields
39const char kApiKey[] = "api_key";
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070040const char kClientId[] = "client_id";
41const char kClientSecret[] = "client_secret";
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070042const char kOAuthURL[] = "oauth_url";
43const char kServiceURL[] = "service_url";
Christopher Wileyab8af682015-02-08 09:25:34 -080044// Credentials related to a particular registration.
45const char kDeviceId[] = "device_id";
46const char kRefreshToken[] = "refresh_token";
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070047const char kRobotAccount[] = "robot_account";
Christopher Wileyab8af682015-02-08 09:25:34 -080048// Fields in our device metadata.
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070049const char kDeviceKind[] = "device_kind";
Anton Muhin9e19cdd2014-10-20 13:55:04 +040050const char kName[] = "name";
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070051const char kDisplayName[] = "display_name";
Vitaly Bukad3eb19c2014-11-21 13:39:43 -080052const char kDescription[] = "description";
53const char kLocation[] = "location";
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070054
Alex Vakulenko8e34d392014-04-29 11:02:56 -070055} // namespace storage_keys
56} // namespace buffet
57
58namespace {
59
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070060const base::FilePath::CharType kDeviceInfoFilePath[] =
61 FILE_PATH_LITERAL("/var/lib/buffet/device_reg_info");
62
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070063std::pair<std::string, std::string> BuildAuthHeader(
64 const std::string& access_token_type,
65 const std::string& access_token) {
Alex Vakulenkoaf23b322014-05-08 16:25:45 -070066 std::string authorization =
Alex Vakulenkob8fc1df2014-08-20 15:38:07 -070067 chromeos::string_utils::Join(' ', access_token_type, access_token);
Alex Vakulenkocca20932014-08-20 17:35:12 -070068 return {chromeos::http::request_header::kAuthorization, authorization};
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070069}
70
71std::unique_ptr<base::DictionaryValue> ParseOAuthResponse(
Alex Vakulenkocca20932014-08-20 17:35:12 -070072 const chromeos::http::Response* response, chromeos::ErrorPtr* error) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070073 int code = 0;
Alex Vakulenkocca20932014-08-20 17:35:12 -070074 auto resp = chromeos::http::ParseJsonResponse(response, &code, error);
75 if (resp && code >= chromeos::http::status_code::BadRequest) {
Alex Vakulenkob3aac252014-05-07 17:35:24 -070076 if (error) {
77 std::string error_code, error_message;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070078 if (resp->GetString("error", &error_code) &&
Alex Vakulenkob3aac252014-05-07 17:35:24 -070079 resp->GetString("error_description", &error_message)) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -080080 chromeos::Error::AddTo(error, FROM_HERE, buffet::kErrorDomainOAuth2,
81 error_code, error_message);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070082 } else {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -080083 chromeos::Error::AddTo(error, FROM_HERE, buffet::kErrorDomainOAuth2,
Alex Vakulenko5f472062014-08-14 17:54:04 -070084 "unexpected_response", "Unexpected OAuth error");
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070085 }
86 }
87 return std::unique_ptr<base::DictionaryValue>();
88 }
89 return resp;
90}
91
Alex Vakulenko5f472062014-08-14 17:54:04 -070092inline void SetUnexpectedError(chromeos::ErrorPtr* error) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -080093 chromeos::Error::AddTo(error, FROM_HERE, buffet::kErrorDomainGCD,
94 "unexpected_response", "Unexpected GCD error");
Alex Vakulenkob3aac252014-05-07 17:35:24 -070095}
96
Alex Vakulenko5f472062014-08-14 17:54:04 -070097void ParseGCDError(const base::DictionaryValue* json,
98 chromeos::ErrorPtr* error) {
Alex Vakulenkob3aac252014-05-07 17:35:24 -070099 if (!error)
100 return;
101
102 const base::Value* list_value = nullptr;
103 const base::ListValue* error_list = nullptr;
104 if (!json->Get("error.errors", &list_value) ||
105 !list_value->GetAsList(&error_list)) {
106 SetUnexpectedError(error);
107 return;
108 }
109
110 for (size_t i = 0; i < error_list->GetSize(); i++) {
111 const base::Value* error_value = nullptr;
112 const base::DictionaryValue* error_object = nullptr;
113 if (!error_list->Get(i, &error_value) ||
114 !error_value->GetAsDictionary(&error_object)) {
115 SetUnexpectedError(error);
116 continue;
117 }
118 std::string error_code, error_message;
119 if (error_object->GetString("reason", &error_code) &&
120 error_object->GetString("message", &error_message)) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800121 chromeos::Error::AddTo(error, FROM_HERE, buffet::kErrorDomainGCDServer,
Alex Vakulenko5f472062014-08-14 17:54:04 -0700122 error_code, error_message);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700123 } else {
124 SetUnexpectedError(error);
125 }
126 }
127}
128
Alex Vakulenkobda220a2014-04-18 15:25:44 -0700129std::string BuildURL(const std::string& url,
130 const std::vector<std::string>& subpaths,
Alex Vakulenkoe4879a22014-08-20 15:47:36 -0700131 const chromeos::data_encoding::WebParamList& params) {
Alex Vakulenkobd5b5442014-08-20 16:16:34 -0700132 std::string result = chromeos::url::CombineMultiple(url, subpaths);
133 return chromeos::url::AppendQueryParams(result, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700134}
135
Alex Vakulenko0357c032015-01-06 16:32:31 -0800136void IgnoreCloudError(const chromeos::Error*) {
Anton Muhin5191e812014-10-30 17:49:48 +0400137}
138
139void IgnoreCloudResult(const base::DictionaryValue&) {
140}
Anton Muhin6d2569e2014-10-30 12:32:27 +0400141
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700142} // anonymous namespace
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700143
144namespace buffet {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700145
Alex Vakulenko1f30a622014-07-23 11:13:15 -0700146DeviceRegistrationInfo::DeviceRegistrationInfo(
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700147 const std::shared_ptr<CommandManager>& command_manager,
Anton Muhin332df192014-11-22 05:59:14 +0400148 const std::shared_ptr<StateManager>& state_manager,
149 std::unique_ptr<chromeos::KeyValueStore> config_store)
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700150 : DeviceRegistrationInfo(
151 command_manager,
152 state_manager,
Anton Muhin332df192014-11-22 05:59:14 +0400153 std::move(config_store),
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700154 chromeos::http::Transport::CreateDefault(),
155 // TODO(avakulenko): Figure out security implications of storing
156 // this data unencrypted.
157 std::make_shared<FileStorage>(base::FilePath{kDeviceInfoFilePath})) {
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700158}
159
160DeviceRegistrationInfo::DeviceRegistrationInfo(
Alex Vakulenko1f30a622014-07-23 11:13:15 -0700161 const std::shared_ptr<CommandManager>& command_manager,
Anton Muhinb8315622014-11-20 03:17:05 +0400162 const std::shared_ptr<StateManager>& state_manager,
Anton Muhin332df192014-11-22 05:59:14 +0400163 std::unique_ptr<chromeos::KeyValueStore> config_store,
Alex Vakulenkocca20932014-08-20 17:35:12 -0700164 const std::shared_ptr<chromeos::http::Transport>& transport,
Alex Vakulenko1f30a622014-07-23 11:13:15 -0700165 const std::shared_ptr<StorageInterface>& storage)
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700166 : transport_{transport},
167 storage_{storage},
168 command_manager_{command_manager},
Anton Muhin332df192014-11-22 05:59:14 +0400169 state_manager_{state_manager},
170 config_store_{std::move(config_store)} {
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700171}
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700172
Anton Muhin332df192014-11-22 05:59:14 +0400173DeviceRegistrationInfo::~DeviceRegistrationInfo() = default;
174
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700175std::pair<std::string, std::string>
176 DeviceRegistrationInfo::GetAuthorizationHeader() const {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700177 return BuildAuthHeader("Bearer", access_token_);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700178}
179
180std::string DeviceRegistrationInfo::GetServiceURL(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700181 const std::string& subpath,
Alex Vakulenkoe4879a22014-08-20 15:47:36 -0700182 const chromeos::data_encoding::WebParamList& params) const {
Alex Vakulenkobda220a2014-04-18 15:25:44 -0700183 return BuildURL(service_url_, {subpath}, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700184}
185
186std::string DeviceRegistrationInfo::GetDeviceURL(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700187 const std::string& subpath,
Alex Vakulenkoe4879a22014-08-20 15:47:36 -0700188 const chromeos::data_encoding::WebParamList& params) const {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700189 CHECK(!device_id_.empty()) << "Must have a valid device ID";
Alex Vakulenkobda220a2014-04-18 15:25:44 -0700190 return BuildURL(service_url_, {"devices", device_id_, subpath}, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700191}
192
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700193std::string DeviceRegistrationInfo::GetOAuthURL(
194 const std::string& subpath,
Alex Vakulenkoe4879a22014-08-20 15:47:36 -0700195 const chromeos::data_encoding::WebParamList& params) const {
Alex Vakulenkobda220a2014-04-18 15:25:44 -0700196 return BuildURL(oauth_url_, {subpath}, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700197}
198
Alex Vakulenko5f472062014-08-14 17:54:04 -0700199std::string DeviceRegistrationInfo::GetDeviceId(chromeos::ErrorPtr* error) {
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700200 return CheckRegistration(error) ? device_id_ : std::string();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700201}
202
203bool DeviceRegistrationInfo::Load() {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700204 auto value = storage_->Load();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700205 const base::DictionaryValue* dict = nullptr;
206 if (!value || !value->GetAsDictionary(&dict))
207 return false;
208
209 // Get the values into temp variables first to make sure we can get
210 // all the data correctly before changing the state of this object.
211 std::string client_id;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700212 if (!dict->GetString(storage_keys::kClientId, &client_id))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700213 return false;
214 std::string client_secret;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700215 if (!dict->GetString(storage_keys::kClientSecret, &client_secret))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700216 return false;
217 std::string api_key;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700218 if (!dict->GetString(storage_keys::kApiKey, &api_key))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700219 return false;
220 std::string refresh_token;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700221 if (!dict->GetString(storage_keys::kRefreshToken, &refresh_token))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700222 return false;
223 std::string device_id;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700224 if (!dict->GetString(storage_keys::kDeviceId, &device_id))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700225 return false;
226 std::string oauth_url;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700227 if (!dict->GetString(storage_keys::kOAuthURL, &oauth_url))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700228 return false;
229 std::string service_url;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700230 if (!dict->GetString(storage_keys::kServiceURL, &service_url))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700231 return false;
232 std::string device_robot_account;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700233 if (!dict->GetString(storage_keys::kRobotAccount, &device_robot_account))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700234 return false;
235
236 client_id_ = client_id;
237 client_secret_ = client_secret;
238 api_key_ = api_key;
239 refresh_token_ = refresh_token;
240 device_id_ = device_id;
241 oauth_url_ = oauth_url;
242 service_url_ = service_url;
243 device_robot_account_ = device_robot_account;
244 return true;
245}
246
247bool DeviceRegistrationInfo::Save() const {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700248 base::DictionaryValue dict;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700249 dict.SetString(storage_keys::kClientId, client_id_);
250 dict.SetString(storage_keys::kClientSecret, client_secret_);
251 dict.SetString(storage_keys::kApiKey, api_key_);
252 dict.SetString(storage_keys::kRefreshToken, refresh_token_);
253 dict.SetString(storage_keys::kDeviceId, device_id_);
254 dict.SetString(storage_keys::kOAuthURL, oauth_url_);
255 dict.SetString(storage_keys::kServiceURL, service_url_);
256 dict.SetString(storage_keys::kRobotAccount, device_robot_account_);
257 return storage_->Save(&dict);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700258}
259
Alex Vakulenko5f472062014-08-14 17:54:04 -0700260bool DeviceRegistrationInfo::CheckRegistration(chromeos::ErrorPtr* error) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700261 LOG(INFO) << "Checking device registration record.";
262 if (refresh_token_.empty() ||
263 device_id_.empty() ||
264 device_robot_account_.empty()) {
265 LOG(INFO) << "No valid device registration record found.";
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800266 chromeos::Error::AddTo(error, FROM_HERE, kErrorDomainGCD,
267 "device_not_registered",
Alex Vakulenko5f472062014-08-14 17:54:04 -0700268 "No valid device registration record found");
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700269 return false;
270 }
271
272 LOG(INFO) << "Device registration record found.";
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700273 return ValidateAndRefreshAccessToken(error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700274}
275
Alex Vakulenko5f472062014-08-14 17:54:04 -0700276bool DeviceRegistrationInfo::ValidateAndRefreshAccessToken(
277 chromeos::ErrorPtr* error) {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700278 LOG(INFO) << "Checking access token expiration.";
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700279 if (!access_token_.empty() &&
280 !access_token_expiration_.is_null() &&
281 access_token_expiration_ > base::Time::Now()) {
282 LOG(INFO) << "Access token is still valid.";
283 return true;
284 }
285
Alex Vakulenko89dde2f2015-01-07 12:05:12 -0800286 auto response = chromeos::http::PostFormDataAndBlock(GetOAuthURL("token"), {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700287 {"refresh_token", refresh_token_},
288 {"client_id", client_id_},
289 {"client_secret", client_secret_},
290 {"grant_type", "refresh_token"},
Alex Vakulenko89dde2f2015-01-07 12:05:12 -0800291 }, {}, transport_, error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700292 if (!response)
293 return false;
294
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700295 auto json = ParseOAuthResponse(response.get(), error);
296 if (!json)
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700297 return false;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700298
299 int expires_in = 0;
300 if (!json->GetString("access_token", &access_token_) ||
301 !json->GetInteger("expires_in", &expires_in) ||
302 access_token_.empty() ||
303 expires_in <= 0) {
304 LOG(ERROR) << "Access token unavailable.";
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800305 chromeos::Error::AddTo(error, FROM_HERE, kErrorDomainOAuth2,
Alex Vakulenko5f472062014-08-14 17:54:04 -0700306 "unexpected_server_response",
307 "Access token unavailable");
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700308 return false;
309 }
310
311 access_token_expiration_ = base::Time::Now() +
312 base::TimeDelta::FromSeconds(expires_in);
313
314 LOG(INFO) << "Access token is refreshed for additional " << expires_in
315 << " seconds.";
316 return true;
317}
318
Anton Muhind8d32162014-10-02 20:37:00 +0400319std::unique_ptr<base::DictionaryValue>
320DeviceRegistrationInfo::BuildDeviceResource(chromeos::ErrorPtr* error) {
321 std::unique_ptr<base::DictionaryValue> commands =
322 command_manager_->GetCommandDictionary().GetCommandsAsJson(true, error);
323 if (!commands)
324 return nullptr;
325
326 std::unique_ptr<base::DictionaryValue> state =
327 state_manager_->GetStateValuesAsJson(error);
328 if (!state)
329 return nullptr;
330
331 std::unique_ptr<base::DictionaryValue> resource{new base::DictionaryValue};
332 if (!device_id_.empty())
333 resource->SetString("id", device_id_);
334 resource->SetString("deviceKind", device_kind_);
Anton Muhin9e19cdd2014-10-20 13:55:04 +0400335 resource->SetString("name", name_);
Anton Muhind8d32162014-10-02 20:37:00 +0400336 if (!display_name_.empty())
337 resource->SetString("displayName", display_name_);
Vitaly Bukad3eb19c2014-11-21 13:39:43 -0800338 if (!description_.empty())
339 resource->SetString("description", description_);
340 if (!location_.empty())
341 resource->SetString("location", location_);
Anton Muhind8d32162014-10-02 20:37:00 +0400342 resource->SetString("channel.supportedType", "xmpp");
343 resource->Set("commandDefs", commands.release());
344 resource->Set("state", state.release());
345
346 return resource;
347}
348
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700349std::unique_ptr<base::Value> DeviceRegistrationInfo::GetDeviceInfo(
Alex Vakulenko5f472062014-08-14 17:54:04 -0700350 chromeos::ErrorPtr* error) {
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700351 if (!CheckRegistration(error))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700352 return std::unique_ptr<base::Value>();
353
Anton Muhinac661ab2014-10-03 20:29:48 +0400354 // TODO(antonm): Switch to DoCloudRequest later.
Alex Vakulenko89dde2f2015-01-07 12:05:12 -0800355 auto response = chromeos::http::GetAndBlock(
Alex Vakulenkocca20932014-08-20 17:35:12 -0700356 GetDeviceURL(), {GetAuthorizationHeader()}, transport_, error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700357 int status_code = 0;
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700358 std::unique_ptr<base::DictionaryValue> json =
Alex Vakulenkocca20932014-08-20 17:35:12 -0700359 chromeos::http::ParseJsonResponse(response.get(), &status_code, error);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700360 if (json) {
Alex Vakulenkocca20932014-08-20 17:35:12 -0700361 if (status_code >= chromeos::http::status_code::BadRequest) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700362 LOG(WARNING) << "Failed to retrieve the device info. Response code = "
363 << status_code;
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700364 ParseGCDError(json.get(), error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700365 return std::unique_ptr<base::Value>();
366 }
367 }
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700368 return std::unique_ptr<base::Value>(json.release());
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700369}
370
371bool CheckParam(const std::string& param_name,
372 const std::string& param_value,
Alex Vakulenko5f472062014-08-14 17:54:04 -0700373 chromeos::ErrorPtr* error) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700374 if (!param_value.empty())
375 return true;
376
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800377 chromeos::Error::AddToPrintf(error, FROM_HERE, kErrorDomainBuffet,
378 "missing_parameter",
Alex Vakulenko5f472062014-08-14 17:54:04 -0700379 "Parameter %s not specified",
380 param_name.c_str());
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700381 return false;
382}
383
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400384std::string DeviceRegistrationInfo::RegisterDevice(
Alex Vakulenkoa9044342014-08-23 19:31:27 -0700385 const std::map<std::string, std::string>& params,
Alex Vakulenko5f472062014-08-14 17:54:04 -0700386 chromeos::ErrorPtr* error) {
Anton Muhina4803142014-09-24 19:30:45 +0400387 GetParamValue(params, "ticket_id", &ticket_id_);
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700388 GetParamValue(params, storage_keys::kClientId, &client_id_);
389 GetParamValue(params, storage_keys::kClientSecret, &client_secret_);
390 GetParamValue(params, storage_keys::kApiKey, &api_key_);
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700391 GetParamValue(params, storage_keys::kDeviceKind, &device_kind_);
Anton Muhin9e19cdd2014-10-20 13:55:04 +0400392 GetParamValue(params, storage_keys::kName, &name_);
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700393 GetParamValue(params, storage_keys::kDisplayName, &display_name_);
Vitaly Bukad3eb19c2014-11-21 13:39:43 -0800394 GetParamValue(params, storage_keys::kDescription, &description_);
395 GetParamValue(params, storage_keys::kLocation, &location_);
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700396 GetParamValue(params, storage_keys::kOAuthURL, &oauth_url_);
397 GetParamValue(params, storage_keys::kServiceURL, &service_url_);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700398
Anton Muhind8d32162014-10-02 20:37:00 +0400399 std::unique_ptr<base::DictionaryValue> device_draft =
400 BuildDeviceResource(error);
401 if (!device_draft)
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700402 return std::string();
403
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700404 base::DictionaryValue req_json;
Anton Muhina4803142014-09-24 19:30:45 +0400405 req_json.SetString("id", ticket_id_);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700406 req_json.SetString("oauthClientId", client_id_);
Anton Muhind8d32162014-10-02 20:37:00 +0400407 req_json.Set("deviceDraft", device_draft.release());
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700408
Anton Muhina4803142014-09-24 19:30:45 +0400409 auto url = GetServiceURL("registrationTickets/" + ticket_id_,
410 {{"key", api_key_}});
Anton Muhin532ff7e2014-09-29 23:21:21 +0400411 std::unique_ptr<chromeos::http::Response> response =
Alex Vakulenko89dde2f2015-01-07 12:05:12 -0800412 chromeos::http::PatchJsonAndBlock(url, &req_json, {}, transport_, error);
Anton Muhin532ff7e2014-09-29 23:21:21 +0400413 auto json_resp = chromeos::http::ParseJsonResponse(response.get(), nullptr,
414 error);
415 if (!json_resp)
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700416 return std::string();
Anton Muhin532ff7e2014-09-29 23:21:21 +0400417 if (!response->IsSuccessful())
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700418 return std::string();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700419
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400420 url = GetServiceURL("registrationTickets/" + ticket_id_ +
421 "/finalize?key=" + api_key_);
Alex Vakulenko89dde2f2015-01-07 12:05:12 -0800422 response = chromeos::http::SendRequestWithNoDataAndBlock(
423 chromeos::http::request_type::kPost, url, {}, transport_, error);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700424 if (!response)
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400425 return std::string();
426 json_resp = chromeos::http::ParseJsonResponse(response.get(), nullptr, error);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700427 if (!json_resp)
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400428 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700429 if (!response->IsSuccessful()) {
430 ParseGCDError(json_resp.get(), error);
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400431 return std::string();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700432 }
Anton Muhin532ff7e2014-09-29 23:21:21 +0400433
434 std::string auth_code;
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700435 if (!json_resp->GetString("robotAccountEmail", &device_robot_account_) ||
436 !json_resp->GetString("robotAccountAuthorizationCode", &auth_code) ||
437 !json_resp->GetString("deviceDraft.id", &device_id_)) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800438 chromeos::Error::AddTo(error, FROM_HERE, kErrorDomainGCD,
439 "unexpected_response",
Alex Vakulenko5f472062014-08-14 17:54:04 -0700440 "Device account missing in response");
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400441 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700442 }
443
444 // Now get access_token and refresh_token
Alex Vakulenko89dde2f2015-01-07 12:05:12 -0800445 response = chromeos::http::PostFormDataAndBlock(GetOAuthURL("token"), {
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700446 {"code", auth_code},
447 {"client_id", client_id_},
448 {"client_secret", client_secret_},
449 {"redirect_uri", "oob"},
450 {"scope", "https://www.googleapis.com/auth/clouddevices"},
451 {"grant_type", "authorization_code"}
Alex Vakulenko89dde2f2015-01-07 12:05:12 -0800452 }, {}, transport_, error);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700453 if (!response)
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400454 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700455
456 json_resp = ParseOAuthResponse(response.get(), error);
457 int expires_in = 0;
458 if (!json_resp ||
459 !json_resp->GetString("access_token", &access_token_) ||
460 !json_resp->GetString("refresh_token", &refresh_token_) ||
461 !json_resp->GetInteger("expires_in", &expires_in) ||
462 access_token_.empty() ||
463 refresh_token_.empty() ||
464 expires_in <= 0) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800465 chromeos::Error::AddTo(error, FROM_HERE,
466 kErrorDomainGCD, "unexpected_response",
Alex Vakulenko5f472062014-08-14 17:54:04 -0700467 "Device access_token missing in response");
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400468 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700469 }
470
471 access_token_expiration_ = base::Time::Now() +
472 base::TimeDelta::FromSeconds(expires_in);
473
474 Save();
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400475 return device_id_;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700476}
477
Anton Muhin633eded2014-10-03 20:40:10 +0400478namespace {
479
480template <class T>
481void PostToCallback(base::Callback<void(const T&)> callback,
482 std::unique_ptr<T> value) {
483 auto cb = [callback] (T* result) {
484 callback.Run(*result);
485 };
486 base::MessageLoop::current()->PostTask(
487 FROM_HERE, base::Bind(cb, base::Owned(value.release())));
488}
489
Anton Muhin0ae1fba2014-10-22 19:30:40 +0400490void PostRepeatingTask(const tracked_objects::Location& from_here,
491 base::Closure task,
492 base::TimeDelta delay) {
493 task.Run();
494 base::MessageLoop::current()->PostDelayedTask(
495 from_here, base::Bind(&PostRepeatingTask, from_here, task, delay), delay);
496}
497
Alex Vakulenko0357c032015-01-06 16:32:31 -0800498using ResponsePtr = scoped_ptr<chromeos::http::Response>;
Anton Muhin633eded2014-10-03 20:40:10 +0400499
Alex Vakulenko0357c032015-01-06 16:32:31 -0800500void SendRequestWithRetries(
Anton Muhin633eded2014-10-03 20:40:10 +0400501 const std::string& method,
502 const std::string& url,
503 const std::string& data,
504 const std::string& mime_type,
505 const chromeos::http::HeaderList& headers,
506 std::shared_ptr<chromeos::http::Transport> transport,
507 int num_retries,
Alex Vakulenko0357c032015-01-06 16:32:31 -0800508 const chromeos::http::SuccessCallback& success_callback,
509 const chromeos::http::ErrorCallback& error_callback) {
510 auto on_failure =
511 [method, url, data, mime_type, headers, transport, num_retries,
Alex Vakulenko6401d012015-01-16 07:40:43 -0800512 success_callback, error_callback](int request_id,
513 const chromeos::Error* error) {
Anton Muhin633eded2014-10-03 20:40:10 +0400514 if (num_retries > 0) {
Alex Vakulenko0357c032015-01-06 16:32:31 -0800515 SendRequestWithRetries(method, url, data, mime_type,
516 headers, transport, num_retries - 1,
517 success_callback, error_callback);
Anton Muhin633eded2014-10-03 20:40:10 +0400518 } else {
Alex Vakulenko6401d012015-01-16 07:40:43 -0800519 error_callback.Run(request_id, error);
Anton Muhin633eded2014-10-03 20:40:10 +0400520 }
521 };
522
Alex Vakulenko0357c032015-01-06 16:32:31 -0800523 auto on_success =
Alex Vakulenko6401d012015-01-16 07:40:43 -0800524 [on_failure, success_callback, error_callback](int request_id,
525 ResponsePtr response) {
Alex Vakulenko0357c032015-01-06 16:32:31 -0800526 int status_code = response->GetStatusCode();
527 if (status_code >= chromeos::http::status_code::Continue &&
528 status_code < chromeos::http::status_code::BadRequest) {
Alex Vakulenko6401d012015-01-16 07:40:43 -0800529 success_callback.Run(request_id, response.Pass());
Anton Muhin633eded2014-10-03 20:40:10 +0400530 return;
531 }
Anton Muhin633eded2014-10-03 20:40:10 +0400532
Alex Vakulenko0357c032015-01-06 16:32:31 -0800533 // TODO(antonm): Should add some useful information to error.
534 LOG(WARNING) << "Request failed. Response code = " << status_code;
Anton Muhin633eded2014-10-03 20:40:10 +0400535
Alex Vakulenko0357c032015-01-06 16:32:31 -0800536 chromeos::ErrorPtr error;
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800537 chromeos::Error::AddTo(&error, FROM_HERE, chromeos::errors::http::kDomain,
Anton Muhin233d2ee2014-10-22 15:16:24 +0400538 std::to_string(status_code),
539 response->GetStatusText());
Alex Vakulenko0357c032015-01-06 16:32:31 -0800540 if (status_code >= chromeos::http::status_code::InternalServerError &&
541 status_code < 600) {
542 // Request was valid, but server failed, retry.
543 // TODO(antonm): Implement exponential backoff.
544 // TODO(antonm): Reconsider status codes, maybe only some require
545 // retry.
546 // TODO(antonm): Support Retry-After header.
Alex Vakulenko6401d012015-01-16 07:40:43 -0800547 on_failure(request_id, error.get());
Alex Vakulenko0357c032015-01-06 16:32:31 -0800548 } else {
Alex Vakulenko6401d012015-01-16 07:40:43 -0800549 error_callback.Run(request_id, error.get());
Alex Vakulenko0357c032015-01-06 16:32:31 -0800550 }
551 };
552
553 chromeos::http::SendRequest(method, url, data.c_str(), data.size(),
554 mime_type, headers, transport,
555 base::Bind(on_success),
556 base::Bind(on_failure));
Anton Muhin633eded2014-10-03 20:40:10 +0400557}
558
559} // namespace
560
Anton Muhinac661ab2014-10-03 20:29:48 +0400561void DeviceRegistrationInfo::DoCloudRequest(
Anton Muhin233d2ee2014-10-22 15:16:24 +0400562 const std::string& method,
Anton Muhinac661ab2014-10-03 20:29:48 +0400563 const std::string& url,
564 const base::DictionaryValue* body,
Alex Vakulenko0357c032015-01-06 16:32:31 -0800565 const CloudRequestCallback& success_callback,
566 const CloudRequestErrorCallback& error_callback) {
567 // TODO(antonm): Add reauthorization on access token expiration (do not
Anton Muhinac661ab2014-10-03 20:29:48 +0400568 // forget about 5xx when fetching new access token).
569 // TODO(antonm): Add support for device removal.
570
Anton Muhinac661ab2014-10-03 20:29:48 +0400571 std::string data;
572 if (body)
573 base::JSONWriter::Write(body, &data);
574
575 const std::string mime_type{chromeos::mime::AppendParameter(
576 chromeos::mime::application::kJson,
577 chromeos::mime::parameters::kCharset,
578 "utf-8")};
579
Alex Vakulenko6401d012015-01-16 07:40:43 -0800580 auto request_cb =
581 [success_callback, error_callback](int request_id, ResponsePtr response) {
Anton Muhin633eded2014-10-03 20:40:10 +0400582 chromeos::ErrorPtr error;
Anton Muhinac661ab2014-10-03 20:29:48 +0400583
Anton Muhin633eded2014-10-03 20:40:10 +0400584 std::unique_ptr<base::DictionaryValue> json_resp{
Alex Vakulenko0357c032015-01-06 16:32:31 -0800585 chromeos::http::ParseJsonResponse(response.get(), nullptr, &error)};
Anton Muhin633eded2014-10-03 20:40:10 +0400586 if (!json_resp) {
Alex Vakulenko0357c032015-01-06 16:32:31 -0800587 error_callback.Run(error.get());
Anton Muhin633eded2014-10-03 20:40:10 +0400588 return;
589 }
Anton Muhinac661ab2014-10-03 20:29:48 +0400590
Alex Vakulenko0357c032015-01-06 16:32:31 -0800591 success_callback.Run(*json_resp);
Anton Muhinac661ab2014-10-03 20:29:48 +0400592 };
Anton Muhin633eded2014-10-03 20:40:10 +0400593
Alex Vakulenko6401d012015-01-16 07:40:43 -0800594 auto error_cb =
595 [error_callback](int request_id, const chromeos::Error* error) {
596 error_callback.Run(error);
597 };
598
Anton Muhin233d2ee2014-10-22 15:16:24 +0400599 auto transport = transport_;
Alex Vakulenko6401d012015-01-16 07:40:43 -0800600 auto error_callackback_with_reauthorization =
601 base::Bind([method, url, data, mime_type, transport, request_cb, error_cb]
602 (DeviceRegistrationInfo* self,
603 int request_id,
604 const chromeos::Error* error) {
Alex Vakulenko0357c032015-01-06 16:32:31 -0800605 if (error->HasError(chromeos::errors::http::kDomain,
606 std::to_string(chromeos::http::status_code::Denied))) {
Anton Muhin233d2ee2014-10-22 15:16:24 +0400607 chromeos::ErrorPtr reauthorization_error;
608 if (!self->ValidateAndRefreshAccessToken(&reauthorization_error)) {
609 // TODO(antonm): Check if the device has been actually removed.
Alex Vakulenko6401d012015-01-16 07:40:43 -0800610 error_cb(request_id, reauthorization_error.get());
Anton Muhin233d2ee2014-10-22 15:16:24 +0400611 return;
612 }
Alex Vakulenko0357c032015-01-06 16:32:31 -0800613 SendRequestWithRetries(method, url,
614 data, mime_type,
615 {self->GetAuthorizationHeader()},
616 transport,
617 7,
Alex Vakulenko6401d012015-01-16 07:40:43 -0800618 base::Bind(request_cb), base::Bind(error_cb));
Anton Muhin233d2ee2014-10-22 15:16:24 +0400619 } else {
Alex Vakulenko6401d012015-01-16 07:40:43 -0800620 error_cb(request_id, error);
Anton Muhin233d2ee2014-10-22 15:16:24 +0400621 }
622 }, base::Unretained(this));
623
Alex Vakulenko0357c032015-01-06 16:32:31 -0800624 SendRequestWithRetries(method, url,
625 data, mime_type,
626 {GetAuthorizationHeader()},
627 transport,
628 7,
629 base::Bind(request_cb),
630 error_callackback_with_reauthorization);
Anton Muhinac661ab2014-10-03 20:29:48 +0400631}
632
Anton Muhind8d32162014-10-02 20:37:00 +0400633void DeviceRegistrationInfo::StartDevice(chromeos::ErrorPtr* error) {
634 if (!CheckRegistration(error))
635 return;
636
Anton Muhinc635c592014-10-28 21:48:08 +0400637 base::Bind(
638 &DeviceRegistrationInfo::UpdateDeviceResource,
639 base::Unretained(this),
640 base::Bind(
641 &DeviceRegistrationInfo::FetchCommands,
642 base::Unretained(this),
643 base::Bind(
644 &DeviceRegistrationInfo::AbortLimboCommands,
645 base::Unretained(this),
646 base::Bind(
647 &DeviceRegistrationInfo::PeriodicallyPollCommands,
648 base::Unretained(this))))).Run();
649}
650
Anton Muhin59755522014-11-05 21:30:12 +0400651void DeviceRegistrationInfo::UpdateCommand(
652 const std::string& command_id,
653 const base::DictionaryValue& command_patch) {
654 DoCloudRequest(
655 chromeos::http::request_type::kPatch,
656 GetServiceURL("commands/" + command_id),
657 &command_patch,
658 base::Bind(&IgnoreCloudResult), base::Bind(&IgnoreCloudError));
659}
660
Anton Muhinc635c592014-10-28 21:48:08 +0400661void DeviceRegistrationInfo::UpdateDeviceResource(base::Closure callback) {
Anton Muhind8d32162014-10-02 20:37:00 +0400662 std::unique_ptr<base::DictionaryValue> device_resource =
Anton Muhinc635c592014-10-28 21:48:08 +0400663 BuildDeviceResource(nullptr);
Anton Muhind8d32162014-10-02 20:37:00 +0400664 if (!device_resource)
665 return;
666
Anton Muhinc635c592014-10-28 21:48:08 +0400667 DoCloudRequest(
668 chromeos::http::request_type::kPut,
669 GetDeviceURL(),
670 device_resource.get(),
671 base::Bind([callback](const base::DictionaryValue&){
672 base::MessageLoop::current()->PostTask(FROM_HERE, callback);
673 }),
674 // TODO(antonm): Failure to update device resource probably deserves
675 // some additional actions.
Anton Muhin5191e812014-10-30 17:49:48 +0400676 base::Bind(&IgnoreCloudError));
Anton Muhinc635c592014-10-28 21:48:08 +0400677}
Anton Muhina34f0d92014-10-03 21:09:40 +0400678
Anton Muhinc635c592014-10-28 21:48:08 +0400679void DeviceRegistrationInfo::FetchCommands(
680 base::Callback<void(const base::ListValue&)> callback) {
681 DoCloudRequest(
682 chromeos::http::request_type::kGet,
683 GetServiceURL("commands/queue", {{"deviceId", device_id_}}),
684 nullptr,
685 base::Bind([callback](const base::DictionaryValue& json) {
686 const base::ListValue* commands{nullptr};
687 if (!json.GetList("commands", &commands)) {
688 VLOG(1) << "No commands in the response.";
689 }
690 const base::ListValue empty;
691 callback.Run(commands ? *commands : empty);
692 }),
Anton Muhin5191e812014-10-30 17:49:48 +0400693 base::Bind(&IgnoreCloudError));
Anton Muhinc635c592014-10-28 21:48:08 +0400694}
Anton Muhina34f0d92014-10-03 21:09:40 +0400695
Anton Muhinc635c592014-10-28 21:48:08 +0400696void DeviceRegistrationInfo::AbortLimboCommands(
697 base::Closure callback, const base::ListValue& commands) {
698 const size_t size{commands.GetSize()};
699 for (size_t i = 0; i < size; ++i) {
700 const base::DictionaryValue* command{nullptr};
701 if (!commands.GetDictionary(i, &command)) {
702 LOG(WARNING) << "No command resource at " << i;
703 continue;
Anton Muhina34f0d92014-10-03 21:09:40 +0400704 }
Anton Muhinc635c592014-10-28 21:48:08 +0400705 std::string command_state;
706 if (!command->GetString("state", &command_state)) {
707 LOG(WARNING) << "Command with no state at " << i;
708 continue;
709 }
710 if (command_state != "error" &&
711 command_state != "inProgress" &&
712 command_state != "paused") {
713 // It's not a limbo command, ignore.
714 continue;
715 }
716 std::string command_id;
717 if (!command->GetString("id", &command_id)) {
718 LOG(WARNING) << "Command with no ID at " << i;
719 continue;
720 }
Anton Muhin6d2569e2014-10-30 12:32:27 +0400721
722 std::unique_ptr<base::DictionaryValue> command_copy{command->DeepCopy()};
723 command_copy->SetString("state", "aborted");
724 DoCloudRequest(
725 chromeos::http::request_type::kPut,
726 GetServiceURL("commands/" + command_id),
727 command_copy.get(),
Anton Muhin5191e812014-10-30 17:49:48 +0400728 base::Bind(&IgnoreCloudResult), base::Bind(&IgnoreCloudError));
Anton Muhinc635c592014-10-28 21:48:08 +0400729 }
Anton Muhin0ae1fba2014-10-22 19:30:40 +0400730
Anton Muhinc635c592014-10-28 21:48:08 +0400731 base::MessageLoop::current()->PostTask(FROM_HERE, callback);
732}
733
734void DeviceRegistrationInfo::PeriodicallyPollCommands() {
Anton Muhind07e2062014-10-27 10:53:29 +0400735 VLOG(1) << "Poll commands";
736 PostRepeatingTask(
737 FROM_HERE,
738 base::Bind(
739 &DeviceRegistrationInfo::FetchCommands,
740 base::Unretained(this),
741 base::Bind(&DeviceRegistrationInfo::PublishCommands,
742 base::Unretained(this))),
743 base::TimeDelta::FromSeconds(7));
Anton Muhinb8315622014-11-20 03:17:05 +0400744 // TODO(antonm): Use better trigger: when StateManager registers new updates,
Alex Vakulenko0357c032015-01-06 16:32:31 -0800745 // it should call closure which will post a task, probably with some
746 // throttling, to publish state updates.
Anton Muhinb8315622014-11-20 03:17:05 +0400747 PostRepeatingTask(
748 FROM_HERE,
749 base::Bind(&DeviceRegistrationInfo::PublishStateUpdates,
750 base::Unretained(this)),
751 base::TimeDelta::FromSeconds(7));
Anton Muhind07e2062014-10-27 10:53:29 +0400752}
753
754void DeviceRegistrationInfo::PublishCommands(const base::ListValue& commands) {
755 const CommandDictionary& command_dictionary =
756 command_manager_->GetCommandDictionary();
757
758 const size_t size{commands.GetSize()};
759 for (size_t i = 0; i < size; ++i) {
760 const base::DictionaryValue* command{nullptr};
761 if (!commands.GetDictionary(i, &command)) {
762 LOG(WARNING) << "No command resource at " << i;
763 continue;
764 }
765
766 std::unique_ptr<CommandInstance> command_instance =
767 CommandInstance::FromJson(command, command_dictionary, nullptr);
768 if (!command_instance) {
769 LOG(WARNING) << "Failed to parse a command";
770 continue;
771 }
772
Anton Muhin5191e812014-10-30 17:49:48 +0400773 // TODO(antonm): Properly process cancellation of commands.
Anton Muhin59755522014-11-05 21:30:12 +0400774 if (!command_manager_->FindCommand(command_instance->GetID())) {
775 std::unique_ptr<CommandProxyInterface> cloud_proxy{
776 new CloudCommandProxy(command_instance.get(), this)};
777 command_instance->AddProxy(std::move(cloud_proxy));
Anton Muhin5191e812014-10-30 17:49:48 +0400778 command_manager_->AddCommand(std::move(command_instance));
Anton Muhin59755522014-11-05 21:30:12 +0400779 }
Anton Muhind07e2062014-10-27 10:53:29 +0400780 }
Anton Muhind8d32162014-10-02 20:37:00 +0400781}
782
Anton Muhinb8315622014-11-20 03:17:05 +0400783void DeviceRegistrationInfo::PublishStateUpdates() {
784 VLOG(1) << "PublishStateUpdates";
785 const std::vector<StateChange> state_changes{
786 state_manager_->GetAndClearRecordedStateChanges()};
787 if (state_changes.empty())
788 return;
789
790 std::unique_ptr<base::ListValue> patches{new base::ListValue};
791 for (const auto& state_change : state_changes) {
792 std::unique_ptr<base::DictionaryValue> patch{new base::DictionaryValue};
Anton Muhin76933fd2014-11-21 21:25:18 +0400793 patch->SetString("timeMs",
794 std::to_string(state_change.timestamp.ToJavaTime()));
Anton Muhinb8315622014-11-20 03:17:05 +0400795
796 std::unique_ptr<base::DictionaryValue> changes{new base::DictionaryValue};
797 for (const auto& pair : state_change.changed_properties) {
798 auto value = pair.second->ToJson(nullptr);
799 if (!value) {
800 return;
801 }
Alex Vakulenko61ad4db2015-01-20 10:50:04 -0800802 // The key in |pair.first| is the full property name in format
803 // "package.property_name", so must use DictionaryValue::Set() instead of
804 // DictionaryValue::SetWithoutPathExpansion to recreate the JSON
805 // property tree properly.
806 changes->Set(pair.first, value.release());
Anton Muhinb8315622014-11-20 03:17:05 +0400807 }
808 patch->Set("patch", changes.release());
809
810 patches->Append(patch.release());
811 }
812
813 base::DictionaryValue body;
Anton Muhin76933fd2014-11-21 21:25:18 +0400814 body.SetString("requestTimeMs",
815 std::to_string(base::Time::Now().ToJavaTime()));
Anton Muhinb8315622014-11-20 03:17:05 +0400816 body.Set("patches", patches.release());
817
818 DoCloudRequest(
819 chromeos::http::request_type::kPost,
820 GetDeviceURL("patchState"),
821 &body,
822 base::Bind(&IgnoreCloudResult), base::Bind(&IgnoreCloudError));
823}
824
Anton Muhin332df192014-11-22 05:59:14 +0400825void DeviceRegistrationInfo::GetParamValue(
826 const std::map<std::string, std::string>& params,
827 const std::string& param_name,
828 std::string* param_value) {
829 auto p = params.find(param_name);
830 if (p != params.end()) {
831 *param_value = p->second;
832 return;
833 }
834
835 bool present = config_store_->GetString(param_name, param_value);
836 CHECK(present) << "No default for parameter " << param_name;
837}
838
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700839} // namespace buffet