blob: 6336c5dbe1e4855c60c7b67754c1710f7676721c [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
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070038// Persistent keys
39const char kClientId[] = "client_id";
40const char kClientSecret[] = "client_secret";
41const char kApiKey[] = "api_key";
42const char kRefreshToken[] = "refresh_token";
43const char kDeviceId[] = "device_id";
44const char kOAuthURL[] = "oauth_url";
45const char kServiceURL[] = "service_url";
46const char kRobotAccount[] = "robot_account";
47// Transient keys
48const char kDeviceKind[] = "device_kind";
Anton Muhin9e19cdd2014-10-20 13:55:04 +040049const char kName[] = "name";
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070050const char kDisplayName[] = "display_name";
Vitaly Bukad3eb19c2014-11-21 13:39:43 -080051const char kDescription[] = "description";
52const char kLocation[] = "location";
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070053
Alex Vakulenko8e34d392014-04-29 11:02:56 -070054} // namespace storage_keys
55} // namespace buffet
56
57namespace {
58
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070059const base::FilePath::CharType kDeviceInfoFilePath[] =
60 FILE_PATH_LITERAL("/var/lib/buffet/device_reg_info");
61
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070062std::pair<std::string, std::string> BuildAuthHeader(
63 const std::string& access_token_type,
64 const std::string& access_token) {
Alex Vakulenkoaf23b322014-05-08 16:25:45 -070065 std::string authorization =
Alex Vakulenkob8fc1df2014-08-20 15:38:07 -070066 chromeos::string_utils::Join(' ', access_token_type, access_token);
Alex Vakulenkocca20932014-08-20 17:35:12 -070067 return {chromeos::http::request_header::kAuthorization, authorization};
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070068}
69
70std::unique_ptr<base::DictionaryValue> ParseOAuthResponse(
Alex Vakulenkocca20932014-08-20 17:35:12 -070071 const chromeos::http::Response* response, chromeos::ErrorPtr* error) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070072 int code = 0;
Alex Vakulenkocca20932014-08-20 17:35:12 -070073 auto resp = chromeos::http::ParseJsonResponse(response, &code, error);
74 if (resp && code >= chromeos::http::status_code::BadRequest) {
Alex Vakulenkob3aac252014-05-07 17:35:24 -070075 if (error) {
76 std::string error_code, error_message;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070077 if (resp->GetString("error", &error_code) &&
Alex Vakulenkob3aac252014-05-07 17:35:24 -070078 resp->GetString("error_description", &error_message)) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -080079 chromeos::Error::AddTo(error, FROM_HERE, buffet::kErrorDomainOAuth2,
80 error_code, error_message);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070081 } else {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -080082 chromeos::Error::AddTo(error, FROM_HERE, buffet::kErrorDomainOAuth2,
Alex Vakulenko5f472062014-08-14 17:54:04 -070083 "unexpected_response", "Unexpected OAuth error");
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070084 }
85 }
86 return std::unique_ptr<base::DictionaryValue>();
87 }
88 return resp;
89}
90
Alex Vakulenko5f472062014-08-14 17:54:04 -070091inline void SetUnexpectedError(chromeos::ErrorPtr* error) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -080092 chromeos::Error::AddTo(error, FROM_HERE, buffet::kErrorDomainGCD,
93 "unexpected_response", "Unexpected GCD error");
Alex Vakulenkob3aac252014-05-07 17:35:24 -070094}
95
Alex Vakulenko5f472062014-08-14 17:54:04 -070096void ParseGCDError(const base::DictionaryValue* json,
97 chromeos::ErrorPtr* error) {
Alex Vakulenkob3aac252014-05-07 17:35:24 -070098 if (!error)
99 return;
100
101 const base::Value* list_value = nullptr;
102 const base::ListValue* error_list = nullptr;
103 if (!json->Get("error.errors", &list_value) ||
104 !list_value->GetAsList(&error_list)) {
105 SetUnexpectedError(error);
106 return;
107 }
108
109 for (size_t i = 0; i < error_list->GetSize(); i++) {
110 const base::Value* error_value = nullptr;
111 const base::DictionaryValue* error_object = nullptr;
112 if (!error_list->Get(i, &error_value) ||
113 !error_value->GetAsDictionary(&error_object)) {
114 SetUnexpectedError(error);
115 continue;
116 }
117 std::string error_code, error_message;
118 if (error_object->GetString("reason", &error_code) &&
119 error_object->GetString("message", &error_message)) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800120 chromeos::Error::AddTo(error, FROM_HERE, buffet::kErrorDomainGCDServer,
Alex Vakulenko5f472062014-08-14 17:54:04 -0700121 error_code, error_message);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700122 } else {
123 SetUnexpectedError(error);
124 }
125 }
126}
127
Alex Vakulenkobda220a2014-04-18 15:25:44 -0700128std::string BuildURL(const std::string& url,
129 const std::vector<std::string>& subpaths,
Alex Vakulenkoe4879a22014-08-20 15:47:36 -0700130 const chromeos::data_encoding::WebParamList& params) {
Alex Vakulenkobd5b5442014-08-20 16:16:34 -0700131 std::string result = chromeos::url::CombineMultiple(url, subpaths);
132 return chromeos::url::AppendQueryParams(result, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700133}
134
Anton Muhin5191e812014-10-30 17:49:48 +0400135void IgnoreCloudError(const chromeos::Error&) {
136}
137
138void IgnoreCloudResult(const base::DictionaryValue&) {
139}
Anton Muhin6d2569e2014-10-30 12:32:27 +0400140
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700141} // anonymous namespace
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700142
143namespace buffet {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700144
Alex Vakulenko1f30a622014-07-23 11:13:15 -0700145DeviceRegistrationInfo::DeviceRegistrationInfo(
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700146 const std::shared_ptr<CommandManager>& command_manager,
Anton Muhin332df192014-11-22 05:59:14 +0400147 const std::shared_ptr<StateManager>& state_manager,
148 std::unique_ptr<chromeos::KeyValueStore> config_store)
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700149 : DeviceRegistrationInfo(
150 command_manager,
151 state_manager,
Anton Muhin332df192014-11-22 05:59:14 +0400152 std::move(config_store),
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700153 chromeos::http::Transport::CreateDefault(),
154 // TODO(avakulenko): Figure out security implications of storing
155 // this data unencrypted.
156 std::make_shared<FileStorage>(base::FilePath{kDeviceInfoFilePath})) {
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700157}
158
159DeviceRegistrationInfo::DeviceRegistrationInfo(
Alex Vakulenko1f30a622014-07-23 11:13:15 -0700160 const std::shared_ptr<CommandManager>& command_manager,
Anton Muhinb8315622014-11-20 03:17:05 +0400161 const std::shared_ptr<StateManager>& state_manager,
Anton Muhin332df192014-11-22 05:59:14 +0400162 std::unique_ptr<chromeos::KeyValueStore> config_store,
Alex Vakulenkocca20932014-08-20 17:35:12 -0700163 const std::shared_ptr<chromeos::http::Transport>& transport,
Alex Vakulenko1f30a622014-07-23 11:13:15 -0700164 const std::shared_ptr<StorageInterface>& storage)
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700165 : transport_{transport},
166 storage_{storage},
167 command_manager_{command_manager},
Anton Muhin332df192014-11-22 05:59:14 +0400168 state_manager_{state_manager},
169 config_store_{std::move(config_store)} {
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700170}
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700171
Anton Muhin332df192014-11-22 05:59:14 +0400172DeviceRegistrationInfo::~DeviceRegistrationInfo() = default;
173
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700174std::pair<std::string, std::string>
175 DeviceRegistrationInfo::GetAuthorizationHeader() const {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700176 return BuildAuthHeader("Bearer", access_token_);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700177}
178
179std::string DeviceRegistrationInfo::GetServiceURL(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700180 const std::string& subpath,
Alex Vakulenkoe4879a22014-08-20 15:47:36 -0700181 const chromeos::data_encoding::WebParamList& params) const {
Alex Vakulenkobda220a2014-04-18 15:25:44 -0700182 return BuildURL(service_url_, {subpath}, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700183}
184
185std::string DeviceRegistrationInfo::GetDeviceURL(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700186 const std::string& subpath,
Alex Vakulenkoe4879a22014-08-20 15:47:36 -0700187 const chromeos::data_encoding::WebParamList& params) const {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700188 CHECK(!device_id_.empty()) << "Must have a valid device ID";
Alex Vakulenkobda220a2014-04-18 15:25:44 -0700189 return BuildURL(service_url_, {"devices", device_id_, subpath}, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700190}
191
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700192std::string DeviceRegistrationInfo::GetOAuthURL(
193 const std::string& subpath,
Alex Vakulenkoe4879a22014-08-20 15:47:36 -0700194 const chromeos::data_encoding::WebParamList& params) const {
Alex Vakulenkobda220a2014-04-18 15:25:44 -0700195 return BuildURL(oauth_url_, {subpath}, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700196}
197
Alex Vakulenko5f472062014-08-14 17:54:04 -0700198std::string DeviceRegistrationInfo::GetDeviceId(chromeos::ErrorPtr* error) {
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700199 return CheckRegistration(error) ? device_id_ : std::string();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700200}
201
202bool DeviceRegistrationInfo::Load() {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700203 auto value = storage_->Load();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700204 const base::DictionaryValue* dict = nullptr;
205 if (!value || !value->GetAsDictionary(&dict))
206 return false;
207
208 // Get the values into temp variables first to make sure we can get
209 // all the data correctly before changing the state of this object.
210 std::string client_id;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700211 if (!dict->GetString(storage_keys::kClientId, &client_id))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700212 return false;
213 std::string client_secret;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700214 if (!dict->GetString(storage_keys::kClientSecret, &client_secret))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700215 return false;
216 std::string api_key;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700217 if (!dict->GetString(storage_keys::kApiKey, &api_key))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700218 return false;
219 std::string refresh_token;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700220 if (!dict->GetString(storage_keys::kRefreshToken, &refresh_token))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700221 return false;
222 std::string device_id;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700223 if (!dict->GetString(storage_keys::kDeviceId, &device_id))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700224 return false;
225 std::string oauth_url;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700226 if (!dict->GetString(storage_keys::kOAuthURL, &oauth_url))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700227 return false;
228 std::string service_url;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700229 if (!dict->GetString(storage_keys::kServiceURL, &service_url))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700230 return false;
231 std::string device_robot_account;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700232 if (!dict->GetString(storage_keys::kRobotAccount, &device_robot_account))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700233 return false;
234
235 client_id_ = client_id;
236 client_secret_ = client_secret;
237 api_key_ = api_key;
238 refresh_token_ = refresh_token;
239 device_id_ = device_id;
240 oauth_url_ = oauth_url;
241 service_url_ = service_url;
242 device_robot_account_ = device_robot_account;
243 return true;
244}
245
246bool DeviceRegistrationInfo::Save() const {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700247 base::DictionaryValue dict;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700248 dict.SetString(storage_keys::kClientId, client_id_);
249 dict.SetString(storage_keys::kClientSecret, client_secret_);
250 dict.SetString(storage_keys::kApiKey, api_key_);
251 dict.SetString(storage_keys::kRefreshToken, refresh_token_);
252 dict.SetString(storage_keys::kDeviceId, device_id_);
253 dict.SetString(storage_keys::kOAuthURL, oauth_url_);
254 dict.SetString(storage_keys::kServiceURL, service_url_);
255 dict.SetString(storage_keys::kRobotAccount, device_robot_account_);
256 return storage_->Save(&dict);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700257}
258
Alex Vakulenko5f472062014-08-14 17:54:04 -0700259bool DeviceRegistrationInfo::CheckRegistration(chromeos::ErrorPtr* error) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700260 LOG(INFO) << "Checking device registration record.";
261 if (refresh_token_.empty() ||
262 device_id_.empty() ||
263 device_robot_account_.empty()) {
264 LOG(INFO) << "No valid device registration record found.";
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800265 chromeos::Error::AddTo(error, FROM_HERE, kErrorDomainGCD,
266 "device_not_registered",
Alex Vakulenko5f472062014-08-14 17:54:04 -0700267 "No valid device registration record found");
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700268 return false;
269 }
270
271 LOG(INFO) << "Device registration record found.";
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700272 return ValidateAndRefreshAccessToken(error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700273}
274
Alex Vakulenko5f472062014-08-14 17:54:04 -0700275bool DeviceRegistrationInfo::ValidateAndRefreshAccessToken(
276 chromeos::ErrorPtr* error) {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700277 LOG(INFO) << "Checking access token expiration.";
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700278 if (!access_token_.empty() &&
279 !access_token_expiration_.is_null() &&
280 access_token_expiration_ > base::Time::Now()) {
281 LOG(INFO) << "Access token is still valid.";
282 return true;
283 }
284
Alex Vakulenkocca20932014-08-20 17:35:12 -0700285 auto response = chromeos::http::PostFormData(GetOAuthURL("token"), {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700286 {"refresh_token", refresh_token_},
287 {"client_id", client_id_},
288 {"client_secret", client_secret_},
289 {"grant_type", "refresh_token"},
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700290 }, transport_, error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700291 if (!response)
292 return false;
293
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700294 auto json = ParseOAuthResponse(response.get(), error);
295 if (!json)
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700296 return false;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700297
298 int expires_in = 0;
299 if (!json->GetString("access_token", &access_token_) ||
300 !json->GetInteger("expires_in", &expires_in) ||
301 access_token_.empty() ||
302 expires_in <= 0) {
303 LOG(ERROR) << "Access token unavailable.";
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800304 chromeos::Error::AddTo(error, FROM_HERE, kErrorDomainOAuth2,
Alex Vakulenko5f472062014-08-14 17:54:04 -0700305 "unexpected_server_response",
306 "Access token unavailable");
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700307 return false;
308 }
309
310 access_token_expiration_ = base::Time::Now() +
311 base::TimeDelta::FromSeconds(expires_in);
312
313 LOG(INFO) << "Access token is refreshed for additional " << expires_in
314 << " seconds.";
315 return true;
316}
317
Anton Muhind8d32162014-10-02 20:37:00 +0400318std::unique_ptr<base::DictionaryValue>
319DeviceRegistrationInfo::BuildDeviceResource(chromeos::ErrorPtr* error) {
320 std::unique_ptr<base::DictionaryValue> commands =
321 command_manager_->GetCommandDictionary().GetCommandsAsJson(true, error);
322 if (!commands)
323 return nullptr;
324
325 std::unique_ptr<base::DictionaryValue> state =
326 state_manager_->GetStateValuesAsJson(error);
327 if (!state)
328 return nullptr;
329
330 std::unique_ptr<base::DictionaryValue> resource{new base::DictionaryValue};
331 if (!device_id_.empty())
332 resource->SetString("id", device_id_);
333 resource->SetString("deviceKind", device_kind_);
Anton Muhin9e19cdd2014-10-20 13:55:04 +0400334 resource->SetString("name", name_);
Anton Muhind8d32162014-10-02 20:37:00 +0400335 if (!display_name_.empty())
336 resource->SetString("displayName", display_name_);
Vitaly Bukad3eb19c2014-11-21 13:39:43 -0800337 if (!description_.empty())
338 resource->SetString("description", description_);
339 if (!location_.empty())
340 resource->SetString("location", location_);
Anton Muhind8d32162014-10-02 20:37:00 +0400341 resource->SetString("channel.supportedType", "xmpp");
342 resource->Set("commandDefs", commands.release());
343 resource->Set("state", state.release());
344
345 return resource;
346}
347
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700348std::unique_ptr<base::Value> DeviceRegistrationInfo::GetDeviceInfo(
Alex Vakulenko5f472062014-08-14 17:54:04 -0700349 chromeos::ErrorPtr* error) {
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700350 if (!CheckRegistration(error))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700351 return std::unique_ptr<base::Value>();
352
Anton Muhinac661ab2014-10-03 20:29:48 +0400353 // TODO(antonm): Switch to DoCloudRequest later.
Alex Vakulenkocca20932014-08-20 17:35:12 -0700354 auto response = chromeos::http::Get(
355 GetDeviceURL(), {GetAuthorizationHeader()}, transport_, error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700356 int status_code = 0;
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700357 std::unique_ptr<base::DictionaryValue> json =
Alex Vakulenkocca20932014-08-20 17:35:12 -0700358 chromeos::http::ParseJsonResponse(response.get(), &status_code, error);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700359 if (json) {
Alex Vakulenkocca20932014-08-20 17:35:12 -0700360 if (status_code >= chromeos::http::status_code::BadRequest) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700361 LOG(WARNING) << "Failed to retrieve the device info. Response code = "
362 << status_code;
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700363 ParseGCDError(json.get(), error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700364 return std::unique_ptr<base::Value>();
365 }
366 }
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700367 return std::unique_ptr<base::Value>(json.release());
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700368}
369
370bool CheckParam(const std::string& param_name,
371 const std::string& param_value,
Alex Vakulenko5f472062014-08-14 17:54:04 -0700372 chromeos::ErrorPtr* error) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700373 if (!param_value.empty())
374 return true;
375
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800376 chromeos::Error::AddToPrintf(error, FROM_HERE, kErrorDomainBuffet,
377 "missing_parameter",
Alex Vakulenko5f472062014-08-14 17:54:04 -0700378 "Parameter %s not specified",
379 param_name.c_str());
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700380 return false;
381}
382
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400383std::string DeviceRegistrationInfo::RegisterDevice(
Alex Vakulenkoa9044342014-08-23 19:31:27 -0700384 const std::map<std::string, std::string>& params,
Alex Vakulenko5f472062014-08-14 17:54:04 -0700385 chromeos::ErrorPtr* error) {
Anton Muhina4803142014-09-24 19:30:45 +0400386 GetParamValue(params, "ticket_id", &ticket_id_);
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700387 GetParamValue(params, storage_keys::kClientId, &client_id_);
388 GetParamValue(params, storage_keys::kClientSecret, &client_secret_);
389 GetParamValue(params, storage_keys::kApiKey, &api_key_);
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700390 GetParamValue(params, storage_keys::kDeviceKind, &device_kind_);
Anton Muhin9e19cdd2014-10-20 13:55:04 +0400391 GetParamValue(params, storage_keys::kName, &name_);
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700392 GetParamValue(params, storage_keys::kDisplayName, &display_name_);
Vitaly Bukad3eb19c2014-11-21 13:39:43 -0800393 GetParamValue(params, storage_keys::kDescription, &description_);
394 GetParamValue(params, storage_keys::kLocation, &location_);
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700395 GetParamValue(params, storage_keys::kOAuthURL, &oauth_url_);
396 GetParamValue(params, storage_keys::kServiceURL, &service_url_);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700397
Anton Muhind8d32162014-10-02 20:37:00 +0400398 std::unique_ptr<base::DictionaryValue> device_draft =
399 BuildDeviceResource(error);
400 if (!device_draft)
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700401 return std::string();
402
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700403 base::DictionaryValue req_json;
Anton Muhina4803142014-09-24 19:30:45 +0400404 req_json.SetString("id", ticket_id_);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700405 req_json.SetString("oauthClientId", client_id_);
Anton Muhind8d32162014-10-02 20:37:00 +0400406 req_json.Set("deviceDraft", device_draft.release());
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700407
Anton Muhina4803142014-09-24 19:30:45 +0400408 auto url = GetServiceURL("registrationTickets/" + ticket_id_,
409 {{"key", api_key_}});
Anton Muhin532ff7e2014-09-29 23:21:21 +0400410 std::unique_ptr<chromeos::http::Response> response =
411 chromeos::http::PatchJson(url, &req_json, transport_, error);
412 auto json_resp = chromeos::http::ParseJsonResponse(response.get(), nullptr,
413 error);
414 if (!json_resp)
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700415 return std::string();
Anton Muhin532ff7e2014-09-29 23:21:21 +0400416 if (!response->IsSuccessful())
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700417 return std::string();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700418
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700419 std::string auth_url = GetOAuthURL("auth", {
420 {"scope", "https://www.googleapis.com/auth/clouddevices"},
421 {"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"},
422 {"response_type", "code"},
423 {"client_id", client_id_}
424 });
425
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400426 url = GetServiceURL("registrationTickets/" + ticket_id_ +
427 "/finalize?key=" + api_key_);
428 response = chromeos::http::PostBinary(url, nullptr, 0, transport_, error);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700429 if (!response)
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400430 return std::string();
431 json_resp = chromeos::http::ParseJsonResponse(response.get(), nullptr, error);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700432 if (!json_resp)
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400433 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700434 if (!response->IsSuccessful()) {
435 ParseGCDError(json_resp.get(), error);
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400436 return std::string();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700437 }
Anton Muhin532ff7e2014-09-29 23:21:21 +0400438
439 std::string auth_code;
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700440 if (!json_resp->GetString("robotAccountEmail", &device_robot_account_) ||
441 !json_resp->GetString("robotAccountAuthorizationCode", &auth_code) ||
442 !json_resp->GetString("deviceDraft.id", &device_id_)) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800443 chromeos::Error::AddTo(error, FROM_HERE, kErrorDomainGCD,
444 "unexpected_response",
Alex Vakulenko5f472062014-08-14 17:54:04 -0700445 "Device account missing in response");
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400446 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700447 }
448
449 // Now get access_token and refresh_token
Alex Vakulenkocca20932014-08-20 17:35:12 -0700450 response = chromeos::http::PostFormData(GetOAuthURL("token"), {
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700451 {"code", auth_code},
452 {"client_id", client_id_},
453 {"client_secret", client_secret_},
454 {"redirect_uri", "oob"},
455 {"scope", "https://www.googleapis.com/auth/clouddevices"},
456 {"grant_type", "authorization_code"}
457 }, transport_, error);
458 if (!response)
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400459 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700460
461 json_resp = ParseOAuthResponse(response.get(), error);
462 int expires_in = 0;
463 if (!json_resp ||
464 !json_resp->GetString("access_token", &access_token_) ||
465 !json_resp->GetString("refresh_token", &refresh_token_) ||
466 !json_resp->GetInteger("expires_in", &expires_in) ||
467 access_token_.empty() ||
468 refresh_token_.empty() ||
469 expires_in <= 0) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800470 chromeos::Error::AddTo(error, FROM_HERE,
471 kErrorDomainGCD, "unexpected_response",
Alex Vakulenko5f472062014-08-14 17:54:04 -0700472 "Device access_token missing in response");
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400473 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700474 }
475
476 access_token_expiration_ = base::Time::Now() +
477 base::TimeDelta::FromSeconds(expires_in);
478
479 Save();
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400480 return device_id_;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700481}
482
Anton Muhin633eded2014-10-03 20:40:10 +0400483namespace {
484
485template <class T>
486void PostToCallback(base::Callback<void(const T&)> callback,
487 std::unique_ptr<T> value) {
488 auto cb = [callback] (T* result) {
489 callback.Run(*result);
490 };
491 base::MessageLoop::current()->PostTask(
492 FROM_HERE, base::Bind(cb, base::Owned(value.release())));
493}
494
Anton Muhin0ae1fba2014-10-22 19:30:40 +0400495void PostRepeatingTask(const tracked_objects::Location& from_here,
496 base::Closure task,
497 base::TimeDelta delay) {
498 task.Run();
499 base::MessageLoop::current()->PostDelayedTask(
500 from_here, base::Bind(&PostRepeatingTask, from_here, task, delay), delay);
501}
502
Anton Muhin633eded2014-10-03 20:40:10 +0400503// TODO(antonm): May belong to chromeos/http.
504
505void SendRequestAsync(
506 const std::string& method,
507 const std::string& url,
508 const std::string& data,
509 const std::string& mime_type,
510 const chromeos::http::HeaderList& headers,
511 std::shared_ptr<chromeos::http::Transport> transport,
512 int num_retries,
513 base::Callback<void(const chromeos::http::Response&)> callback,
514 base::Callback<void(const chromeos::Error&)> errorback) {
515 chromeos::ErrorPtr error;
516
517 auto on_retriable_failure = [&error, method, url, data, mime_type,
518 headers, transport, num_retries, callback, errorback] () {
519 if (num_retries > 0) {
520 auto c = [method, url, data, mime_type, headers, transport,
521 num_retries, callback, errorback] () {
522 SendRequestAsync(method, url,
523 data, mime_type,
524 headers,
525 transport,
526 num_retries - 1,
527 callback, errorback);
528 };
Anton Muhin0ae1fba2014-10-22 19:30:40 +0400529 base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(c));
Anton Muhin633eded2014-10-03 20:40:10 +0400530 } else {
531 PostToCallback(errorback, std::move(error));
532 }
533 };
534
535 chromeos::http::Request request(url, method.c_str(), transport);
536 request.AddHeaders(headers);
537 if (!data.empty()) {
538 request.SetContentType(mime_type.c_str());
539 if (!request.AddRequestBody(data.c_str(), data.size(), &error)) {
540 on_retriable_failure();
541 return;
542 }
543 }
544
545 std::unique_ptr<chromeos::http::Response> response{
546 request.GetResponse(&error)};
547 if (!response) {
548 on_retriable_failure();
549 return;
550 }
551
552 int status_code{response->GetStatusCode()};
553 if (status_code >= chromeos::http::status_code::Continue &&
554 status_code < chromeos::http::status_code::BadRequest) {
555 PostToCallback(callback, std::move(response));
556 return;
557 }
558
559 // TODO(antonm): Should add some useful information to error.
560 LOG(WARNING) << "Request failed. Response code = " << status_code;
561
562 if (status_code >= 500 && status_code < 600) {
563 // Request was valid, but server failed, retry.
564 // TODO(antonm): Implement exponential backoff.
565 // TODO(antonm): Reconsider status codes, maybe only some require
566 // retry.
567 // TODO(antonm): Support Retry-After header.
568 on_retriable_failure();
569 } else {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800570 chromeos::Error::AddTo(&error, FROM_HERE, chromeos::errors::http::kDomain,
Anton Muhin233d2ee2014-10-22 15:16:24 +0400571 std::to_string(status_code),
572 response->GetStatusText());
Anton Muhin633eded2014-10-03 20:40:10 +0400573 PostToCallback(errorback, std::move(error));
574 }
575}
576
577} // namespace
578
Anton Muhinac661ab2014-10-03 20:29:48 +0400579void DeviceRegistrationInfo::DoCloudRequest(
Anton Muhin233d2ee2014-10-22 15:16:24 +0400580 const std::string& method,
Anton Muhinac661ab2014-10-03 20:29:48 +0400581 const std::string& url,
582 const base::DictionaryValue* body,
583 CloudRequestCallback callback,
584 CloudRequestErroback errorback) {
Anton Muhinac661ab2014-10-03 20:29:48 +0400585 // TODO(antonm): Add reauthorisation on access token expiration (do not
586 // forget about 5xx when fetching new access token).
587 // TODO(antonm): Add support for device removal.
588
Anton Muhinac661ab2014-10-03 20:29:48 +0400589 std::string data;
590 if (body)
591 base::JSONWriter::Write(body, &data);
592
593 const std::string mime_type{chromeos::mime::AppendParameter(
594 chromeos::mime::application::kJson,
595 chromeos::mime::parameters::kCharset,
596 "utf-8")};
597
Anton Muhin633eded2014-10-03 20:40:10 +0400598 auto request_cb = [callback, errorback] (
599 const chromeos::http::Response& response) {
600 chromeos::ErrorPtr error;
Anton Muhinac661ab2014-10-03 20:29:48 +0400601
Anton Muhin633eded2014-10-03 20:40:10 +0400602 std::unique_ptr<base::DictionaryValue> json_resp{
603 chromeos::http::ParseJsonResponse(&response, nullptr, &error)};
604 if (!json_resp) {
605 PostToCallback(errorback, std::move(error));
606 return;
607 }
Anton Muhinac661ab2014-10-03 20:29:48 +0400608
Anton Muhin633eded2014-10-03 20:40:10 +0400609 PostToCallback(callback, std::move(json_resp));
Anton Muhinac661ab2014-10-03 20:29:48 +0400610 };
Anton Muhin633eded2014-10-03 20:40:10 +0400611
Anton Muhin233d2ee2014-10-22 15:16:24 +0400612 auto transport = transport_;
613 auto errorback_with_reauthorization = base::Bind(
614 [method, url, data, mime_type, transport, request_cb, errorback]
615 (DeviceRegistrationInfo* self, const chromeos::Error& error) {
616 if (error.HasError(chromeos::errors::http::kDomain,
617 std::to_string(chromeos::http::status_code::Denied))) {
618 chromeos::ErrorPtr reauthorization_error;
619 if (!self->ValidateAndRefreshAccessToken(&reauthorization_error)) {
620 // TODO(antonm): Check if the device has been actually removed.
621 errorback.Run(*reauthorization_error.get());
622 return;
623 }
624 SendRequestAsync(method, url,
625 data, mime_type,
626 {self->GetAuthorizationHeader()},
627 transport,
628 7,
629 base::Bind(request_cb), errorback);
630 } else {
631 errorback.Run(error);
632 }
633 }, base::Unretained(this));
634
Anton Muhin633eded2014-10-03 20:40:10 +0400635 SendRequestAsync(method, url,
636 data, mime_type,
637 {GetAuthorizationHeader()},
Anton Muhin233d2ee2014-10-22 15:16:24 +0400638 transport,
Anton Muhin633eded2014-10-03 20:40:10 +0400639 7,
Anton Muhin233d2ee2014-10-22 15:16:24 +0400640 base::Bind(request_cb), errorback_with_reauthorization);
Anton Muhinac661ab2014-10-03 20:29:48 +0400641}
642
Anton Muhind8d32162014-10-02 20:37:00 +0400643void DeviceRegistrationInfo::StartDevice(chromeos::ErrorPtr* error) {
644 if (!CheckRegistration(error))
645 return;
646
Anton Muhinc635c592014-10-28 21:48:08 +0400647 base::Bind(
648 &DeviceRegistrationInfo::UpdateDeviceResource,
649 base::Unretained(this),
650 base::Bind(
651 &DeviceRegistrationInfo::FetchCommands,
652 base::Unretained(this),
653 base::Bind(
654 &DeviceRegistrationInfo::AbortLimboCommands,
655 base::Unretained(this),
656 base::Bind(
657 &DeviceRegistrationInfo::PeriodicallyPollCommands,
658 base::Unretained(this))))).Run();
659}
660
Anton Muhin59755522014-11-05 21:30:12 +0400661void DeviceRegistrationInfo::UpdateCommand(
662 const std::string& command_id,
663 const base::DictionaryValue& command_patch) {
664 DoCloudRequest(
665 chromeos::http::request_type::kPatch,
666 GetServiceURL("commands/" + command_id),
667 &command_patch,
668 base::Bind(&IgnoreCloudResult), base::Bind(&IgnoreCloudError));
669}
670
Anton Muhinc635c592014-10-28 21:48:08 +0400671void DeviceRegistrationInfo::UpdateDeviceResource(base::Closure callback) {
Anton Muhind8d32162014-10-02 20:37:00 +0400672 std::unique_ptr<base::DictionaryValue> device_resource =
Anton Muhinc635c592014-10-28 21:48:08 +0400673 BuildDeviceResource(nullptr);
Anton Muhind8d32162014-10-02 20:37:00 +0400674 if (!device_resource)
675 return;
676
Anton Muhinc635c592014-10-28 21:48:08 +0400677 DoCloudRequest(
678 chromeos::http::request_type::kPut,
679 GetDeviceURL(),
680 device_resource.get(),
681 base::Bind([callback](const base::DictionaryValue&){
682 base::MessageLoop::current()->PostTask(FROM_HERE, callback);
683 }),
684 // TODO(antonm): Failure to update device resource probably deserves
685 // some additional actions.
Anton Muhin5191e812014-10-30 17:49:48 +0400686 base::Bind(&IgnoreCloudError));
Anton Muhinc635c592014-10-28 21:48:08 +0400687}
Anton Muhina34f0d92014-10-03 21:09:40 +0400688
Anton Muhinc635c592014-10-28 21:48:08 +0400689void DeviceRegistrationInfo::FetchCommands(
690 base::Callback<void(const base::ListValue&)> callback) {
691 DoCloudRequest(
692 chromeos::http::request_type::kGet,
693 GetServiceURL("commands/queue", {{"deviceId", device_id_}}),
694 nullptr,
695 base::Bind([callback](const base::DictionaryValue& json) {
696 const base::ListValue* commands{nullptr};
697 if (!json.GetList("commands", &commands)) {
698 VLOG(1) << "No commands in the response.";
699 }
700 const base::ListValue empty;
701 callback.Run(commands ? *commands : empty);
702 }),
Anton Muhin5191e812014-10-30 17:49:48 +0400703 base::Bind(&IgnoreCloudError));
Anton Muhinc635c592014-10-28 21:48:08 +0400704}
Anton Muhina34f0d92014-10-03 21:09:40 +0400705
Anton Muhinc635c592014-10-28 21:48:08 +0400706void DeviceRegistrationInfo::AbortLimboCommands(
707 base::Closure callback, const base::ListValue& commands) {
708 const size_t size{commands.GetSize()};
709 for (size_t i = 0; i < size; ++i) {
710 const base::DictionaryValue* command{nullptr};
711 if (!commands.GetDictionary(i, &command)) {
712 LOG(WARNING) << "No command resource at " << i;
713 continue;
Anton Muhina34f0d92014-10-03 21:09:40 +0400714 }
Anton Muhinc635c592014-10-28 21:48:08 +0400715 std::string command_state;
716 if (!command->GetString("state", &command_state)) {
717 LOG(WARNING) << "Command with no state at " << i;
718 continue;
719 }
720 if (command_state != "error" &&
721 command_state != "inProgress" &&
722 command_state != "paused") {
723 // It's not a limbo command, ignore.
724 continue;
725 }
726 std::string command_id;
727 if (!command->GetString("id", &command_id)) {
728 LOG(WARNING) << "Command with no ID at " << i;
729 continue;
730 }
Anton Muhin6d2569e2014-10-30 12:32:27 +0400731
732 std::unique_ptr<base::DictionaryValue> command_copy{command->DeepCopy()};
733 command_copy->SetString("state", "aborted");
734 DoCloudRequest(
735 chromeos::http::request_type::kPut,
736 GetServiceURL("commands/" + command_id),
737 command_copy.get(),
Anton Muhin5191e812014-10-30 17:49:48 +0400738 base::Bind(&IgnoreCloudResult), base::Bind(&IgnoreCloudError));
Anton Muhinc635c592014-10-28 21:48:08 +0400739 }
Anton Muhin0ae1fba2014-10-22 19:30:40 +0400740
Anton Muhinc635c592014-10-28 21:48:08 +0400741 base::MessageLoop::current()->PostTask(FROM_HERE, callback);
742}
743
744void DeviceRegistrationInfo::PeriodicallyPollCommands() {
Anton Muhind07e2062014-10-27 10:53:29 +0400745 VLOG(1) << "Poll commands";
746 PostRepeatingTask(
747 FROM_HERE,
748 base::Bind(
749 &DeviceRegistrationInfo::FetchCommands,
750 base::Unretained(this),
751 base::Bind(&DeviceRegistrationInfo::PublishCommands,
752 base::Unretained(this))),
753 base::TimeDelta::FromSeconds(7));
Anton Muhinb8315622014-11-20 03:17:05 +0400754 // TODO(antonm): Use better trigger: when StateManager registers new updates,
755 // it should call closure which will post a task, probabluy with some
756 // throtlling, to publish state updates.
757 PostRepeatingTask(
758 FROM_HERE,
759 base::Bind(&DeviceRegistrationInfo::PublishStateUpdates,
760 base::Unretained(this)),
761 base::TimeDelta::FromSeconds(7));
Anton Muhind07e2062014-10-27 10:53:29 +0400762}
763
764void DeviceRegistrationInfo::PublishCommands(const base::ListValue& commands) {
765 const CommandDictionary& command_dictionary =
766 command_manager_->GetCommandDictionary();
767
768 const size_t size{commands.GetSize()};
769 for (size_t i = 0; i < size; ++i) {
770 const base::DictionaryValue* command{nullptr};
771 if (!commands.GetDictionary(i, &command)) {
772 LOG(WARNING) << "No command resource at " << i;
773 continue;
774 }
775
776 std::unique_ptr<CommandInstance> command_instance =
777 CommandInstance::FromJson(command, command_dictionary, nullptr);
778 if (!command_instance) {
779 LOG(WARNING) << "Failed to parse a command";
780 continue;
781 }
782
Anton Muhin5191e812014-10-30 17:49:48 +0400783 // TODO(antonm): Properly process cancellation of commands.
Anton Muhin59755522014-11-05 21:30:12 +0400784 if (!command_manager_->FindCommand(command_instance->GetID())) {
785 std::unique_ptr<CommandProxyInterface> cloud_proxy{
786 new CloudCommandProxy(command_instance.get(), this)};
787 command_instance->AddProxy(std::move(cloud_proxy));
Anton Muhin5191e812014-10-30 17:49:48 +0400788 command_manager_->AddCommand(std::move(command_instance));
Anton Muhin59755522014-11-05 21:30:12 +0400789 }
Anton Muhind07e2062014-10-27 10:53:29 +0400790 }
Anton Muhind8d32162014-10-02 20:37:00 +0400791}
792
Anton Muhinb8315622014-11-20 03:17:05 +0400793void DeviceRegistrationInfo::PublishStateUpdates() {
794 VLOG(1) << "PublishStateUpdates";
795 const std::vector<StateChange> state_changes{
796 state_manager_->GetAndClearRecordedStateChanges()};
797 if (state_changes.empty())
798 return;
799
800 std::unique_ptr<base::ListValue> patches{new base::ListValue};
801 for (const auto& state_change : state_changes) {
802 std::unique_ptr<base::DictionaryValue> patch{new base::DictionaryValue};
Anton Muhin76933fd2014-11-21 21:25:18 +0400803 patch->SetString("timeMs",
804 std::to_string(state_change.timestamp.ToJavaTime()));
Anton Muhinb8315622014-11-20 03:17:05 +0400805
806 std::unique_ptr<base::DictionaryValue> changes{new base::DictionaryValue};
807 for (const auto& pair : state_change.changed_properties) {
808 auto value = pair.second->ToJson(nullptr);
809 if (!value) {
810 return;
811 }
812 changes->SetWithoutPathExpansion(pair.first, value.release());
813 }
814 patch->Set("patch", changes.release());
815
816 patches->Append(patch.release());
817 }
818
819 base::DictionaryValue body;
Anton Muhin76933fd2014-11-21 21:25:18 +0400820 body.SetString("requestTimeMs",
821 std::to_string(base::Time::Now().ToJavaTime()));
Anton Muhinb8315622014-11-20 03:17:05 +0400822 body.Set("patches", patches.release());
823
824 DoCloudRequest(
825 chromeos::http::request_type::kPost,
826 GetDeviceURL("patchState"),
827 &body,
828 base::Bind(&IgnoreCloudResult), base::Bind(&IgnoreCloudError));
829}
830
Anton Muhin332df192014-11-22 05:59:14 +0400831void DeviceRegistrationInfo::GetParamValue(
832 const std::map<std::string, std::string>& params,
833 const std::string& param_name,
834 std::string* param_value) {
835 auto p = params.find(param_name);
836 if (p != params.end()) {
837 *param_value = p->second;
838 return;
839 }
840
841 bool present = config_store_->GetString(param_name, param_value);
842 CHECK(present) << "No default for parameter " << param_name;
843}
844
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700845} // namespace buffet