blob: 0ef58d7fb10c8bcbecb1d3dfbd9cafa36ba5e0ca [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 Vakulenko9ea5a322015-04-17 15:35:34 -07008#include <set>
Alex Vakulenkob3aac252014-05-07 17:35:24 -07009#include <utility>
10#include <vector>
Christopher Wiley006e94e2014-05-02 13:44:48 -070011
Christopher Wileycd419662015-02-06 17:51:43 -080012#include <base/bind.h>
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070013#include <base/json/json_writer.h>
Anton Muhinac661ab2014-10-03 20:29:48 +040014#include <base/message_loop/message_loop.h>
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070015#include <base/values.h>
Anton Muhinac661ab2014-10-03 20:29:48 +040016#include <chromeos/bind_lambda.h>
Alex Vakulenkoe4879a22014-08-20 15:47:36 -070017#include <chromeos/data_encoding.h>
Anton Muhin233d2ee2014-10-22 15:16:24 +040018#include <chromeos/errors/error_codes.h>
Alex Vakulenkoa8b95bc2014-08-27 11:00:57 -070019#include <chromeos/http/http_utils.h>
Anton Muhin332df192014-11-22 05:59:14 +040020#include <chromeos/key_value_store.h>
Alex Vakulenko3aeea1c2014-08-20 16:33:12 -070021#include <chromeos/mime_utils.h>
Alex Vakulenkoa8b95bc2014-08-27 11:00:57 -070022#include <chromeos/strings/string_utils.h>
Alex Vakulenkobd5b5442014-08-20 16:16:34 -070023#include <chromeos/url_utils.h>
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070024
Anton Muhin59755522014-11-05 21:30:12 +040025#include "buffet/commands/cloud_command_proxy.h"
Alex Vakulenko45109442014-07-29 11:07:10 -070026#include "buffet/commands/command_definition.h"
27#include "buffet/commands/command_manager.h"
Alex Vakulenkof784e212015-04-20 12:33:52 -070028#include "buffet/commands/schema_constants.h"
Alex Vakulenko8e34d392014-04-29 11:02:56 -070029#include "buffet/device_registration_storage_keys.h"
Vitaly Bukafa947062015-04-17 00:41:31 -070030#include "buffet/org.chromium.Buffet.Manager.h"
Alex Vakulenko07216fe2014-09-19 15:31:09 -070031#include "buffet/states/state_manager.h"
Alex Vakulenkob04936f2014-09-19 14:53:58 -070032#include "buffet/utils.h"
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070033
Alex Vakulenkob3aac252014-05-07 17:35:24 -070034const char buffet::kErrorDomainOAuth2[] = "oauth2";
35const char buffet::kErrorDomainGCD[] = "gcd";
36const char buffet::kErrorDomainGCDServer[] = "gcd_server";
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070037
Alex Vakulenko8e34d392014-04-29 11:02:56 -070038namespace buffet {
39namespace storage_keys {
40
Christopher Wileybb5b8482015-02-12 13:42:16 -080041// Persistent keys
Vitaly Buka2c1029f2015-04-30 22:47:18 -070042const char kRefreshToken[] = "refresh_token";
43const char kDeviceId[] = "device_id";
44const char kRobotAccount[] = "robot_account";
45const char kName[] = "name";
46const char kDescription[] = "description";
47const char kLocation[] = "location";
48const char kAnonymousAccessRole[] = "anonymous_access_role";
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070049
Alex Vakulenko8e34d392014-04-29 11:02:56 -070050} // namespace storage_keys
51} // namespace buffet
52
53namespace {
54
Christopher Wileyba983c82015-03-05 16:32:23 -080055const int kMaxStartDeviceRetryDelayMinutes{1};
56const int64_t kMinStartDeviceRetryDelaySeconds{5};
Alex Vakulenkod1978d32015-04-29 17:33:26 -070057const int64_t kAbortCommandRetryDelaySeconds{5};
Christopher Wileyba983c82015-03-05 16:32:23 -080058
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070059std::pair<std::string, std::string> BuildAuthHeader(
60 const std::string& access_token_type,
61 const std::string& access_token) {
Alex Vakulenkoaf23b322014-05-08 16:25:45 -070062 std::string authorization =
Vitaly Bukadb770e72015-03-10 19:33:33 -070063 chromeos::string_utils::Join(" ", access_token_type, access_token);
Alex Vakulenkocca20932014-08-20 17:35:12 -070064 return {chromeos::http::request_header::kAuthorization, authorization};
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070065}
66
Alex Vakulenko5f472062014-08-14 17:54:04 -070067inline void SetUnexpectedError(chromeos::ErrorPtr* error) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -080068 chromeos::Error::AddTo(error, FROM_HERE, buffet::kErrorDomainGCD,
69 "unexpected_response", "Unexpected GCD error");
Alex Vakulenkob3aac252014-05-07 17:35:24 -070070}
71
Alex Vakulenko5f472062014-08-14 17:54:04 -070072void ParseGCDError(const base::DictionaryValue* json,
73 chromeos::ErrorPtr* error) {
Alex Vakulenkob3aac252014-05-07 17:35:24 -070074 const base::Value* list_value = nullptr;
75 const base::ListValue* error_list = nullptr;
76 if (!json->Get("error.errors", &list_value) ||
77 !list_value->GetAsList(&error_list)) {
78 SetUnexpectedError(error);
79 return;
80 }
81
82 for (size_t i = 0; i < error_list->GetSize(); i++) {
83 const base::Value* error_value = nullptr;
84 const base::DictionaryValue* error_object = nullptr;
85 if (!error_list->Get(i, &error_value) ||
86 !error_value->GetAsDictionary(&error_object)) {
87 SetUnexpectedError(error);
88 continue;
89 }
90 std::string error_code, error_message;
91 if (error_object->GetString("reason", &error_code) &&
92 error_object->GetString("message", &error_message)) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -080093 chromeos::Error::AddTo(error, FROM_HERE, buffet::kErrorDomainGCDServer,
Alex Vakulenko5f472062014-08-14 17:54:04 -070094 error_code, error_message);
Alex Vakulenkob3aac252014-05-07 17:35:24 -070095 } else {
96 SetUnexpectedError(error);
97 }
98 }
99}
100
Alex Vakulenkobda220a2014-04-18 15:25:44 -0700101std::string BuildURL(const std::string& url,
102 const std::vector<std::string>& subpaths,
Alex Vakulenkoe4879a22014-08-20 15:47:36 -0700103 const chromeos::data_encoding::WebParamList& params) {
Alex Vakulenkobd5b5442014-08-20 16:16:34 -0700104 std::string result = chromeos::url::CombineMultiple(url, subpaths);
105 return chromeos::url::AppendQueryParams(result, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700106}
107
Alex Vakulenko0357c032015-01-06 16:32:31 -0800108void IgnoreCloudError(const chromeos::Error*) {
Anton Muhin5191e812014-10-30 17:49:48 +0400109}
110
Christopher Wileyba983c82015-03-05 16:32:23 -0800111void IgnoreCloudErrorWithCallback(const base::Closure& cb,
112 const chromeos::Error*) {
113 cb.Run();
114}
115
Anton Muhin5191e812014-10-30 17:49:48 +0400116void IgnoreCloudResult(const base::DictionaryValue&) {
117}
Anton Muhin6d2569e2014-10-30 12:32:27 +0400118
Christopher Wileyba983c82015-03-05 16:32:23 -0800119void IgnoreCloudResultWithCallback(const base::Closure& cb,
120 const base::DictionaryValue&) {
121 cb.Run();
122}
123
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700124} // anonymous namespace
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700125
126namespace buffet {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700127
Alex Vakulenko1f30a622014-07-23 11:13:15 -0700128DeviceRegistrationInfo::DeviceRegistrationInfo(
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700129 const std::shared_ptr<CommandManager>& command_manager,
Anton Muhin332df192014-11-22 05:59:14 +0400130 const std::shared_ptr<StateManager>& state_manager,
Vitaly Buka867b0882015-04-16 10:03:26 -0700131 std::unique_ptr<BuffetConfig> config,
Alex Vakulenkocca20932014-08-20 17:35:12 -0700132 const std::shared_ptr<chromeos::http::Transport>& transport,
Christopher Wileyc900e482015-02-15 15:42:04 -0800133 const std::shared_ptr<StorageInterface>& state_store,
Christopher Wileyd732bd02015-04-07 11:11:18 -0700134 bool xmpp_enabled,
Vitaly Bukafa947062015-04-17 00:41:31 -0700135 org::chromium::Buffet::ManagerAdaptor* manager)
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700136 : transport_{transport},
Christopher Wileye0fdeee2015-02-07 18:29:32 -0800137 storage_{state_store},
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700138 command_manager_{command_manager},
Anton Muhin332df192014-11-22 05:59:14 +0400139 state_manager_{state_manager},
Christopher Wiley583d64b2015-03-24 14:30:17 -0700140 config_{std::move(config)},
Christopher Wileyd732bd02015-04-07 11:11:18 -0700141 xmpp_enabled_{xmpp_enabled},
Vitaly Bukafa947062015-04-17 00:41:31 -0700142 manager_{manager} {
143 OnConfigChanged();
Vitaly Buka5e6ff6c2015-05-11 15:41:33 -0700144 command_manager_->AddOnCommandDefChanged(
Alex Vakulenko9ea5a322015-04-17 15:35:34 -0700145 base::Bind(&DeviceRegistrationInfo::OnCommandDefsChanged,
146 weak_factory_.GetWeakPtr()));
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700147}
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700148
Anton Muhin332df192014-11-22 05:59:14 +0400149DeviceRegistrationInfo::~DeviceRegistrationInfo() = default;
150
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700151std::pair<std::string, std::string>
152 DeviceRegistrationInfo::GetAuthorizationHeader() const {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700153 return BuildAuthHeader("Bearer", access_token_);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700154}
155
156std::string DeviceRegistrationInfo::GetServiceURL(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700157 const std::string& subpath,
Alex Vakulenkoe4879a22014-08-20 15:47:36 -0700158 const chromeos::data_encoding::WebParamList& params) const {
Christopher Wiley583d64b2015-03-24 14:30:17 -0700159 return BuildURL(config_->service_url(), {subpath}, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700160}
161
162std::string DeviceRegistrationInfo::GetDeviceURL(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700163 const std::string& subpath,
Alex Vakulenkoe4879a22014-08-20 15:47:36 -0700164 const chromeos::data_encoding::WebParamList& params) const {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700165 CHECK(!device_id_.empty()) << "Must have a valid device ID";
Christopher Wiley583d64b2015-03-24 14:30:17 -0700166 return BuildURL(config_->service_url(),
167 {"devices", device_id_, subpath}, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700168}
169
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700170std::string DeviceRegistrationInfo::GetOAuthURL(
171 const std::string& subpath,
Alex Vakulenkoe4879a22014-08-20 15:47:36 -0700172 const chromeos::data_encoding::WebParamList& params) const {
Christopher Wiley583d64b2015-03-24 14:30:17 -0700173 return BuildURL(config_->oauth_url(), {subpath}, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700174}
175
Vitaly Buka620bd7e2015-03-16 01:07:01 -0700176const std::string& DeviceRegistrationInfo::GetDeviceId() const {
177 return device_id_;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700178}
179
180bool DeviceRegistrationInfo::Load() {
Vitaly Buka7ad8ffb2015-03-20 09:46:57 -0700181 // Set kInvalidCredentials to trigger on_status_changed_ callback.
182 registration_status_ = RegistrationStatus::kInvalidCredentials;
Vitaly Bukab055f152015-03-12 13:41:43 -0700183 SetRegistrationStatus(RegistrationStatus::kUnconfigured);
Vitaly Buka7ad8ffb2015-03-20 09:46:57 -0700184
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700185 auto value = storage_->Load();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700186 const base::DictionaryValue* dict = nullptr;
187 if (!value || !value->GetAsDictionary(&dict))
188 return false;
189
Vitaly Buka867b0882015-04-16 10:03:26 -0700190 // Read all available data before failing.
Alex Vakulenko468f7f32015-04-08 10:42:45 -0700191 std::string name;
Vitaly Buka867b0882015-04-16 10:03:26 -0700192 if (dict->GetString(storage_keys::kName, &name) && !name.empty())
193 config_->set_name(name);
Vitaly Buka6522ab62015-02-19 10:26:31 -0800194
Vitaly Buka867b0882015-04-16 10:03:26 -0700195 std::string description;
196 if (dict->GetString(storage_keys::kDescription, &description))
197 config_->set_description(description);
198
199 std::string location;
200 if (dict->GetString(storage_keys::kLocation, &location))
201 config_->set_location(location);
202
Vitaly Buka2c1029f2015-04-30 22:47:18 -0700203 std::string access_role;
204 if (dict->GetString(storage_keys::kAnonymousAccessRole, &access_role))
205 config_->set_anonymous_access_role(access_role);
206
Vitaly Buka867b0882015-04-16 10:03:26 -0700207 dict->GetString(storage_keys::kRefreshToken, &refresh_token_);
208 dict->GetString(storage_keys::kRobotAccount, &device_robot_account_);
209
210 std::string device_id;
211 if (dict->GetString(storage_keys::kDeviceId, &device_id))
212 SetDeviceId(device_id);
Vitaly Buka620bd7e2015-03-16 01:07:01 -0700213
Vitaly Bukafa947062015-04-17 00:41:31 -0700214 OnConfigChanged();
215
Christopher Wileyba983c82015-03-05 16:32:23 -0800216 if (HaveRegistrationCredentials(nullptr)) {
Christopher Wileyba983c82015-03-05 16:32:23 -0800217 // Wait a significant amount of time for local daemons to publish their
218 // state to Buffet before publishing it to the cloud.
219 // TODO(wiley) We could do a lot of things here to either expose this
220 // timeout as a configurable knob or allow local
221 // daemons to signal that their state is up to date so that
222 // we need not wait for them.
223 ScheduleStartDevice(base::TimeDelta::FromSeconds(5));
224 }
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700225 return true;
226}
227
228bool DeviceRegistrationInfo::Save() const {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700229 base::DictionaryValue dict;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700230 dict.SetString(storage_keys::kRefreshToken, refresh_token_);
231 dict.SetString(storage_keys::kDeviceId, device_id_);
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700232 dict.SetString(storage_keys::kRobotAccount, device_robot_account_);
Vitaly Buka867b0882015-04-16 10:03:26 -0700233 dict.SetString(storage_keys::kName, config_->name());
234 dict.SetString(storage_keys::kDescription, config_->description());
235 dict.SetString(storage_keys::kLocation, config_->location());
Vitaly Buka2c1029f2015-04-30 22:47:18 -0700236 dict.SetString(storage_keys::kAnonymousAccessRole,
237 config_->anonymous_access_role());
Nathan Bullock02ca28b2015-02-11 16:22:16 -0500238
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700239 return storage_->Save(&dict);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700240}
241
Christopher Wileycd419662015-02-06 17:51:43 -0800242void DeviceRegistrationInfo::ScheduleStartDevice(const base::TimeDelta& later) {
Vitaly Bukab055f152015-03-12 13:41:43 -0700243 SetRegistrationStatus(RegistrationStatus::kConnecting);
Christopher Wileycd419662015-02-06 17:51:43 -0800244 base::MessageLoop* current = base::MessageLoop::current();
245 if (!current)
246 return; // Assume we're in unittests
Christopher Wileyba983c82015-03-05 16:32:23 -0800247 base::TimeDelta max_delay =
248 base::TimeDelta::FromMinutes(kMaxStartDeviceRetryDelayMinutes);
249 base::TimeDelta min_delay =
250 base::TimeDelta::FromSeconds(kMinStartDeviceRetryDelaySeconds);
251 base::TimeDelta retry_delay = later * 2;
252 if (retry_delay > max_delay) { retry_delay = max_delay; }
253 if (retry_delay < min_delay) { retry_delay = min_delay; }
Christopher Wileycd419662015-02-06 17:51:43 -0800254 current->PostDelayedTask(
255 FROM_HERE,
256 base::Bind(&DeviceRegistrationInfo::StartDevice,
Christopher Wileyba983c82015-03-05 16:32:23 -0800257 weak_factory_.GetWeakPtr(), nullptr,
258 retry_delay),
Christopher Wileycd419662015-02-06 17:51:43 -0800259 later);
260}
261
Alex Vakulenko5f472062014-08-14 17:54:04 -0700262bool DeviceRegistrationInfo::CheckRegistration(chromeos::ErrorPtr* error) {
Christopher Wileyc900e482015-02-15 15:42:04 -0800263 return HaveRegistrationCredentials(error) &&
David Zeuthen390d1912015-03-03 14:54:48 -0500264 MaybeRefreshAccessToken(error);
Christopher Wileyc900e482015-02-15 15:42:04 -0800265}
266
267bool DeviceRegistrationInfo::HaveRegistrationCredentials(
268 chromeos::ErrorPtr* error) {
269 const bool have_credentials = !refresh_token_.empty() &&
270 !device_id_.empty() &&
271 !device_robot_account_.empty();
272
273 VLOG(1) << "Device registration record "
274 << ((have_credentials) ? "found" : "not found.");
275 if (!have_credentials)
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800276 chromeos::Error::AddTo(error, FROM_HERE, kErrorDomainGCD,
277 "device_not_registered",
Alex Vakulenko5f472062014-08-14 17:54:04 -0700278 "No valid device registration record found");
Christopher Wileyc900e482015-02-15 15:42:04 -0800279 return have_credentials;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700280}
281
Nathan Bullock24d189f2015-02-26 13:09:18 -0500282std::unique_ptr<base::DictionaryValue>
Alex Vakulenkof71cca62015-04-09 15:17:17 -0700283DeviceRegistrationInfo::ParseOAuthResponse(chromeos::http::Response* response,
284 chromeos::ErrorPtr* error) {
Nathan Bullock24d189f2015-02-26 13:09:18 -0500285 int code = 0;
286 auto resp = chromeos::http::ParseJsonResponse(response, &code, error);
287 if (resp && code >= chromeos::http::status_code::BadRequest) {
288 std::string error_code, error_message;
289 if (!resp->GetString("error", &error_code)) {
290 error_code = "unexpected_response";
291 }
292 if (error_code == "invalid_grant") {
293 LOG(INFO) << "The device's registration has been revoked.";
294 SetRegistrationStatus(RegistrationStatus::kInvalidCredentials);
295 }
296 // I have never actually seen an error_description returned.
297 if (!resp->GetString("error_description", &error_message)) {
298 error_message = "Unexpected OAuth error";
299 }
300 chromeos::Error::AddTo(error, FROM_HERE, buffet::kErrorDomainOAuth2,
301 error_code, error_message);
302 return std::unique_ptr<base::DictionaryValue>();
303 }
304 return resp;
305}
306
David Zeuthen390d1912015-03-03 14:54:48 -0500307bool DeviceRegistrationInfo::MaybeRefreshAccessToken(
Alex Vakulenko5f472062014-08-14 17:54:04 -0700308 chromeos::ErrorPtr* error) {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700309 LOG(INFO) << "Checking access token expiration.";
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700310 if (!access_token_.empty() &&
311 !access_token_expiration_.is_null() &&
312 access_token_expiration_ > base::Time::Now()) {
313 LOG(INFO) << "Access token is still valid.";
314 return true;
315 }
David Zeuthen390d1912015-03-03 14:54:48 -0500316 return RefreshAccessToken(error);
317}
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700318
David Zeuthen390d1912015-03-03 14:54:48 -0500319bool DeviceRegistrationInfo::RefreshAccessToken(
320 chromeos::ErrorPtr* error) {
321 LOG(INFO) << "Refreshing access token.";
Alex Vakulenko89dde2f2015-01-07 12:05:12 -0800322 auto response = chromeos::http::PostFormDataAndBlock(GetOAuthURL("token"), {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700323 {"refresh_token", refresh_token_},
Christopher Wiley583d64b2015-03-24 14:30:17 -0700324 {"client_id", config_->client_id()},
325 {"client_secret", config_->client_secret()},
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700326 {"grant_type", "refresh_token"},
Alex Vakulenko89dde2f2015-01-07 12:05:12 -0800327 }, {}, transport_, error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700328 if (!response)
329 return false;
330
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700331 auto json = ParseOAuthResponse(response.get(), error);
332 if (!json)
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700333 return false;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700334
335 int expires_in = 0;
336 if (!json->GetString("access_token", &access_token_) ||
337 !json->GetInteger("expires_in", &expires_in) ||
338 access_token_.empty() ||
339 expires_in <= 0) {
340 LOG(ERROR) << "Access token unavailable.";
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800341 chromeos::Error::AddTo(error, FROM_HERE, kErrorDomainOAuth2,
Alex Vakulenko5f472062014-08-14 17:54:04 -0700342 "unexpected_server_response",
343 "Access token unavailable");
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700344 return false;
345 }
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700346 access_token_expiration_ = base::Time::Now() +
347 base::TimeDelta::FromSeconds(expires_in);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700348 LOG(INFO) << "Access token is refreshed for additional " << expires_in
349 << " seconds.";
Nathan Bullockd9e0bcd2015-02-11 11:36:39 -0500350
Nathan Bullockbea91132015-02-19 09:13:33 -0500351 StartXmpp();
Nathan Bullockd9e0bcd2015-02-11 11:36:39 -0500352
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700353 return true;
354}
355
Nathan Bullockbea91132015-02-19 09:13:33 -0500356void DeviceRegistrationInfo::StartXmpp() {
Christopher Wileyd732bd02015-04-07 11:11:18 -0700357 if (!xmpp_enabled_) {
358 LOG(WARNING) << "XMPP support disabled by flag.";
359 return;
360 }
Nathan Bullockbea91132015-02-19 09:13:33 -0500361 // If no MessageLoop assume we're in unittests.
362 if (!base::MessageLoop::current()) {
363 LOG(INFO) << "No MessageLoop, not starting XMPP";
364 return;
365 }
366
367 if (!fd_watcher_.StopWatchingFileDescriptor()) {
368 LOG(WARNING) << "Failed to stop the previous watcher";
369 return;
370 }
371
372 std::unique_ptr<XmppConnection> connection(new XmppConnection());
373 if (!connection->Initialize()) {
374 LOG(WARNING) << "Failed to connect to XMPP server";
375 return;
376 }
377 xmpp_client_.reset(new XmppClient(device_robot_account_, access_token_,
378 std::move(connection)));
379 if (!base::MessageLoopForIO::current()->WatchFileDescriptor(
380 xmpp_client_->GetFileDescriptor(), true /* persistent */,
381 base::MessageLoopForIO::WATCH_READ, &fd_watcher_, this)) {
382 LOG(WARNING) << "Failed to watch XMPP file descriptor";
383 return;
384 }
Vitaly Bukab055f152015-03-12 13:41:43 -0700385
Nathan Bullockbea91132015-02-19 09:13:33 -0500386 xmpp_client_->StartStream();
387}
388
Nathan Bullockf12f7f02015-02-20 14:46:53 -0500389void DeviceRegistrationInfo::OnFileCanReadWithoutBlocking(int fd) {
Vitaly Bukab055f152015-03-12 13:41:43 -0700390 if (!xmpp_client_ || xmpp_client_->GetFileDescriptor() != fd)
391 return;
392 if (!xmpp_client_->Read()) {
393 // Authentication failed or the socket was closed.
394 if (!fd_watcher_.StopWatchingFileDescriptor())
395 LOG(WARNING) << "Failed to stop the watcher";
396 return;
Nathan Bullockf12f7f02015-02-20 14:46:53 -0500397 }
398}
399
Anton Muhind8d32162014-10-02 20:37:00 +0400400std::unique_ptr<base::DictionaryValue>
401DeviceRegistrationInfo::BuildDeviceResource(chromeos::ErrorPtr* error) {
Alex Vakulenko9ea5a322015-04-17 15:35:34 -0700402 // Limit only to commands that are visible to the cloud.
403 auto commands = command_manager_->GetCommandDictionary().GetCommandsAsJson(
404 [](const CommandDefinition* def) { return def->GetVisibility().cloud; },
405 true, error);
Anton Muhind8d32162014-10-02 20:37:00 +0400406 if (!commands)
407 return nullptr;
408
409 std::unique_ptr<base::DictionaryValue> state =
410 state_manager_->GetStateValuesAsJson(error);
411 if (!state)
412 return nullptr;
413
414 std::unique_ptr<base::DictionaryValue> resource{new base::DictionaryValue};
415 if (!device_id_.empty())
416 resource->SetString("id", device_id_);
Vitaly Buka867b0882015-04-16 10:03:26 -0700417 resource->SetString("name", config_->name());
418 if (!config_->description().empty())
419 resource->SetString("description", config_->description());
420 if (!config_->location().empty())
421 resource->SetString("location", config_->location());
422 resource->SetString("modelManifestId", config_->model_id());
Christopher Wiley583d64b2015-03-24 14:30:17 -0700423 resource->SetString("deviceKind", config_->device_kind());
Anton Muhind8d32162014-10-02 20:37:00 +0400424 resource->SetString("channel.supportedType", "xmpp");
425 resource->Set("commandDefs", commands.release());
426 resource->Set("state", state.release());
427
428 return resource;
429}
430
Vitaly Buka6ca3ad62015-03-11 17:03:23 -0700431std::unique_ptr<base::DictionaryValue> DeviceRegistrationInfo::GetDeviceInfo(
Alex Vakulenko5f472062014-08-14 17:54:04 -0700432 chromeos::ErrorPtr* error) {
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700433 if (!CheckRegistration(error))
Vitaly Buka6ca3ad62015-03-11 17:03:23 -0700434 return std::unique_ptr<base::DictionaryValue>();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700435
Anton Muhinac661ab2014-10-03 20:29:48 +0400436 // TODO(antonm): Switch to DoCloudRequest later.
Alex Vakulenko89dde2f2015-01-07 12:05:12 -0800437 auto response = chromeos::http::GetAndBlock(
Alex Vakulenkocca20932014-08-20 17:35:12 -0700438 GetDeviceURL(), {GetAuthorizationHeader()}, transport_, error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700439 int status_code = 0;
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700440 std::unique_ptr<base::DictionaryValue> json =
Alex Vakulenkocca20932014-08-20 17:35:12 -0700441 chromeos::http::ParseJsonResponse(response.get(), &status_code, error);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700442 if (json) {
Alex Vakulenkocca20932014-08-20 17:35:12 -0700443 if (status_code >= chromeos::http::status_code::BadRequest) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700444 LOG(WARNING) << "Failed to retrieve the device info. Response code = "
445 << status_code;
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700446 ParseGCDError(json.get(), error);
Vitaly Buka6ca3ad62015-03-11 17:03:23 -0700447 return std::unique_ptr<base::DictionaryValue>();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700448 }
449 }
Vitaly Buka6ca3ad62015-03-11 17:03:23 -0700450 return json;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700451}
452
Christopher Wiley583d64b2015-03-24 14:30:17 -0700453namespace {
454
455bool GetWithDefault(const std::map<std::string, std::string>& source,
456 const std::string& key,
457 const std::string& default_value,
458 std::string* output) {
459 auto it = source.find(key);
460 if (it == source.end()) {
461 *output = default_value;
462 return false;
463 }
464 *output = it->second;
465 return true;
466}
467
468} // namespace
469
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400470std::string DeviceRegistrationInfo::RegisterDevice(
Alex Vakulenkoa9044342014-08-23 19:31:27 -0700471 const std::map<std::string, std::string>& params,
Alex Vakulenko5f472062014-08-14 17:54:04 -0700472 chromeos::ErrorPtr* error) {
Nathan Bullocke4408482015-02-19 11:13:21 -0500473 std::string ticket_id;
Christopher Wiley583d64b2015-03-24 14:30:17 -0700474 if (!GetWithDefault(params, "ticket_id", "", &ticket_id)) {
475 chromeos::Error::AddTo(error, FROM_HERE, kErrorDomainBuffet,
476 "missing_parameter",
Vitaly Bukafa947062015-04-17 00:41:31 -0700477 "Need ticket_id parameter for RegisterDevice()");
David Zeuthend0db55c2015-02-12 14:47:35 -0500478 return std::string();
Vitaly Buka6522ab62015-02-19 10:26:31 -0800479 }
Vitaly Bukafa947062015-04-17 00:41:31 -0700480
Christopher Wiley583d64b2015-03-24 14:30:17 -0700481 // These fields are optional, and will default to values from the manufacturer
482 // supplied config.
Vitaly Buka867b0882015-04-16 10:03:26 -0700483 std::string name;
484 GetWithDefault(params, storage_keys::kName, config_->name(), &name);
Vitaly Buka867b0882015-04-16 10:03:26 -0700485 std::string description;
486 GetWithDefault(params, storage_keys::kDescription, config_->description(),
487 &description);
Vitaly Buka867b0882015-04-16 10:03:26 -0700488 std::string location;
489 GetWithDefault(params, storage_keys::kLocation, config_->location(),
490 &location);
Vitaly Bukafa947062015-04-17 00:41:31 -0700491 if (!UpdateDeviceInfo(name, description, location, error))
492 return std::string();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700493
Anton Muhind8d32162014-10-02 20:37:00 +0400494 std::unique_ptr<base::DictionaryValue> device_draft =
495 BuildDeviceResource(error);
496 if (!device_draft)
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700497 return std::string();
498
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700499 base::DictionaryValue req_json;
Nathan Bullocke4408482015-02-19 11:13:21 -0500500 req_json.SetString("id", ticket_id);
Christopher Wiley583d64b2015-03-24 14:30:17 -0700501 req_json.SetString("oauthClientId", config_->client_id());
Anton Muhind8d32162014-10-02 20:37:00 +0400502 req_json.Set("deviceDraft", device_draft.release());
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700503
Nathan Bullocke4408482015-02-19 11:13:21 -0500504 auto url = GetServiceURL("registrationTickets/" + ticket_id,
Christopher Wiley583d64b2015-03-24 14:30:17 -0700505 {{"key", config_->api_key()}});
Anton Muhin532ff7e2014-09-29 23:21:21 +0400506 std::unique_ptr<chromeos::http::Response> response =
Alex Vakulenko89dde2f2015-01-07 12:05:12 -0800507 chromeos::http::PatchJsonAndBlock(url, &req_json, {}, transport_, error);
Anton Muhin532ff7e2014-09-29 23:21:21 +0400508 auto json_resp = chromeos::http::ParseJsonResponse(response.get(), nullptr,
509 error);
510 if (!json_resp)
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700511 return std::string();
David Zeuthen1dbad472015-02-12 15:24:21 -0500512 if (!response->IsSuccessful()) {
513 ParseGCDError(json_resp.get(), error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700514 return std::string();
David Zeuthen1dbad472015-02-12 15:24:21 -0500515 }
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700516
Nathan Bullocke4408482015-02-19 11:13:21 -0500517 url = GetServiceURL("registrationTickets/" + ticket_id +
Christopher Wiley583d64b2015-03-24 14:30:17 -0700518 "/finalize?key=" + config_->api_key());
Alex Vakulenko89dde2f2015-01-07 12:05:12 -0800519 response = chromeos::http::SendRequestWithNoDataAndBlock(
520 chromeos::http::request_type::kPost, url, {}, transport_, error);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700521 if (!response)
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400522 return std::string();
523 json_resp = chromeos::http::ParseJsonResponse(response.get(), nullptr, error);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700524 if (!json_resp)
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400525 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700526 if (!response->IsSuccessful()) {
527 ParseGCDError(json_resp.get(), error);
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400528 return std::string();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700529 }
Anton Muhin532ff7e2014-09-29 23:21:21 +0400530
531 std::string auth_code;
Vitaly Buka620bd7e2015-03-16 01:07:01 -0700532 std::string device_id;
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700533 if (!json_resp->GetString("robotAccountEmail", &device_robot_account_) ||
534 !json_resp->GetString("robotAccountAuthorizationCode", &auth_code) ||
Vitaly Buka620bd7e2015-03-16 01:07:01 -0700535 !json_resp->GetString("deviceDraft.id", &device_id)) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800536 chromeos::Error::AddTo(error, FROM_HERE, kErrorDomainGCD,
537 "unexpected_response",
Alex Vakulenko5f472062014-08-14 17:54:04 -0700538 "Device account missing in response");
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400539 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700540 }
Vitaly Buka620bd7e2015-03-16 01:07:01 -0700541 SetDeviceId(device_id);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700542
543 // Now get access_token and refresh_token
Alex Vakulenko89dde2f2015-01-07 12:05:12 -0800544 response = chromeos::http::PostFormDataAndBlock(GetOAuthURL("token"), {
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700545 {"code", auth_code},
Christopher Wiley583d64b2015-03-24 14:30:17 -0700546 {"client_id", config_->client_id()},
547 {"client_secret", config_->client_secret()},
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700548 {"redirect_uri", "oob"},
549 {"scope", "https://www.googleapis.com/auth/clouddevices"},
550 {"grant_type", "authorization_code"}
Alex Vakulenko89dde2f2015-01-07 12:05:12 -0800551 }, {}, transport_, error);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700552 if (!response)
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400553 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700554
555 json_resp = ParseOAuthResponse(response.get(), error);
556 int expires_in = 0;
557 if (!json_resp ||
558 !json_resp->GetString("access_token", &access_token_) ||
559 !json_resp->GetString("refresh_token", &refresh_token_) ||
560 !json_resp->GetInteger("expires_in", &expires_in) ||
561 access_token_.empty() ||
562 refresh_token_.empty() ||
563 expires_in <= 0) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800564 chromeos::Error::AddTo(error, FROM_HERE,
565 kErrorDomainGCD, "unexpected_response",
Alex Vakulenko5f472062014-08-14 17:54:04 -0700566 "Device access_token missing in response");
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400567 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700568 }
569
570 access_token_expiration_ = base::Time::Now() +
571 base::TimeDelta::FromSeconds(expires_in);
572
573 Save();
Nathan Bullockbea91132015-02-19 09:13:33 -0500574 StartXmpp();
Christopher Wileycd419662015-02-06 17:51:43 -0800575
576 // We're going to respond with our success immediately and we'll StartDevice
577 // shortly after.
578 ScheduleStartDevice(base::TimeDelta::FromSeconds(0));
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400579 return device_id_;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700580}
581
Anton Muhin633eded2014-10-03 20:40:10 +0400582namespace {
583
584template <class T>
585void PostToCallback(base::Callback<void(const T&)> callback,
586 std::unique_ptr<T> value) {
587 auto cb = [callback] (T* result) {
588 callback.Run(*result);
589 };
590 base::MessageLoop::current()->PostTask(
591 FROM_HERE, base::Bind(cb, base::Owned(value.release())));
592}
593
Alex Vakulenkoab5f27b2015-04-24 16:42:50 -0700594using ResponsePtr = std::unique_ptr<chromeos::http::Response>;
Anton Muhin633eded2014-10-03 20:40:10 +0400595
Alex Vakulenko0357c032015-01-06 16:32:31 -0800596void SendRequestWithRetries(
Anton Muhin633eded2014-10-03 20:40:10 +0400597 const std::string& method,
598 const std::string& url,
599 const std::string& data,
600 const std::string& mime_type,
601 const chromeos::http::HeaderList& headers,
602 std::shared_ptr<chromeos::http::Transport> transport,
603 int num_retries,
Alex Vakulenko0357c032015-01-06 16:32:31 -0800604 const chromeos::http::SuccessCallback& success_callback,
605 const chromeos::http::ErrorCallback& error_callback) {
606 auto on_failure =
607 [method, url, data, mime_type, headers, transport, num_retries,
Alex Vakulenko6401d012015-01-16 07:40:43 -0800608 success_callback, error_callback](int request_id,
609 const chromeos::Error* error) {
Anton Muhin633eded2014-10-03 20:40:10 +0400610 if (num_retries > 0) {
Alex Vakulenko0357c032015-01-06 16:32:31 -0800611 SendRequestWithRetries(method, url, data, mime_type,
612 headers, transport, num_retries - 1,
613 success_callback, error_callback);
Anton Muhin633eded2014-10-03 20:40:10 +0400614 } else {
Alex Vakulenko6401d012015-01-16 07:40:43 -0800615 error_callback.Run(request_id, error);
Anton Muhin633eded2014-10-03 20:40:10 +0400616 }
617 };
618
Alex Vakulenko0357c032015-01-06 16:32:31 -0800619 auto on_success =
Alex Vakulenko6401d012015-01-16 07:40:43 -0800620 [on_failure, success_callback, error_callback](int request_id,
621 ResponsePtr response) {
Alex Vakulenko0357c032015-01-06 16:32:31 -0800622 int status_code = response->GetStatusCode();
623 if (status_code >= chromeos::http::status_code::Continue &&
624 status_code < chromeos::http::status_code::BadRequest) {
Alex Vakulenkoab5f27b2015-04-24 16:42:50 -0700625 success_callback.Run(request_id, std::move(response));
Anton Muhin633eded2014-10-03 20:40:10 +0400626 return;
627 }
Anton Muhin633eded2014-10-03 20:40:10 +0400628
Alex Vakulenko0357c032015-01-06 16:32:31 -0800629 // TODO(antonm): Should add some useful information to error.
630 LOG(WARNING) << "Request failed. Response code = " << status_code;
Anton Muhin633eded2014-10-03 20:40:10 +0400631
Alex Vakulenko0357c032015-01-06 16:32:31 -0800632 chromeos::ErrorPtr error;
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800633 chromeos::Error::AddTo(&error, FROM_HERE, chromeos::errors::http::kDomain,
Anton Muhin233d2ee2014-10-22 15:16:24 +0400634 std::to_string(status_code),
635 response->GetStatusText());
Alex Vakulenko0357c032015-01-06 16:32:31 -0800636 if (status_code >= chromeos::http::status_code::InternalServerError &&
637 status_code < 600) {
638 // Request was valid, but server failed, retry.
639 // TODO(antonm): Implement exponential backoff.
640 // TODO(antonm): Reconsider status codes, maybe only some require
641 // retry.
642 // TODO(antonm): Support Retry-After header.
Alex Vakulenko6401d012015-01-16 07:40:43 -0800643 on_failure(request_id, error.get());
Alex Vakulenko0357c032015-01-06 16:32:31 -0800644 } else {
Alex Vakulenko6401d012015-01-16 07:40:43 -0800645 error_callback.Run(request_id, error.get());
Alex Vakulenko0357c032015-01-06 16:32:31 -0800646 }
647 };
648
649 chromeos::http::SendRequest(method, url, data.c_str(), data.size(),
650 mime_type, headers, transport,
651 base::Bind(on_success),
652 base::Bind(on_failure));
Anton Muhin633eded2014-10-03 20:40:10 +0400653}
654
655} // namespace
656
Anton Muhinac661ab2014-10-03 20:29:48 +0400657void DeviceRegistrationInfo::DoCloudRequest(
Anton Muhin233d2ee2014-10-22 15:16:24 +0400658 const std::string& method,
Anton Muhinac661ab2014-10-03 20:29:48 +0400659 const std::string& url,
660 const base::DictionaryValue* body,
Alex Vakulenko0357c032015-01-06 16:32:31 -0800661 const CloudRequestCallback& success_callback,
662 const CloudRequestErrorCallback& error_callback) {
663 // TODO(antonm): Add reauthorization on access token expiration (do not
Anton Muhinac661ab2014-10-03 20:29:48 +0400664 // forget about 5xx when fetching new access token).
665 // TODO(antonm): Add support for device removal.
666
Anton Muhinac661ab2014-10-03 20:29:48 +0400667 std::string data;
668 if (body)
669 base::JSONWriter::Write(body, &data);
670
671 const std::string mime_type{chromeos::mime::AppendParameter(
672 chromeos::mime::application::kJson,
673 chromeos::mime::parameters::kCharset,
674 "utf-8")};
675
Vitaly Bukab055f152015-03-12 13:41:43 -0700676 auto status_cb = base::Bind(&DeviceRegistrationInfo::SetRegistrationStatus,
677 weak_factory_.GetWeakPtr());
678
679 auto request_cb = [success_callback, error_callback, status_cb](
680 int request_id, ResponsePtr response) {
681 status_cb.Run(RegistrationStatus::kConnected);
Anton Muhin633eded2014-10-03 20:40:10 +0400682 chromeos::ErrorPtr error;
Anton Muhinac661ab2014-10-03 20:29:48 +0400683
Anton Muhin633eded2014-10-03 20:40:10 +0400684 std::unique_ptr<base::DictionaryValue> json_resp{
Alex Vakulenko0357c032015-01-06 16:32:31 -0800685 chromeos::http::ParseJsonResponse(response.get(), nullptr, &error)};
Anton Muhin633eded2014-10-03 20:40:10 +0400686 if (!json_resp) {
Alex Vakulenko0357c032015-01-06 16:32:31 -0800687 error_callback.Run(error.get());
Anton Muhin633eded2014-10-03 20:40:10 +0400688 return;
689 }
Anton Muhinac661ab2014-10-03 20:29:48 +0400690
Alex Vakulenko0357c032015-01-06 16:32:31 -0800691 success_callback.Run(*json_resp);
Anton Muhinac661ab2014-10-03 20:29:48 +0400692 };
Anton Muhin633eded2014-10-03 20:40:10 +0400693
Alex Vakulenko6401d012015-01-16 07:40:43 -0800694 auto error_cb =
695 [error_callback](int request_id, const chromeos::Error* error) {
696 error_callback.Run(error);
697 };
698
Anton Muhin233d2ee2014-10-22 15:16:24 +0400699 auto transport = transport_;
Vitaly Bukab055f152015-03-12 13:41:43 -0700700 auto error_callackback_with_reauthorization = base::Bind(
701 [method, url, data, mime_type, transport, request_cb, error_cb,
702 status_cb](DeviceRegistrationInfo* self, int request_id,
703 const chromeos::Error* error) {
704 status_cb.Run(RegistrationStatus::kConnecting);
705 if (error->HasError(
706 chromeos::errors::http::kDomain,
707 std::to_string(chromeos::http::status_code::Denied))) {
708 chromeos::ErrorPtr reauthorization_error;
709 // Forcibly refresh the access token.
710 if (!self->RefreshAccessToken(&reauthorization_error)) {
711 // TODO(antonm): Check if the device has been actually removed.
712 error_cb(request_id, reauthorization_error.get());
713 return;
714 }
715 SendRequestWithRetries(method, url, data, mime_type,
716 {self->GetAuthorizationHeader()}, transport, 7,
717 base::Bind(request_cb), base::Bind(error_cb));
718 } else {
719 error_cb(request_id, error);
720 }
721 },
722 base::Unretained(this));
Anton Muhin233d2ee2014-10-22 15:16:24 +0400723
Alex Vakulenko0357c032015-01-06 16:32:31 -0800724 SendRequestWithRetries(method, url,
725 data, mime_type,
726 {GetAuthorizationHeader()},
727 transport,
728 7,
729 base::Bind(request_cb),
730 error_callackback_with_reauthorization);
Anton Muhinac661ab2014-10-03 20:29:48 +0400731}
732
Christopher Wileyba983c82015-03-05 16:32:23 -0800733void DeviceRegistrationInfo::StartDevice(
734 chromeos::ErrorPtr* error,
735 const base::TimeDelta& retry_delay) {
Nathan Bullock8fb6de72015-02-24 11:58:39 -0500736 if (!HaveRegistrationCredentials(error))
Anton Muhind8d32162014-10-02 20:37:00 +0400737 return;
Christopher Wileyba983c82015-03-05 16:32:23 -0800738 auto handle_start_device_failure_cb = base::Bind(
739 &IgnoreCloudErrorWithCallback,
740 base::Bind(&DeviceRegistrationInfo::ScheduleStartDevice,
741 weak_factory_.GetWeakPtr(),
742 retry_delay));
743 // "Starting" a device just means that we:
744 // 1) push an updated device resource
745 // 2) fetch an initial set of outstanding commands
746 // 3) abort any commands that we've previously marked as "in progress"
747 // or as being in an error state.
748 // 4) Initiate periodic polling for commands.
749 auto periodically_poll_commands_cb = base::Bind(
750 &DeviceRegistrationInfo::PeriodicallyPollCommands,
751 weak_factory_.GetWeakPtr());
752 auto abort_commands_cb = base::Bind(
753 &DeviceRegistrationInfo::AbortLimboCommands,
754 weak_factory_.GetWeakPtr(),
755 periodically_poll_commands_cb);
756 auto fetch_commands_cb = base::Bind(
757 &DeviceRegistrationInfo::FetchCommands,
758 weak_factory_.GetWeakPtr(),
759 abort_commands_cb,
760 handle_start_device_failure_cb);
761 UpdateDeviceResource(fetch_commands_cb, handle_start_device_failure_cb);
Anton Muhinc635c592014-10-28 21:48:08 +0400762}
763
Vitaly Bukafa947062015-04-17 00:41:31 -0700764bool DeviceRegistrationInfo::UpdateDeviceInfo(const std::string& name,
765 const std::string& description,
766 const std::string& location,
767 chromeos::ErrorPtr* error) {
768 if (name.empty()) {
769 chromeos::Error::AddTo(error, FROM_HERE, kErrorDomainBuffet,
770 "invalid_parameter", "Empty device name");
771 return false;
772 }
773 config_->set_name(name);
774 config_->set_description(description);
775 config_->set_location(location);
776
Vitaly Bukacf5248c2015-04-30 18:49:08 -0700777 Save();
778
Vitaly Bukafa947062015-04-17 00:41:31 -0700779 OnConfigChanged();
780
781 if (HaveRegistrationCredentials(nullptr)) {
782 UpdateDeviceResource(base::Bind(&base::DoNothing),
783 base::Bind(&IgnoreCloudError));
784 }
785
786 return true;
787}
788
Anton Muhin59755522014-11-05 21:30:12 +0400789void DeviceRegistrationInfo::UpdateCommand(
790 const std::string& command_id,
Alex Vakulenkob211c102015-04-21 11:43:23 -0700791 const base::DictionaryValue& command_patch,
792 const base::Closure& on_success,
793 const base::Closure& on_error) {
Anton Muhin59755522014-11-05 21:30:12 +0400794 DoCloudRequest(
795 chromeos::http::request_type::kPatch,
796 GetServiceURL("commands/" + command_id),
797 &command_patch,
Alex Vakulenkob211c102015-04-21 11:43:23 -0700798 base::Bind(&IgnoreCloudResultWithCallback, on_success),
799 base::Bind(&IgnoreCloudErrorWithCallback, on_error));
Anton Muhin59755522014-11-05 21:30:12 +0400800}
801
Alex Vakulenkod1978d32015-04-29 17:33:26 -0700802void DeviceRegistrationInfo::NotifyCommandAborted(
803 const std::string& command_id,
804 chromeos::ErrorPtr error) {
805 base::DictionaryValue command_patch;
806 command_patch.SetString(commands::attributes::kCommand_State,
807 CommandInstance::kStatusAborted);
808 if (error) {
809 command_patch.SetString(commands::attributes::kCommand_ErrorCode,
810 chromeos::string_utils::Join(":",
811 error->GetDomain(),
812 error->GetCode()));
813 std::vector<std::string> messages;
814 const chromeos::Error* current_error = error.get();
815 while (current_error) {
816 messages.push_back(current_error->GetMessage());
817 current_error = current_error->GetInnerError();
818 }
819 command_patch.SetString(commands::attributes::kCommand_ErrorMessage,
820 chromeos::string_utils::Join(";", messages));
821 }
822 UpdateCommand(command_id,
823 command_patch,
824 base::Bind(&base::DoNothing),
825 base::Bind(&DeviceRegistrationInfo::RetryNotifyCommandAborted,
826 weak_factory_.GetWeakPtr(),
827 command_id, base::Passed(std::move(error))));
828}
829
830void DeviceRegistrationInfo::RetryNotifyCommandAborted(
831 const std::string& command_id,
832 chromeos::ErrorPtr error) {
833 base::MessageLoop::current()->PostDelayedTask(
834 FROM_HERE,
835 base::Bind(&DeviceRegistrationInfo::NotifyCommandAborted,
836 weak_factory_.GetWeakPtr(),
837 command_id, base::Passed(std::move(error))),
838 base::TimeDelta::FromSeconds(kAbortCommandRetryDelaySeconds));
839}
840
Christopher Wileyba983c82015-03-05 16:32:23 -0800841void DeviceRegistrationInfo::UpdateDeviceResource(
842 const base::Closure& on_success,
843 const CloudRequestErrorCallback& on_failure) {
Alex Vakulenko9ea5a322015-04-17 15:35:34 -0700844 VLOG(1) << "Updating GCD server with CDD...";
Anton Muhind8d32162014-10-02 20:37:00 +0400845 std::unique_ptr<base::DictionaryValue> device_resource =
Anton Muhinc635c592014-10-28 21:48:08 +0400846 BuildDeviceResource(nullptr);
Anton Muhind8d32162014-10-02 20:37:00 +0400847 if (!device_resource)
848 return;
849
Anton Muhinc635c592014-10-28 21:48:08 +0400850 DoCloudRequest(
851 chromeos::http::request_type::kPut,
852 GetDeviceURL(),
853 device_resource.get(),
Christopher Wileyba983c82015-03-05 16:32:23 -0800854 base::Bind(&IgnoreCloudResultWithCallback, on_success),
855 on_failure);
Anton Muhinc635c592014-10-28 21:48:08 +0400856}
Anton Muhina34f0d92014-10-03 21:09:40 +0400857
Christopher Wileyba983c82015-03-05 16:32:23 -0800858namespace {
859
860void HandleFetchCommandsResult(
861 const base::Callback<void(const base::ListValue&)>& callback,
862 const base::DictionaryValue& json) {
863 const base::ListValue* commands{nullptr};
864 if (!json.GetList("commands", &commands)) {
865 VLOG(1) << "No commands in the response.";
866 }
867 const base::ListValue empty;
868 callback.Run(commands ? *commands : empty);
869}
870
871} // namespace
872
Anton Muhinc635c592014-10-28 21:48:08 +0400873void DeviceRegistrationInfo::FetchCommands(
Christopher Wileyba983c82015-03-05 16:32:23 -0800874 const base::Callback<void(const base::ListValue&)>& on_success,
875 const CloudRequestErrorCallback& on_failure) {
Anton Muhinc635c592014-10-28 21:48:08 +0400876 DoCloudRequest(
877 chromeos::http::request_type::kGet,
878 GetServiceURL("commands/queue", {{"deviceId", device_id_}}),
879 nullptr,
Christopher Wileyba983c82015-03-05 16:32:23 -0800880 base::Bind(&HandleFetchCommandsResult, on_success),
881 on_failure);
Anton Muhinc635c592014-10-28 21:48:08 +0400882}
Anton Muhina34f0d92014-10-03 21:09:40 +0400883
Anton Muhinc635c592014-10-28 21:48:08 +0400884void DeviceRegistrationInfo::AbortLimboCommands(
Christopher Wileyba983c82015-03-05 16:32:23 -0800885 const base::Closure& callback, const base::ListValue& commands) {
Anton Muhinc635c592014-10-28 21:48:08 +0400886 const size_t size{commands.GetSize()};
887 for (size_t i = 0; i < size; ++i) {
888 const base::DictionaryValue* command{nullptr};
889 if (!commands.GetDictionary(i, &command)) {
890 LOG(WARNING) << "No command resource at " << i;
891 continue;
Anton Muhina34f0d92014-10-03 21:09:40 +0400892 }
Anton Muhinc635c592014-10-28 21:48:08 +0400893 std::string command_state;
894 if (!command->GetString("state", &command_state)) {
895 LOG(WARNING) << "Command with no state at " << i;
896 continue;
897 }
898 if (command_state != "error" &&
899 command_state != "inProgress" &&
900 command_state != "paused") {
901 // It's not a limbo command, ignore.
902 continue;
903 }
904 std::string command_id;
905 if (!command->GetString("id", &command_id)) {
906 LOG(WARNING) << "Command with no ID at " << i;
907 continue;
908 }
Anton Muhin6d2569e2014-10-30 12:32:27 +0400909
910 std::unique_ptr<base::DictionaryValue> command_copy{command->DeepCopy()};
911 command_copy->SetString("state", "aborted");
Christopher Wileyba983c82015-03-05 16:32:23 -0800912 // TODO(wiley) We could consider handling this error case more gracefully.
Anton Muhin6d2569e2014-10-30 12:32:27 +0400913 DoCloudRequest(
914 chromeos::http::request_type::kPut,
915 GetServiceURL("commands/" + command_id),
916 command_copy.get(),
Anton Muhin5191e812014-10-30 17:49:48 +0400917 base::Bind(&IgnoreCloudResult), base::Bind(&IgnoreCloudError));
Anton Muhinc635c592014-10-28 21:48:08 +0400918 }
Anton Muhin0ae1fba2014-10-22 19:30:40 +0400919
Anton Muhinc635c592014-10-28 21:48:08 +0400920 base::MessageLoop::current()->PostTask(FROM_HERE, callback);
921}
922
923void DeviceRegistrationInfo::PeriodicallyPollCommands() {
Anton Muhind07e2062014-10-27 10:53:29 +0400924 VLOG(1) << "Poll commands";
Christopher Wiley34eae042015-03-18 10:25:08 -0700925 command_poll_timer_.Start(
Anton Muhind07e2062014-10-27 10:53:29 +0400926 FROM_HERE,
Christopher Wiley34eae042015-03-18 10:25:08 -0700927 base::TimeDelta::FromMilliseconds(config_->polling_period_ms()),
928 base::Bind(&DeviceRegistrationInfo::FetchCommands,
929 base::Unretained(this),
930 base::Bind(&DeviceRegistrationInfo::PublishCommands,
931 base::Unretained(this)),
932 base::Bind(&IgnoreCloudError)));
Anton Muhinb8315622014-11-20 03:17:05 +0400933 // TODO(antonm): Use better trigger: when StateManager registers new updates,
Alex Vakulenko0357c032015-01-06 16:32:31 -0800934 // it should call closure which will post a task, probably with some
935 // throttling, to publish state updates.
Christopher Wiley34eae042015-03-18 10:25:08 -0700936 state_push_timer_.Start(
Anton Muhinb8315622014-11-20 03:17:05 +0400937 FROM_HERE,
Christopher Wiley34eae042015-03-18 10:25:08 -0700938 base::TimeDelta::FromMilliseconds(config_->polling_period_ms()),
Anton Muhinb8315622014-11-20 03:17:05 +0400939 base::Bind(&DeviceRegistrationInfo::PublishStateUpdates,
Christopher Wiley34eae042015-03-18 10:25:08 -0700940 base::Unretained(this)));
Anton Muhind07e2062014-10-27 10:53:29 +0400941}
942
943void DeviceRegistrationInfo::PublishCommands(const base::ListValue& commands) {
944 const CommandDictionary& command_dictionary =
945 command_manager_->GetCommandDictionary();
946
947 const size_t size{commands.GetSize()};
948 for (size_t i = 0; i < size; ++i) {
949 const base::DictionaryValue* command{nullptr};
950 if (!commands.GetDictionary(i, &command)) {
951 LOG(WARNING) << "No command resource at " << i;
952 continue;
953 }
954
Alex Vakulenkod1978d32015-04-29 17:33:26 -0700955 std::string command_id;
956 chromeos::ErrorPtr error;
Alex Vakulenkof784e212015-04-20 12:33:52 -0700957 auto command_instance = CommandInstance::FromJson(
958 command, commands::attributes::kCommand_Visibility_Cloud,
Alex Vakulenkod1978d32015-04-29 17:33:26 -0700959 command_dictionary, &command_id, &error);
Anton Muhind07e2062014-10-27 10:53:29 +0400960 if (!command_instance) {
Alex Vakulenkod1978d32015-04-29 17:33:26 -0700961 LOG(WARNING) << "Failed to parse a command with ID: " << command_id;
962 if (!command_id.empty())
963 NotifyCommandAborted(command_id, std::move(error));
Anton Muhind07e2062014-10-27 10:53:29 +0400964 continue;
965 }
966
Anton Muhin5191e812014-10-30 17:49:48 +0400967 // TODO(antonm): Properly process cancellation of commands.
Anton Muhin59755522014-11-05 21:30:12 +0400968 if (!command_manager_->FindCommand(command_instance->GetID())) {
969 std::unique_ptr<CommandProxyInterface> cloud_proxy{
970 new CloudCommandProxy(command_instance.get(), this)};
971 command_instance->AddProxy(std::move(cloud_proxy));
Anton Muhin5191e812014-10-30 17:49:48 +0400972 command_manager_->AddCommand(std::move(command_instance));
Anton Muhin59755522014-11-05 21:30:12 +0400973 }
Anton Muhind07e2062014-10-27 10:53:29 +0400974 }
Anton Muhind8d32162014-10-02 20:37:00 +0400975}
976
Anton Muhinb8315622014-11-20 03:17:05 +0400977void DeviceRegistrationInfo::PublishStateUpdates() {
978 VLOG(1) << "PublishStateUpdates";
979 const std::vector<StateChange> state_changes{
980 state_manager_->GetAndClearRecordedStateChanges()};
981 if (state_changes.empty())
982 return;
983
984 std::unique_ptr<base::ListValue> patches{new base::ListValue};
985 for (const auto& state_change : state_changes) {
986 std::unique_ptr<base::DictionaryValue> patch{new base::DictionaryValue};
Anton Muhin76933fd2014-11-21 21:25:18 +0400987 patch->SetString("timeMs",
988 std::to_string(state_change.timestamp.ToJavaTime()));
Anton Muhinb8315622014-11-20 03:17:05 +0400989
990 std::unique_ptr<base::DictionaryValue> changes{new base::DictionaryValue};
991 for (const auto& pair : state_change.changed_properties) {
992 auto value = pair.second->ToJson(nullptr);
993 if (!value) {
994 return;
995 }
Alex Vakulenko61ad4db2015-01-20 10:50:04 -0800996 // The key in |pair.first| is the full property name in format
997 // "package.property_name", so must use DictionaryValue::Set() instead of
998 // DictionaryValue::SetWithoutPathExpansion to recreate the JSON
999 // property tree properly.
1000 changes->Set(pair.first, value.release());
Anton Muhinb8315622014-11-20 03:17:05 +04001001 }
1002 patch->Set("patch", changes.release());
1003
1004 patches->Append(patch.release());
1005 }
1006
1007 base::DictionaryValue body;
Anton Muhin76933fd2014-11-21 21:25:18 +04001008 body.SetString("requestTimeMs",
1009 std::to_string(base::Time::Now().ToJavaTime()));
Anton Muhinb8315622014-11-20 03:17:05 +04001010 body.Set("patches", patches.release());
1011
1012 DoCloudRequest(
1013 chromeos::http::request_type::kPost,
1014 GetDeviceURL("patchState"),
1015 &body,
1016 base::Bind(&IgnoreCloudResult), base::Bind(&IgnoreCloudError));
1017}
1018
Christopher Wileyc900e482015-02-15 15:42:04 -08001019void DeviceRegistrationInfo::SetRegistrationStatus(
1020 RegistrationStatus new_status) {
Christopher Wileyc900e482015-02-15 15:42:04 -08001021 registration_status_ = new_status;
Vitaly Bukafa947062015-04-17 00:41:31 -07001022 if (manager_)
1023 manager_->SetStatus(StatusToString(registration_status_));
1024 VLOG_IF(1, new_status != registration_status_)
1025 << "Changing registration status to " << StatusToString(new_status);
Vitaly Buka620bd7e2015-03-16 01:07:01 -07001026}
1027
1028void DeviceRegistrationInfo::SetDeviceId(const std::string& device_id) {
Vitaly Buka620bd7e2015-03-16 01:07:01 -07001029 device_id_ = device_id;
Vitaly Bukafa947062015-04-17 00:41:31 -07001030 if (manager_)
1031 manager_->SetDeviceId(device_id_);
1032}
1033
1034void DeviceRegistrationInfo::OnConfigChanged() {
1035 if (!manager_)
1036 return;
1037 manager_->SetOemName(config_->oem_name());
1038 manager_->SetModelName(config_->model_name());
1039 manager_->SetModelId(config_->model_id());
1040 manager_->SetName(config_->name());
1041 manager_->SetDescription(config_->description());
1042 manager_->SetLocation(config_->location());
Vitaly Buka2c1029f2015-04-30 22:47:18 -07001043 manager_->SetAnonymousAccessRole(config_->anonymous_access_role());
Christopher Wileyc900e482015-02-15 15:42:04 -08001044}
1045
Alex Vakulenko9ea5a322015-04-17 15:35:34 -07001046void DeviceRegistrationInfo::OnCommandDefsChanged() {
1047 VLOG(1) << "CommandDefinitionChanged notification received";
1048 if (!HaveRegistrationCredentials(nullptr))
1049 return;
1050
1051 UpdateDeviceResource(base::Bind(&base::DoNothing),
1052 base::Bind(&IgnoreCloudError));
1053}
1054
Alex Vakulenko3cb466c2014-04-15 11:36:32 -07001055} // namespace buffet