blob: d4cee7671eecdd86711cdaab807d4392ee212c2b [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>
Alex Vakulenko3aeea1c2014-08-20 16:33:12 -070018#include <chromeos/mime_utils.h>
Alex Vakulenkoa8b95bc2014-08-27 11:00:57 -070019#include <chromeos/strings/string_utils.h>
Alex Vakulenkobd5b5442014-08-20 16:16:34 -070020#include <chromeos/url_utils.h>
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070021
Alex Vakulenko45109442014-07-29 11:07:10 -070022#include "buffet/commands/command_definition.h"
23#include "buffet/commands/command_manager.h"
Alex Vakulenko8e34d392014-04-29 11:02:56 -070024#include "buffet/device_registration_storage_keys.h"
Alex Vakulenko07216fe2014-09-19 15:31:09 -070025#include "buffet/states/state_manager.h"
Alex Vakulenko5841c302014-07-23 10:49:49 -070026#include "buffet/storage_impls.h"
Alex Vakulenkob04936f2014-09-19 14:53:58 -070027#include "buffet/utils.h"
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070028
Alex Vakulenkob3aac252014-05-07 17:35:24 -070029const char buffet::kErrorDomainOAuth2[] = "oauth2";
30const char buffet::kErrorDomainGCD[] = "gcd";
31const char buffet::kErrorDomainGCDServer[] = "gcd_server";
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070032
Alex Vakulenko8e34d392014-04-29 11:02:56 -070033namespace buffet {
34namespace storage_keys {
35
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070036// Persistent keys
37const char kClientId[] = "client_id";
38const char kClientSecret[] = "client_secret";
39const char kApiKey[] = "api_key";
40const char kRefreshToken[] = "refresh_token";
41const char kDeviceId[] = "device_id";
42const char kOAuthURL[] = "oauth_url";
43const char kServiceURL[] = "service_url";
44const char kRobotAccount[] = "robot_account";
45// Transient keys
46const char kDeviceKind[] = "device_kind";
Anton Muhin9e19cdd2014-10-20 13:55:04 +040047const char kName[] = "name";
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070048const char kDisplayName[] = "display_name";
49
Alex Vakulenko8e34d392014-04-29 11:02:56 -070050} // namespace storage_keys
51} // namespace buffet
52
53namespace {
54
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070055const base::FilePath::CharType kDeviceInfoFilePath[] =
56 FILE_PATH_LITERAL("/var/lib/buffet/device_reg_info");
57
58bool GetParamValue(
Alex Vakulenkoa9044342014-08-23 19:31:27 -070059 const std::map<std::string, std::string>& params,
Alex Vakulenkoa3062c52014-04-21 17:05:51 -070060 const std::string& param_name,
61 std::string* param_value) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070062 auto p = params.find(param_name);
63 if (p == params.end())
64 return false;
65
Alex Vakulenkoa9044342014-08-23 19:31:27 -070066 *param_value = p->second;
67 return true;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070068}
69
70std::pair<std::string, std::string> BuildAuthHeader(
71 const std::string& access_token_type,
72 const std::string& access_token) {
Alex Vakulenkoaf23b322014-05-08 16:25:45 -070073 std::string authorization =
Alex Vakulenkob8fc1df2014-08-20 15:38:07 -070074 chromeos::string_utils::Join(' ', access_token_type, access_token);
Alex Vakulenkocca20932014-08-20 17:35:12 -070075 return {chromeos::http::request_header::kAuthorization, authorization};
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070076}
77
78std::unique_ptr<base::DictionaryValue> ParseOAuthResponse(
Alex Vakulenkocca20932014-08-20 17:35:12 -070079 const chromeos::http::Response* response, chromeos::ErrorPtr* error) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070080 int code = 0;
Alex Vakulenkocca20932014-08-20 17:35:12 -070081 auto resp = chromeos::http::ParseJsonResponse(response, &code, error);
82 if (resp && code >= chromeos::http::status_code::BadRequest) {
Alex Vakulenkob3aac252014-05-07 17:35:24 -070083 if (error) {
84 std::string error_code, error_message;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070085 if (resp->GetString("error", &error_code) &&
Alex Vakulenkob3aac252014-05-07 17:35:24 -070086 resp->GetString("error_description", &error_message)) {
Alex Vakulenko5f472062014-08-14 17:54:04 -070087 chromeos::Error::AddTo(error, buffet::kErrorDomainOAuth2, error_code,
88 error_message);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070089 } else {
Alex Vakulenko5f472062014-08-14 17:54:04 -070090 chromeos::Error::AddTo(error, buffet::kErrorDomainOAuth2,
91 "unexpected_response", "Unexpected OAuth error");
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070092 }
93 }
94 return std::unique_ptr<base::DictionaryValue>();
95 }
96 return resp;
97}
98
Alex Vakulenko5f472062014-08-14 17:54:04 -070099inline void SetUnexpectedError(chromeos::ErrorPtr* error) {
100 chromeos::Error::AddTo(error, buffet::kErrorDomainGCD, "unexpected_response",
101 "Unexpected GCD error");
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700102}
103
Alex Vakulenko5f472062014-08-14 17:54:04 -0700104void ParseGCDError(const base::DictionaryValue* json,
105 chromeos::ErrorPtr* error) {
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700106 if (!error)
107 return;
108
109 const base::Value* list_value = nullptr;
110 const base::ListValue* error_list = nullptr;
111 if (!json->Get("error.errors", &list_value) ||
112 !list_value->GetAsList(&error_list)) {
113 SetUnexpectedError(error);
114 return;
115 }
116
117 for (size_t i = 0; i < error_list->GetSize(); i++) {
118 const base::Value* error_value = nullptr;
119 const base::DictionaryValue* error_object = nullptr;
120 if (!error_list->Get(i, &error_value) ||
121 !error_value->GetAsDictionary(&error_object)) {
122 SetUnexpectedError(error);
123 continue;
124 }
125 std::string error_code, error_message;
126 if (error_object->GetString("reason", &error_code) &&
127 error_object->GetString("message", &error_message)) {
Alex Vakulenko5f472062014-08-14 17:54:04 -0700128 chromeos::Error::AddTo(error, buffet::kErrorDomainGCDServer,
129 error_code, error_message);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700130 } else {
131 SetUnexpectedError(error);
132 }
133 }
134}
135
Alex Vakulenkobda220a2014-04-18 15:25:44 -0700136std::string BuildURL(const std::string& url,
137 const std::vector<std::string>& subpaths,
Alex Vakulenkoe4879a22014-08-20 15:47:36 -0700138 const chromeos::data_encoding::WebParamList& params) {
Alex Vakulenkobd5b5442014-08-20 16:16:34 -0700139 std::string result = chromeos::url::CombineMultiple(url, subpaths);
140 return chromeos::url::AppendQueryParams(result, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700141}
142
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700143} // anonymous namespace
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700144
145namespace buffet {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700146
Alex Vakulenko1f30a622014-07-23 11:13:15 -0700147DeviceRegistrationInfo::DeviceRegistrationInfo(
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700148 const std::shared_ptr<CommandManager>& command_manager,
149 const std::shared_ptr<const StateManager>& state_manager)
150 : DeviceRegistrationInfo(
151 command_manager,
152 state_manager,
153 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,
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700161 const std::shared_ptr<const StateManager>& state_manager,
Alex Vakulenkocca20932014-08-20 17:35:12 -0700162 const std::shared_ptr<chromeos::http::Transport>& transport,
Alex Vakulenko1f30a622014-07-23 11:13:15 -0700163 const std::shared_ptr<StorageInterface>& storage)
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700164 : transport_{transport},
165 storage_{storage},
166 command_manager_{command_manager},
167 state_manager_{state_manager} {
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700168}
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700169
170std::pair<std::string, std::string>
171 DeviceRegistrationInfo::GetAuthorizationHeader() const {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700172 return BuildAuthHeader("Bearer", access_token_);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700173}
174
175std::string DeviceRegistrationInfo::GetServiceURL(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700176 const std::string& subpath,
Alex Vakulenkoe4879a22014-08-20 15:47:36 -0700177 const chromeos::data_encoding::WebParamList& params) const {
Alex Vakulenkobda220a2014-04-18 15:25:44 -0700178 return BuildURL(service_url_, {subpath}, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700179}
180
181std::string DeviceRegistrationInfo::GetDeviceURL(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700182 const std::string& subpath,
Alex Vakulenkoe4879a22014-08-20 15:47:36 -0700183 const chromeos::data_encoding::WebParamList& params) const {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700184 CHECK(!device_id_.empty()) << "Must have a valid device ID";
Alex Vakulenkobda220a2014-04-18 15:25:44 -0700185 return BuildURL(service_url_, {"devices", device_id_, subpath}, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700186}
187
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700188std::string DeviceRegistrationInfo::GetOAuthURL(
189 const std::string& subpath,
Alex Vakulenkoe4879a22014-08-20 15:47:36 -0700190 const chromeos::data_encoding::WebParamList& params) const {
Alex Vakulenkobda220a2014-04-18 15:25:44 -0700191 return BuildURL(oauth_url_, {subpath}, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700192}
193
Alex Vakulenko5f472062014-08-14 17:54:04 -0700194std::string DeviceRegistrationInfo::GetDeviceId(chromeos::ErrorPtr* error) {
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700195 return CheckRegistration(error) ? device_id_ : std::string();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700196}
197
198bool DeviceRegistrationInfo::Load() {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700199 auto value = storage_->Load();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700200 const base::DictionaryValue* dict = nullptr;
201 if (!value || !value->GetAsDictionary(&dict))
202 return false;
203
204 // Get the values into temp variables first to make sure we can get
205 // all the data correctly before changing the state of this object.
206 std::string client_id;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700207 if (!dict->GetString(storage_keys::kClientId, &client_id))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700208 return false;
209 std::string client_secret;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700210 if (!dict->GetString(storage_keys::kClientSecret, &client_secret))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700211 return false;
212 std::string api_key;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700213 if (!dict->GetString(storage_keys::kApiKey, &api_key))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700214 return false;
215 std::string refresh_token;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700216 if (!dict->GetString(storage_keys::kRefreshToken, &refresh_token))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700217 return false;
218 std::string device_id;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700219 if (!dict->GetString(storage_keys::kDeviceId, &device_id))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700220 return false;
221 std::string oauth_url;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700222 if (!dict->GetString(storage_keys::kOAuthURL, &oauth_url))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700223 return false;
224 std::string service_url;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700225 if (!dict->GetString(storage_keys::kServiceURL, &service_url))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700226 return false;
227 std::string device_robot_account;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700228 if (!dict->GetString(storage_keys::kRobotAccount, &device_robot_account))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700229 return false;
230
231 client_id_ = client_id;
232 client_secret_ = client_secret;
233 api_key_ = api_key;
234 refresh_token_ = refresh_token;
235 device_id_ = device_id;
236 oauth_url_ = oauth_url;
237 service_url_ = service_url;
238 device_robot_account_ = device_robot_account;
239 return true;
240}
241
242bool DeviceRegistrationInfo::Save() const {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700243 base::DictionaryValue dict;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700244 dict.SetString(storage_keys::kClientId, client_id_);
245 dict.SetString(storage_keys::kClientSecret, client_secret_);
246 dict.SetString(storage_keys::kApiKey, api_key_);
247 dict.SetString(storage_keys::kRefreshToken, refresh_token_);
248 dict.SetString(storage_keys::kDeviceId, device_id_);
249 dict.SetString(storage_keys::kOAuthURL, oauth_url_);
250 dict.SetString(storage_keys::kServiceURL, service_url_);
251 dict.SetString(storage_keys::kRobotAccount, device_robot_account_);
252 return storage_->Save(&dict);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700253}
254
Alex Vakulenko5f472062014-08-14 17:54:04 -0700255bool DeviceRegistrationInfo::CheckRegistration(chromeos::ErrorPtr* error) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700256 LOG(INFO) << "Checking device registration record.";
257 if (refresh_token_.empty() ||
258 device_id_.empty() ||
259 device_robot_account_.empty()) {
260 LOG(INFO) << "No valid device registration record found.";
Alex Vakulenko5f472062014-08-14 17:54:04 -0700261 chromeos::Error::AddTo(error, kErrorDomainGCD, "device_not_registered",
262 "No valid device registration record found");
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700263 return false;
264 }
265
266 LOG(INFO) << "Device registration record found.";
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700267 return ValidateAndRefreshAccessToken(error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700268}
269
Alex Vakulenko5f472062014-08-14 17:54:04 -0700270bool DeviceRegistrationInfo::ValidateAndRefreshAccessToken(
271 chromeos::ErrorPtr* error) {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700272 LOG(INFO) << "Checking access token expiration.";
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700273 if (!access_token_.empty() &&
274 !access_token_expiration_.is_null() &&
275 access_token_expiration_ > base::Time::Now()) {
276 LOG(INFO) << "Access token is still valid.";
277 return true;
278 }
279
Alex Vakulenkocca20932014-08-20 17:35:12 -0700280 auto response = chromeos::http::PostFormData(GetOAuthURL("token"), {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700281 {"refresh_token", refresh_token_},
282 {"client_id", client_id_},
283 {"client_secret", client_secret_},
284 {"grant_type", "refresh_token"},
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700285 }, transport_, error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700286 if (!response)
287 return false;
288
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700289 auto json = ParseOAuthResponse(response.get(), error);
290 if (!json)
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700291 return false;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700292
293 int expires_in = 0;
294 if (!json->GetString("access_token", &access_token_) ||
295 !json->GetInteger("expires_in", &expires_in) ||
296 access_token_.empty() ||
297 expires_in <= 0) {
298 LOG(ERROR) << "Access token unavailable.";
Alex Vakulenko5f472062014-08-14 17:54:04 -0700299 chromeos::Error::AddTo(error, kErrorDomainOAuth2,
300 "unexpected_server_response",
301 "Access token unavailable");
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700302 return false;
303 }
304
305 access_token_expiration_ = base::Time::Now() +
306 base::TimeDelta::FromSeconds(expires_in);
307
308 LOG(INFO) << "Access token is refreshed for additional " << expires_in
309 << " seconds.";
310 return true;
311}
312
Anton Muhind8d32162014-10-02 20:37:00 +0400313std::unique_ptr<base::DictionaryValue>
314DeviceRegistrationInfo::BuildDeviceResource(chromeos::ErrorPtr* error) {
315 std::unique_ptr<base::DictionaryValue> commands =
316 command_manager_->GetCommandDictionary().GetCommandsAsJson(true, error);
317 if (!commands)
318 return nullptr;
319
320 std::unique_ptr<base::DictionaryValue> state =
321 state_manager_->GetStateValuesAsJson(error);
322 if (!state)
323 return nullptr;
324
325 std::unique_ptr<base::DictionaryValue> resource{new base::DictionaryValue};
326 if (!device_id_.empty())
327 resource->SetString("id", device_id_);
328 resource->SetString("deviceKind", device_kind_);
Anton Muhin9e19cdd2014-10-20 13:55:04 +0400329 resource->SetString("name", name_);
Anton Muhind8d32162014-10-02 20:37:00 +0400330 if (!display_name_.empty())
331 resource->SetString("displayName", display_name_);
332 resource->SetString("channel.supportedType", "xmpp");
333 resource->Set("commandDefs", commands.release());
334 resource->Set("state", state.release());
335
336 return resource;
337}
338
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700339std::unique_ptr<base::Value> DeviceRegistrationInfo::GetDeviceInfo(
Alex Vakulenko5f472062014-08-14 17:54:04 -0700340 chromeos::ErrorPtr* error) {
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700341 if (!CheckRegistration(error))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700342 return std::unique_ptr<base::Value>();
343
Anton Muhinac661ab2014-10-03 20:29:48 +0400344 // TODO(antonm): Switch to DoCloudRequest later.
Alex Vakulenkocca20932014-08-20 17:35:12 -0700345 auto response = chromeos::http::Get(
346 GetDeviceURL(), {GetAuthorizationHeader()}, transport_, error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700347 int status_code = 0;
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700348 std::unique_ptr<base::DictionaryValue> json =
Alex Vakulenkocca20932014-08-20 17:35:12 -0700349 chromeos::http::ParseJsonResponse(response.get(), &status_code, error);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700350 if (json) {
Alex Vakulenkocca20932014-08-20 17:35:12 -0700351 if (status_code >= chromeos::http::status_code::BadRequest) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700352 LOG(WARNING) << "Failed to retrieve the device info. Response code = "
353 << status_code;
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700354 ParseGCDError(json.get(), error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700355 return std::unique_ptr<base::Value>();
356 }
357 }
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700358 return std::unique_ptr<base::Value>(json.release());
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700359}
360
361bool CheckParam(const std::string& param_name,
362 const std::string& param_value,
Alex Vakulenko5f472062014-08-14 17:54:04 -0700363 chromeos::ErrorPtr* error) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700364 if (!param_value.empty())
365 return true;
366
Alex Vakulenko5f472062014-08-14 17:54:04 -0700367 chromeos::Error::AddToPrintf(error, kErrorDomainBuffet, "missing_parameter",
368 "Parameter %s not specified",
369 param_name.c_str());
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700370 return false;
371}
372
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400373std::string DeviceRegistrationInfo::RegisterDevice(
Alex Vakulenkoa9044342014-08-23 19:31:27 -0700374 const std::map<std::string, std::string>& params,
Alex Vakulenko5f472062014-08-14 17:54:04 -0700375 chromeos::ErrorPtr* error) {
Anton Muhina4803142014-09-24 19:30:45 +0400376 GetParamValue(params, "ticket_id", &ticket_id_);
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700377 GetParamValue(params, storage_keys::kClientId, &client_id_);
378 GetParamValue(params, storage_keys::kClientSecret, &client_secret_);
379 GetParamValue(params, storage_keys::kApiKey, &api_key_);
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700380 GetParamValue(params, storage_keys::kDeviceKind, &device_kind_);
Anton Muhin9e19cdd2014-10-20 13:55:04 +0400381 GetParamValue(params, storage_keys::kName, &name_);
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700382 GetParamValue(params, storage_keys::kDisplayName, &display_name_);
383 GetParamValue(params, storage_keys::kOAuthURL, &oauth_url_);
384 GetParamValue(params, storage_keys::kServiceURL, &service_url_);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700385
Anton Muhind8d32162014-10-02 20:37:00 +0400386 std::unique_ptr<base::DictionaryValue> device_draft =
387 BuildDeviceResource(error);
388 if (!device_draft)
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700389 return std::string();
390
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700391 base::DictionaryValue req_json;
Anton Muhina4803142014-09-24 19:30:45 +0400392 req_json.SetString("id", ticket_id_);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700393 req_json.SetString("oauthClientId", client_id_);
Anton Muhind8d32162014-10-02 20:37:00 +0400394 req_json.Set("deviceDraft", device_draft.release());
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700395
Anton Muhina4803142014-09-24 19:30:45 +0400396 auto url = GetServiceURL("registrationTickets/" + ticket_id_,
397 {{"key", api_key_}});
Anton Muhin532ff7e2014-09-29 23:21:21 +0400398 std::unique_ptr<chromeos::http::Response> response =
399 chromeos::http::PatchJson(url, &req_json, transport_, error);
400 auto json_resp = chromeos::http::ParseJsonResponse(response.get(), nullptr,
401 error);
402 if (!json_resp)
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700403 return std::string();
Anton Muhin532ff7e2014-09-29 23:21:21 +0400404 if (!response->IsSuccessful())
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700405 return std::string();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700406
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700407 std::string auth_url = GetOAuthURL("auth", {
408 {"scope", "https://www.googleapis.com/auth/clouddevices"},
409 {"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"},
410 {"response_type", "code"},
411 {"client_id", client_id_}
412 });
413
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400414 url = GetServiceURL("registrationTickets/" + ticket_id_ +
415 "/finalize?key=" + api_key_);
416 response = chromeos::http::PostBinary(url, nullptr, 0, transport_, error);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700417 if (!response)
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400418 return std::string();
419 json_resp = chromeos::http::ParseJsonResponse(response.get(), nullptr, error);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700420 if (!json_resp)
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400421 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700422 if (!response->IsSuccessful()) {
423 ParseGCDError(json_resp.get(), error);
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400424 return std::string();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700425 }
Anton Muhin532ff7e2014-09-29 23:21:21 +0400426
427 std::string auth_code;
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700428 if (!json_resp->GetString("robotAccountEmail", &device_robot_account_) ||
429 !json_resp->GetString("robotAccountAuthorizationCode", &auth_code) ||
430 !json_resp->GetString("deviceDraft.id", &device_id_)) {
Alex Vakulenko5f472062014-08-14 17:54:04 -0700431 chromeos::Error::AddTo(error, kErrorDomainGCD, "unexpected_response",
432 "Device account missing in response");
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400433 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700434 }
435
436 // Now get access_token and refresh_token
Alex Vakulenkocca20932014-08-20 17:35:12 -0700437 response = chromeos::http::PostFormData(GetOAuthURL("token"), {
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700438 {"code", auth_code},
439 {"client_id", client_id_},
440 {"client_secret", client_secret_},
441 {"redirect_uri", "oob"},
442 {"scope", "https://www.googleapis.com/auth/clouddevices"},
443 {"grant_type", "authorization_code"}
444 }, transport_, error);
445 if (!response)
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400446 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700447
448 json_resp = ParseOAuthResponse(response.get(), error);
449 int expires_in = 0;
450 if (!json_resp ||
451 !json_resp->GetString("access_token", &access_token_) ||
452 !json_resp->GetString("refresh_token", &refresh_token_) ||
453 !json_resp->GetInteger("expires_in", &expires_in) ||
454 access_token_.empty() ||
455 refresh_token_.empty() ||
456 expires_in <= 0) {
Alex Vakulenko5f472062014-08-14 17:54:04 -0700457 chromeos::Error::AddTo(error, kErrorDomainGCD, "unexpected_response",
458 "Device access_token missing in response");
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400459 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700460 }
461
462 access_token_expiration_ = base::Time::Now() +
463 base::TimeDelta::FromSeconds(expires_in);
464
465 Save();
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400466 return device_id_;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700467}
468
Anton Muhin633eded2014-10-03 20:40:10 +0400469namespace {
470
471template <class T>
472void PostToCallback(base::Callback<void(const T&)> callback,
473 std::unique_ptr<T> value) {
474 auto cb = [callback] (T* result) {
475 callback.Run(*result);
476 };
477 base::MessageLoop::current()->PostTask(
478 FROM_HERE, base::Bind(cb, base::Owned(value.release())));
479}
480
Anton Muhin0ae1fba2014-10-22 19:30:40 +0400481void PostRepeatingTask(const tracked_objects::Location& from_here,
482 base::Closure task,
483 base::TimeDelta delay) {
484 task.Run();
485 base::MessageLoop::current()->PostDelayedTask(
486 from_here, base::Bind(&PostRepeatingTask, from_here, task, delay), delay);
487}
488
Anton Muhin633eded2014-10-03 20:40:10 +0400489// TODO(antonm): May belong to chromeos/http.
490
491void SendRequestAsync(
492 const std::string& method,
493 const std::string& url,
494 const std::string& data,
495 const std::string& mime_type,
496 const chromeos::http::HeaderList& headers,
497 std::shared_ptr<chromeos::http::Transport> transport,
498 int num_retries,
499 base::Callback<void(const chromeos::http::Response&)> callback,
500 base::Callback<void(const chromeos::Error&)> errorback) {
501 chromeos::ErrorPtr error;
502
503 auto on_retriable_failure = [&error, method, url, data, mime_type,
504 headers, transport, num_retries, callback, errorback] () {
505 if (num_retries > 0) {
506 auto c = [method, url, data, mime_type, headers, transport,
507 num_retries, callback, errorback] () {
508 SendRequestAsync(method, url,
509 data, mime_type,
510 headers,
511 transport,
512 num_retries - 1,
513 callback, errorback);
514 };
Anton Muhin0ae1fba2014-10-22 19:30:40 +0400515 base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(c));
Anton Muhin633eded2014-10-03 20:40:10 +0400516 } else {
517 PostToCallback(errorback, std::move(error));
518 }
519 };
520
521 chromeos::http::Request request(url, method.c_str(), transport);
522 request.AddHeaders(headers);
523 if (!data.empty()) {
524 request.SetContentType(mime_type.c_str());
525 if (!request.AddRequestBody(data.c_str(), data.size(), &error)) {
526 on_retriable_failure();
527 return;
528 }
529 }
530
531 std::unique_ptr<chromeos::http::Response> response{
532 request.GetResponse(&error)};
533 if (!response) {
534 on_retriable_failure();
535 return;
536 }
537
538 int status_code{response->GetStatusCode()};
539 if (status_code >= chromeos::http::status_code::Continue &&
540 status_code < chromeos::http::status_code::BadRequest) {
541 PostToCallback(callback, std::move(response));
542 return;
543 }
544
545 // TODO(antonm): Should add some useful information to error.
546 LOG(WARNING) << "Request failed. Response code = " << status_code;
547
548 if (status_code >= 500 && status_code < 600) {
549 // Request was valid, but server failed, retry.
550 // TODO(antonm): Implement exponential backoff.
551 // TODO(antonm): Reconsider status codes, maybe only some require
552 // retry.
553 // TODO(antonm): Support Retry-After header.
554 on_retriable_failure();
555 } else {
Anton Muhin233d2ee2014-10-22 15:16:24 +0400556 chromeos::Error::AddTo(&error, chromeos::errors::http::kDomain,
557 std::to_string(status_code),
558 response->GetStatusText());
Anton Muhin633eded2014-10-03 20:40:10 +0400559 PostToCallback(errorback, std::move(error));
560 }
561}
562
563} // namespace
564
Anton Muhinac661ab2014-10-03 20:29:48 +0400565void DeviceRegistrationInfo::DoCloudRequest(
Anton Muhin233d2ee2014-10-22 15:16:24 +0400566 const std::string& method,
Anton Muhinac661ab2014-10-03 20:29:48 +0400567 const std::string& url,
568 const base::DictionaryValue* body,
569 CloudRequestCallback callback,
570 CloudRequestErroback errorback) {
Anton Muhinac661ab2014-10-03 20:29:48 +0400571 // TODO(antonm): Add reauthorisation on access token expiration (do not
572 // forget about 5xx when fetching new access token).
573 // TODO(antonm): Add support for device removal.
574
Anton Muhinac661ab2014-10-03 20:29:48 +0400575 std::string data;
576 if (body)
577 base::JSONWriter::Write(body, &data);
578
579 const std::string mime_type{chromeos::mime::AppendParameter(
580 chromeos::mime::application::kJson,
581 chromeos::mime::parameters::kCharset,
582 "utf-8")};
583
Anton Muhin633eded2014-10-03 20:40:10 +0400584 auto request_cb = [callback, errorback] (
585 const chromeos::http::Response& response) {
586 chromeos::ErrorPtr error;
Anton Muhinac661ab2014-10-03 20:29:48 +0400587
Anton Muhin633eded2014-10-03 20:40:10 +0400588 std::unique_ptr<base::DictionaryValue> json_resp{
589 chromeos::http::ParseJsonResponse(&response, nullptr, &error)};
590 if (!json_resp) {
591 PostToCallback(errorback, std::move(error));
592 return;
593 }
Anton Muhinac661ab2014-10-03 20:29:48 +0400594
Anton Muhin633eded2014-10-03 20:40:10 +0400595 PostToCallback(callback, std::move(json_resp));
Anton Muhinac661ab2014-10-03 20:29:48 +0400596 };
Anton Muhin633eded2014-10-03 20:40:10 +0400597
Anton Muhin233d2ee2014-10-22 15:16:24 +0400598 auto transport = transport_;
599 auto errorback_with_reauthorization = base::Bind(
600 [method, url, data, mime_type, transport, request_cb, errorback]
601 (DeviceRegistrationInfo* self, const chromeos::Error& error) {
602 if (error.HasError(chromeos::errors::http::kDomain,
603 std::to_string(chromeos::http::status_code::Denied))) {
604 chromeos::ErrorPtr reauthorization_error;
605 if (!self->ValidateAndRefreshAccessToken(&reauthorization_error)) {
606 // TODO(antonm): Check if the device has been actually removed.
607 errorback.Run(*reauthorization_error.get());
608 return;
609 }
610 SendRequestAsync(method, url,
611 data, mime_type,
612 {self->GetAuthorizationHeader()},
613 transport,
614 7,
615 base::Bind(request_cb), errorback);
616 } else {
617 errorback.Run(error);
618 }
619 }, base::Unretained(this));
620
Anton Muhin633eded2014-10-03 20:40:10 +0400621 SendRequestAsync(method, url,
622 data, mime_type,
623 {GetAuthorizationHeader()},
Anton Muhin233d2ee2014-10-22 15:16:24 +0400624 transport,
Anton Muhin633eded2014-10-03 20:40:10 +0400625 7,
Anton Muhin233d2ee2014-10-22 15:16:24 +0400626 base::Bind(request_cb), errorback_with_reauthorization);
Anton Muhinac661ab2014-10-03 20:29:48 +0400627}
628
Anton Muhind8d32162014-10-02 20:37:00 +0400629void DeviceRegistrationInfo::StartDevice(chromeos::ErrorPtr* error) {
630 if (!CheckRegistration(error))
631 return;
632
Anton Muhinc635c592014-10-28 21:48:08 +0400633 base::Bind(
634 &DeviceRegistrationInfo::UpdateDeviceResource,
635 base::Unretained(this),
636 base::Bind(
637 &DeviceRegistrationInfo::FetchCommands,
638 base::Unretained(this),
639 base::Bind(
640 &DeviceRegistrationInfo::AbortLimboCommands,
641 base::Unretained(this),
642 base::Bind(
643 &DeviceRegistrationInfo::PeriodicallyPollCommands,
644 base::Unretained(this))))).Run();
645}
646
647void DeviceRegistrationInfo::UpdateDeviceResource(base::Closure callback) {
Anton Muhind8d32162014-10-02 20:37:00 +0400648 std::unique_ptr<base::DictionaryValue> device_resource =
Anton Muhinc635c592014-10-28 21:48:08 +0400649 BuildDeviceResource(nullptr);
Anton Muhind8d32162014-10-02 20:37:00 +0400650 if (!device_resource)
651 return;
652
Anton Muhinc635c592014-10-28 21:48:08 +0400653 DoCloudRequest(
654 chromeos::http::request_type::kPut,
655 GetDeviceURL(),
656 device_resource.get(),
657 base::Bind([callback](const base::DictionaryValue&){
658 base::MessageLoop::current()->PostTask(FROM_HERE, callback);
659 }),
660 // TODO(antonm): Failure to update device resource probably deserves
661 // some additional actions.
662 base::Bind([](const chromeos::Error&){}));
663}
Anton Muhina34f0d92014-10-03 21:09:40 +0400664
Anton Muhinc635c592014-10-28 21:48:08 +0400665void DeviceRegistrationInfo::FetchCommands(
666 base::Callback<void(const base::ListValue&)> callback) {
667 DoCloudRequest(
668 chromeos::http::request_type::kGet,
669 GetServiceURL("commands/queue", {{"deviceId", device_id_}}),
670 nullptr,
671 base::Bind([callback](const base::DictionaryValue& json) {
672 const base::ListValue* commands{nullptr};
673 if (!json.GetList("commands", &commands)) {
674 VLOG(1) << "No commands in the response.";
675 }
676 const base::ListValue empty;
677 callback.Run(commands ? *commands : empty);
678 }),
679 base::Bind([](const chromeos::Error&){}));
680}
Anton Muhina34f0d92014-10-03 21:09:40 +0400681
Anton Muhinc635c592014-10-28 21:48:08 +0400682void DeviceRegistrationInfo::AbortLimboCommands(
683 base::Closure callback, const base::ListValue& commands) {
684 const size_t size{commands.GetSize()};
685 for (size_t i = 0; i < size; ++i) {
686 const base::DictionaryValue* command{nullptr};
687 if (!commands.GetDictionary(i, &command)) {
688 LOG(WARNING) << "No command resource at " << i;
689 continue;
Anton Muhina34f0d92014-10-03 21:09:40 +0400690 }
Anton Muhinc635c592014-10-28 21:48:08 +0400691 std::string command_state;
692 if (!command->GetString("state", &command_state)) {
693 LOG(WARNING) << "Command with no state at " << i;
694 continue;
695 }
696 if (command_state != "error" &&
697 command_state != "inProgress" &&
698 command_state != "paused") {
699 // It's not a limbo command, ignore.
700 continue;
701 }
702 std::string command_id;
703 if (!command->GetString("id", &command_id)) {
704 LOG(WARNING) << "Command with no ID at " << i;
705 continue;
706 }
707 // TODO(antonm): Really abort the command.
708 }
Anton Muhin0ae1fba2014-10-22 19:30:40 +0400709
Anton Muhinc635c592014-10-28 21:48:08 +0400710 base::MessageLoop::current()->PostTask(FROM_HERE, callback);
711}
712
713void DeviceRegistrationInfo::PeriodicallyPollCommands() {
Anton Muhind07e2062014-10-27 10:53:29 +0400714 VLOG(1) << "Poll commands";
715 PostRepeatingTask(
716 FROM_HERE,
717 base::Bind(
718 &DeviceRegistrationInfo::FetchCommands,
719 base::Unretained(this),
720 base::Bind(&DeviceRegistrationInfo::PublishCommands,
721 base::Unretained(this))),
722 base::TimeDelta::FromSeconds(7));
723}
724
725void DeviceRegistrationInfo::PublishCommands(const base::ListValue& commands) {
726 const CommandDictionary& command_dictionary =
727 command_manager_->GetCommandDictionary();
728
729 const size_t size{commands.GetSize()};
730 for (size_t i = 0; i < size; ++i) {
731 const base::DictionaryValue* command{nullptr};
732 if (!commands.GetDictionary(i, &command)) {
733 LOG(WARNING) << "No command resource at " << i;
734 continue;
735 }
736
737 std::unique_ptr<CommandInstance> command_instance =
738 CommandInstance::FromJson(command, command_dictionary, nullptr);
739 if (!command_instance) {
740 LOG(WARNING) << "Failed to parse a command";
741 continue;
742 }
743
744 // TODO(antonm): Double check if there is a chance to publish
745 // the same command twice if it doesn't change its status.
746 command_manager_->AddCommand(std::move(command_instance));
747 }
Anton Muhind8d32162014-10-02 20:37:00 +0400748}
749
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700750} // namespace buffet