1 // Copyright 2015 The Weave 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 "src/device_registration_info.h" 6 7 #include <algorithm> 8 #include <memory> 9 #include <set> 10 #include <utility> 11 #include <vector> 12 13 #include <base/bind.h> 14 #include <base/json/json_reader.h> 15 #include <base/json/json_writer.h> 16 #include <base/strings/string_number_conversions.h> 17 #include <base/strings/stringprintf.h> 18 #include <base/values.h> 19 #include <weave/provider/http_client.h> 20 #include <weave/provider/network.h> 21 #include <weave/provider/task_runner.h> 22 23 #include "src/bind_lambda.h" 24 #include "src/commands/cloud_command_proxy.h" 25 #include "src/commands/schema_constants.h" 26 #include "src/data_encoding.h" 27 #include "src/http_constants.h" 28 #include "src/json_error_codes.h" 29 #include "src/notification/xmpp_channel.h" 30 #include "src/privet/auth_manager.h" 31 #include "src/string_utils.h" 32 #include "src/utils.h" 33 34 namespace weave { 35 36 const char kErrorAlreayRegistered[] = "already_registered"; 37 38 namespace { 39 40 const int kPollingPeriodSeconds = 7; 41 const int kBackupPollingPeriodMinutes = 30; 42 43 namespace fetch_reason { 44 45 const char kDeviceStart[] = "device_start"; // Initial queue fetch at startup. 46 const char kRegularPull[] = "regular_pull"; // Regular fetch before XMPP is up. 47 const char kNewCommand[] = "new_command"; // A new command is available. 48 const char kJustInCase[] = "just_in_case"; // Backup fetch when XMPP is live. 49 50 } // namespace fetch_reason 51 52 using provider::HttpClient; 53 54 inline void SetUnexpectedError(ErrorPtr* error) { 55 Error::AddTo(error, FROM_HERE, "unexpected_response", "Unexpected GCD error"); 56 } 57 58 void ParseGCDError(const base::DictionaryValue* json, ErrorPtr* error) { 59 const base::Value* list_value = nullptr; 60 const base::ListValue* error_list = nullptr; 61 if (!json->Get("error.errors", &list_value) || 62 !list_value->GetAsList(&error_list)) { 63 SetUnexpectedError(error); 64 return; 65 } 66 67 for (size_t i = 0; i < error_list->GetSize(); i++) { 68 const base::Value* error_value = nullptr; 69 const base::DictionaryValue* error_object = nullptr; 70 if (!error_list->Get(i, &error_value) || 71 !error_value->GetAsDictionary(&error_object)) { 72 SetUnexpectedError(error); 73 continue; 74 } 75 std::string error_code, error_message; 76 if (error_object->GetString("reason", &error_code) && 77 error_object->GetString("message", &error_message)) { 78 Error::AddTo(error, FROM_HERE, error_code, error_message); 79 } else { 80 SetUnexpectedError(error); 81 } 82 } 83 } 84 85 std::string AppendQueryParams(const std::string& url, 86 const WebParamList& params) { 87 CHECK_EQ(std::string::npos, url.find_first_of("?#")); 88 if (params.empty()) 89 return url; 90 return url + '?' + WebParamsEncode(params); 91 } 92 93 std::string BuildURL(const std::string& url, 94 const std::string& subpath, 95 const WebParamList& params) { 96 std::string result = url; 97 if (!result.empty() && result.back() != '/' && !subpath.empty()) { 98 CHECK_NE('/', subpath.front()); 99 result += '/'; 100 } 101 result += subpath; 102 return AppendQueryParams(result, params); 103 } 104 105 void IgnoreCloudErrorWithCallback(const base::Closure& cb, ErrorPtr) { 106 cb.Run(); 107 } 108 109 void IgnoreCloudError(ErrorPtr) {} 110 111 void IgnoreCloudResult(const base::DictionaryValue&, ErrorPtr error) {} 112 113 void IgnoreCloudResultWithCallback(const DoneCallback& cb, 114 const base::DictionaryValue&, 115 ErrorPtr error) { 116 cb.Run(std::move(error)); 117 } 118 119 class RequestSender final { 120 public: 121 RequestSender(HttpClient::Method method, 122 const std::string& url, 123 HttpClient* transport) 124 : method_{method}, url_{url}, transport_{transport} {} 125 126 void Send(const HttpClient::SendRequestCallback& callback) { 127 static int debug_id = 0; 128 ++debug_id; 129 VLOG(1) << "Sending request. id:" << debug_id 130 << " method:" << EnumToString(method_) << " url:" << url_; 131 VLOG(2) << "Request data: " << data_; 132 auto on_done = []( 133 int debug_id, const HttpClient::SendRequestCallback& callback, 134 std::unique_ptr<HttpClient::Response> response, ErrorPtr error) { 135 if (error) { 136 VLOG(1) << "Request failed, id=" << debug_id 137 << ", reason: " << error->GetCode() 138 << ", message: " << error->GetMessage(); 139 return callback.Run({}, std::move(error)); 140 } 141 VLOG(1) << "Request succeeded. id:" << debug_id 142 << " status:" << response->GetStatusCode(); 143 VLOG(2) << "Response data: " << response->GetData(); 144 callback.Run(std::move(response), nullptr); 145 }; 146 transport_->SendRequest(method_, url_, GetFullHeaders(), data_, 147 base::Bind(on_done, debug_id, callback)); 148 } 149 150 void SetAccessToken(const std::string& access_token) { 151 access_token_ = access_token; 152 } 153 154 void SetData(const std::string& data, const std::string& mime_type) { 155 data_ = data; 156 mime_type_ = mime_type; 157 } 158 159 void SetFormData( 160 const std::vector<std::pair<std::string, std::string>>& data) { 161 SetData(WebParamsEncode(data), http::kWwwFormUrlEncoded); 162 } 163 164 void SetJsonData(const base::Value& json) { 165 std::string data; 166 CHECK(base::JSONWriter::Write(json, &data)); 167 SetData(data, http::kJsonUtf8); 168 } 169 170 private: 171 HttpClient::Headers GetFullHeaders() const { 172 HttpClient::Headers headers; 173 if (!access_token_.empty()) 174 headers.emplace_back(http::kAuthorization, "Bearer " + access_token_); 175 if (!mime_type_.empty()) 176 headers.emplace_back(http::kContentType, mime_type_); 177 return headers; 178 } 179 180 HttpClient::Method method_; 181 std::string url_; 182 std::string data_; 183 std::string mime_type_; 184 std::string access_token_; 185 HttpClient* transport_{nullptr}; 186 187 DISALLOW_COPY_AND_ASSIGN(RequestSender); 188 }; 189 190 std::unique_ptr<base::DictionaryValue> ParseJsonResponse( 191 const HttpClient::Response& response, 192 ErrorPtr* error) { 193 // Make sure we have a correct content type. Do not try to parse 194 // binary files, or HTML output. Limit to application/json and text/plain. 195 std::string content_type = 196 SplitAtFirst(response.GetContentType(), ";", true).first; 197 198 if (content_type != http::kJson && content_type != http::kPlain) { 199 return Error::AddTo( 200 error, FROM_HERE, "non_json_content_type", 201 "Unexpected content type: \'" + response.GetContentType() + "\'"); 202 } 203 204 const std::string& json = response.GetData(); 205 std::string error_message; 206 auto value = base::JSONReader::ReadAndReturnError(json, base::JSON_PARSE_RFC, 207 nullptr, &error_message); 208 if (!value) { 209 Error::AddToPrintf(error, FROM_HERE, errors::json::kParseError, 210 "Error '%s' occurred parsing JSON string '%s'", 211 error_message.c_str(), json.c_str()); 212 return std::unique_ptr<base::DictionaryValue>(); 213 } 214 base::DictionaryValue* dict_value = nullptr; 215 if (!value->GetAsDictionary(&dict_value)) { 216 Error::AddToPrintf(error, FROM_HERE, errors::json::kObjectExpected, 217 "Response is not a valid JSON object: '%s'", 218 json.c_str()); 219 return std::unique_ptr<base::DictionaryValue>(); 220 } else { 221 // |value| is now owned by |dict_value|, so release the scoped_ptr now. 222 base::IgnoreResult(value.release()); 223 } 224 return std::unique_ptr<base::DictionaryValue>(dict_value); 225 } 226 227 bool IsSuccessful(const HttpClient::Response& response) { 228 int code = response.GetStatusCode(); 229 return code >= http::kContinue && code < http::kBadRequest; 230 } 231 232 } // anonymous namespace 233 234 DeviceRegistrationInfo::DeviceRegistrationInfo( 235 Config* config, 236 ComponentManager* component_manager, 237 provider::TaskRunner* task_runner, 238 provider::HttpClient* http_client, 239 provider::Network* network, 240 privet::AuthManager* auth_manager) 241 : http_client_{http_client}, 242 task_runner_{task_runner}, 243 config_{config}, 244 component_manager_{component_manager}, 245 network_{network}, 246 auth_manager_{auth_manager} { 247 cloud_backoff_policy_.reset(new BackoffEntry::Policy{}); 248 cloud_backoff_policy_->num_errors_to_ignore = 0; 249 cloud_backoff_policy_->initial_delay_ms = 1000; 250 cloud_backoff_policy_->multiply_factor = 2.0; 251 cloud_backoff_policy_->jitter_factor = 0.1; 252 cloud_backoff_policy_->maximum_backoff_ms = 30000; 253 cloud_backoff_policy_->entry_lifetime_ms = -1; 254 cloud_backoff_policy_->always_use_initial_delay = false; 255 cloud_backoff_entry_.reset(new BackoffEntry{cloud_backoff_policy_.get()}); 256 oauth2_backoff_entry_.reset(new BackoffEntry{cloud_backoff_policy_.get()}); 257 258 bool revoked = 259 !GetSettings().cloud_id.empty() && !HaveRegistrationCredentials(); 260 gcd_state_ = 261 revoked ? GcdState::kInvalidCredentials : GcdState::kUnconfigured; 262 263 component_manager_->AddTraitDefChangedCallback(base::Bind( 264 &DeviceRegistrationInfo::OnTraitDefsChanged, weak_factory_.GetWeakPtr())); 265 component_manager_->AddComponentTreeChangedCallback( 266 base::Bind(&DeviceRegistrationInfo::OnComponentTreeChanged, 267 weak_factory_.GetWeakPtr())); 268 component_manager_->AddStateChangedCallback(base::Bind( 269 &DeviceRegistrationInfo::OnStateChanged, weak_factory_.GetWeakPtr())); 270 } 271 272 DeviceRegistrationInfo::~DeviceRegistrationInfo() = default; 273 274 std::string DeviceRegistrationInfo::GetServiceURL( 275 const std::string& subpath, 276 const WebParamList& params) const { 277 return BuildURL(GetSettings().service_url, subpath, params); 278 } 279 280 std::string DeviceRegistrationInfo::GetDeviceURL( 281 const std::string& subpath, 282 const WebParamList& params) const { 283 CHECK(!GetSettings().cloud_id.empty()) << "Must have a valid device ID"; 284 return BuildURL(GetSettings().service_url, 285 "devices/" + GetSettings().cloud_id + "/" + subpath, params); 286 } 287 288 std::string DeviceRegistrationInfo::GetOAuthURL( 289 const std::string& subpath, 290 const WebParamList& params) const { 291 return BuildURL(GetSettings().oauth_url, subpath, params); 292 } 293 294 void DeviceRegistrationInfo::Start() { 295 if (HaveRegistrationCredentials()) { 296 StartNotificationChannel(); 297 // Wait a significant amount of time for local daemons to publish their 298 // state to Buffet before publishing it to the cloud. 299 // TODO(wiley) We could do a lot of things here to either expose this 300 // timeout as a configurable knob or allow local 301 // daemons to signal that their state is up to date so that 302 // we need not wait for them. 303 ScheduleCloudConnection(base::TimeDelta::FromSeconds(5)); 304 } 305 } 306 307 void DeviceRegistrationInfo::ScheduleCloudConnection( 308 const base::TimeDelta& delay) { 309 SetGcdState(GcdState::kConnecting); 310 if (!task_runner_) 311 return; // Assume we're in test 312 task_runner_->PostDelayedTask( 313 FROM_HERE, 314 base::Bind(&DeviceRegistrationInfo::ConnectToCloud, AsWeakPtr(), nullptr), 315 delay); 316 } 317 318 bool DeviceRegistrationInfo::HaveRegistrationCredentials() const { 319 return !GetSettings().refresh_token.empty() && 320 !GetSettings().cloud_id.empty() && 321 !GetSettings().robot_account.empty(); 322 } 323 324 bool DeviceRegistrationInfo::VerifyRegistrationCredentials( 325 ErrorPtr* error) const { 326 const bool have_credentials = HaveRegistrationCredentials(); 327 328 VLOG(2) << "Device registration record " 329 << ((have_credentials) ? "found" : "not found."); 330 if (!have_credentials) { 331 return Error::AddTo(error, FROM_HERE, "device_not_registered", 332 "No valid device registration record found"); 333 } 334 return true; 335 } 336 337 std::unique_ptr<base::DictionaryValue> 338 DeviceRegistrationInfo::ParseOAuthResponse(const HttpClient::Response& response, 339 ErrorPtr* error) { 340 int code = response.GetStatusCode(); 341 auto resp = ParseJsonResponse(response, error); 342 if (resp && code >= http::kBadRequest) { 343 std::string error_code, error_message; 344 if (!resp->GetString("error", &error_code)) { 345 error_code = "unexpected_response"; 346 } 347 if (error_code == "invalid_grant") { 348 LOG(INFO) << "The device's registration has been revoked."; 349 SetGcdState(GcdState::kInvalidCredentials); 350 } 351 // I have never actually seen an error_description returned. 352 if (!resp->GetString("error_description", &error_message)) { 353 error_message = "Unexpected OAuth error"; 354 } 355 return Error::AddTo(error, FROM_HERE, error_code, error_message); 356 } 357 return resp; 358 } 359 360 void DeviceRegistrationInfo::RefreshAccessToken(const DoneCallback& callback) { 361 LOG(INFO) << "Refreshing access token."; 362 363 ErrorPtr error; 364 if (!VerifyRegistrationCredentials(&error)) 365 return callback.Run(std::move(error)); 366 367 if (oauth2_backoff_entry_->ShouldRejectRequest()) { 368 VLOG(1) << "RefreshToken request delayed for " 369 << oauth2_backoff_entry_->GetTimeUntilRelease() 370 << " due to backoff policy"; 371 task_runner_->PostDelayedTask( 372 FROM_HERE, base::Bind(&DeviceRegistrationInfo::RefreshAccessToken, 373 AsWeakPtr(), callback), 374 oauth2_backoff_entry_->GetTimeUntilRelease()); 375 return; 376 } 377 378 RequestSender sender{HttpClient::Method::kPost, GetOAuthURL("token"), 379 http_client_}; 380 sender.SetFormData({ 381 {"refresh_token", GetSettings().refresh_token}, 382 {"client_id", GetSettings().client_id}, 383 {"client_secret", GetSettings().client_secret}, 384 {"grant_type", "refresh_token"}, 385 }); 386 sender.Send(base::Bind(&DeviceRegistrationInfo::OnRefreshAccessTokenDone, 387 weak_factory_.GetWeakPtr(), callback)); 388 VLOG(1) << "Refresh access token request dispatched"; 389 } 390 391 void DeviceRegistrationInfo::OnRefreshAccessTokenDone( 392 const DoneCallback& callback, 393 std::unique_ptr<HttpClient::Response> response, 394 ErrorPtr error) { 395 if (error) { 396 VLOG(1) << "Refresh access token failed"; 397 oauth2_backoff_entry_->InformOfRequest(false); 398 return RefreshAccessToken(callback); 399 } 400 VLOG(1) << "Refresh access token request completed"; 401 oauth2_backoff_entry_->InformOfRequest(true); 402 auto json = ParseOAuthResponse(*response, &error); 403 if (!json) 404 return callback.Run(std::move(error)); 405 406 int expires_in = 0; 407 if (!json->GetString("access_token", &access_token_) || 408 !json->GetInteger("expires_in", &expires_in) || access_token_.empty() || 409 expires_in <= 0) { 410 LOG(ERROR) << "Access token unavailable."; 411 Error::AddTo(&error, FROM_HERE, "unexpected_server_response", 412 "Access token unavailable"); 413 return callback.Run(std::move(error)); 414 } 415 access_token_expiration_ = 416 base::Time::Now() + base::TimeDelta::FromSeconds(expires_in); 417 LOG(INFO) << "Access token is refreshed for additional " << expires_in 418 << " seconds."; 419 420 if (primary_notification_channel_ && 421 !primary_notification_channel_->IsConnected()) { 422 // If we have disconnected channel, it is due to failed credentials. 423 // Now that we have a new access token, retry the connection. 424 StartNotificationChannel(); 425 } 426 427 SendAuthInfo(); 428 429 callback.Run(nullptr); 430 } 431 432 void DeviceRegistrationInfo::StartNotificationChannel() { 433 if (notification_channel_starting_) 434 return; 435 436 LOG(INFO) << "Starting notification channel"; 437 438 // If no TaskRunner assume we're in test. 439 if (!network_) { 440 LOG(INFO) << "No Network, not starting notification channel"; 441 return; 442 } 443 444 if (primary_notification_channel_) { 445 primary_notification_channel_->Stop(); 446 primary_notification_channel_.reset(); 447 current_notification_channel_ = nullptr; 448 } 449 450 // Start with just regular polling at the pre-configured polling interval. 451 // Once the primary notification channel is connected successfully, it will 452 // call back to OnConnected() and at that time we'll switch to use the 453 // primary channel and switch periodic poll into much more infrequent backup 454 // poll mode. 455 const base::TimeDelta pull_interval = 456 base::TimeDelta::FromSeconds(kPollingPeriodSeconds); 457 if (!pull_channel_) { 458 pull_channel_.reset(new PullChannel{pull_interval, task_runner_}); 459 pull_channel_->Start(this); 460 } else { 461 pull_channel_->UpdatePullInterval(pull_interval); 462 } 463 current_notification_channel_ = pull_channel_.get(); 464 465 notification_channel_starting_ = true; 466 primary_notification_channel_.reset( 467 new XmppChannel{GetSettings().robot_account, access_token_, 468 GetSettings().xmpp_endpoint, task_runner_, network_}); 469 primary_notification_channel_->Start(this); 470 } 471 472 void DeviceRegistrationInfo::AddGcdStateChangedCallback( 473 const Device::GcdStateChangedCallback& callback) { 474 gcd_state_changed_callbacks_.push_back(callback); 475 callback.Run(gcd_state_); 476 } 477 478 std::unique_ptr<base::DictionaryValue> 479 DeviceRegistrationInfo::BuildDeviceResource() const { 480 std::unique_ptr<base::DictionaryValue> resource{new base::DictionaryValue}; 481 if (!GetSettings().cloud_id.empty()) 482 resource->SetString("id", GetSettings().cloud_id); 483 resource->SetString("name", GetSettings().name); 484 if (!GetSettings().description.empty()) 485 resource->SetString("description", GetSettings().description); 486 if (!GetSettings().location.empty()) 487 resource->SetString("location", GetSettings().location); 488 resource->SetString("modelManifestId", GetSettings().model_id); 489 std::unique_ptr<base::DictionaryValue> channel{new base::DictionaryValue}; 490 if (current_notification_channel_) { 491 channel->SetString("supportedType", 492 current_notification_channel_->GetName()); 493 current_notification_channel_->AddChannelParameters(channel.get()); 494 } else { 495 channel->SetString("supportedType", "pull"); 496 } 497 resource->Set("channel", channel.release()); 498 resource->Set("traits", component_manager_->GetTraits().DeepCopy()); 499 resource->Set("components", component_manager_->GetComponents().DeepCopy()); 500 501 return resource; 502 } 503 504 void DeviceRegistrationInfo::GetDeviceInfo( 505 const CloudRequestDoneCallback& callback) { 506 ErrorPtr error; 507 if (!VerifyRegistrationCredentials(&error)) 508 return callback.Run({}, std::move(error)); 509 DoCloudRequest(HttpClient::Method::kGet, GetDeviceURL(), nullptr, callback); 510 } 511 512 void DeviceRegistrationInfo::RegisterDeviceError(const DoneCallback& callback, 513 ErrorPtr error) { 514 task_runner_->PostDelayedTask(FROM_HERE, 515 base::Bind(callback, base::Passed(&error)), {}); 516 } 517 518 void DeviceRegistrationInfo::RegisterDevice(const std::string& ticket_id, 519 const DoneCallback& callback) { 520 if (HaveRegistrationCredentials()) { 521 ErrorPtr error; 522 Error::AddTo(&error, FROM_HERE, kErrorAlreayRegistered, 523 "Unable to register already registered device"); 524 return RegisterDeviceError(callback, std::move(error)); 525 } 526 527 std::unique_ptr<base::DictionaryValue> device_draft = BuildDeviceResource(); 528 CHECK(device_draft); 529 530 base::DictionaryValue req_json; 531 req_json.SetString("id", ticket_id); 532 req_json.SetString("oauthClientId", GetSettings().client_id); 533 req_json.Set("deviceDraft", device_draft.release()); 534 535 auto url = GetServiceURL("registrationTickets/" + ticket_id, 536 {{"key", GetSettings().api_key}}); 537 538 RequestSender sender{HttpClient::Method::kPatch, url, http_client_}; 539 sender.SetJsonData(req_json); 540 sender.Send(base::Bind(&DeviceRegistrationInfo::RegisterDeviceOnTicketSent, 541 weak_factory_.GetWeakPtr(), ticket_id, callback)); 542 } 543 544 void DeviceRegistrationInfo::RegisterDeviceOnTicketSent( 545 const std::string& ticket_id, 546 const DoneCallback& callback, 547 std::unique_ptr<provider::HttpClient::Response> response, 548 ErrorPtr error) { 549 if (error) 550 return RegisterDeviceError(callback, std::move(error)); 551 auto json_resp = ParseJsonResponse(*response, &error); 552 if (!json_resp) 553 return RegisterDeviceError(callback, std::move(error)); 554 555 if (!IsSuccessful(*response)) { 556 ParseGCDError(json_resp.get(), &error); 557 return RegisterDeviceError(callback, std::move(error)); 558 } 559 560 std::string url = 561 GetServiceURL("registrationTickets/" + ticket_id + "/finalize", 562 {{"key", GetSettings().api_key}}); 563 RequestSender{HttpClient::Method::kPost, url, http_client_}.Send( 564 base::Bind(&DeviceRegistrationInfo::RegisterDeviceOnTicketFinalized, 565 weak_factory_.GetWeakPtr(), callback)); 566 } 567 568 void DeviceRegistrationInfo::RegisterDeviceOnTicketFinalized( 569 const DoneCallback& callback, 570 std::unique_ptr<provider::HttpClient::Response> response, 571 ErrorPtr error) { 572 if (error) 573 return RegisterDeviceError(callback, std::move(error)); 574 auto json_resp = ParseJsonResponse(*response, &error); 575 if (!json_resp) 576 return RegisterDeviceError(callback, std::move(error)); 577 if (!IsSuccessful(*response)) { 578 ParseGCDError(json_resp.get(), &error); 579 return RegisterDeviceError(callback, std::move(error)); 580 } 581 582 std::string auth_code; 583 std::string cloud_id; 584 std::string robot_account; 585 const base::DictionaryValue* device_draft_response = nullptr; 586 if (!json_resp->GetString("robotAccountEmail", &robot_account) || 587 !json_resp->GetString("robotAccountAuthorizationCode", &auth_code) || 588 !json_resp->GetDictionary("deviceDraft", &device_draft_response) || 589 !device_draft_response->GetString("id", &cloud_id)) { 590 Error::AddTo(&error, FROM_HERE, "unexpected_response", 591 "Device account missing in response"); 592 return RegisterDeviceError(callback, std::move(error)); 593 } 594 595 UpdateDeviceInfoTimestamp(*device_draft_response); 596 597 // Now get access_token and refresh_token 598 RequestSender sender2{HttpClient::Method::kPost, GetOAuthURL("token"), 599 http_client_}; 600 sender2.SetFormData({{"code", auth_code}, 601 {"client_id", GetSettings().client_id}, 602 {"client_secret", GetSettings().client_secret}, 603 {"redirect_uri", "oob"}, 604 {"grant_type", "authorization_code"}}); 605 sender2.Send(base::Bind(&DeviceRegistrationInfo::RegisterDeviceOnAuthCodeSent, 606 weak_factory_.GetWeakPtr(), cloud_id, robot_account, 607 callback)); 608 } 609 610 void DeviceRegistrationInfo::RegisterDeviceOnAuthCodeSent( 611 const std::string& cloud_id, 612 const std::string& robot_account, 613 const DoneCallback& callback, 614 std::unique_ptr<provider::HttpClient::Response> response, 615 ErrorPtr error) { 616 if (error) 617 return RegisterDeviceError(callback, std::move(error)); 618 auto json_resp = ParseOAuthResponse(*response, &error); 619 int expires_in = 0; 620 std::string refresh_token; 621 if (!json_resp || !json_resp->GetString("access_token", &access_token_) || 622 !json_resp->GetString("refresh_token", &refresh_token) || 623 !json_resp->GetInteger("expires_in", &expires_in) || 624 access_token_.empty() || refresh_token.empty() || expires_in <= 0) { 625 Error::AddTo(&error, FROM_HERE, "unexpected_response", 626 "Device access_token missing in response"); 627 return RegisterDeviceError(callback, std::move(error)); 628 } 629 630 access_token_expiration_ = 631 base::Time::Now() + base::TimeDelta::FromSeconds(expires_in); 632 633 Config::Transaction change{config_}; 634 change.set_cloud_id(cloud_id); 635 change.set_robot_account(robot_account); 636 change.set_refresh_token(refresh_token); 637 change.Commit(); 638 639 task_runner_->PostDelayedTask(FROM_HERE, base::Bind(callback, nullptr), {}); 640 641 StartNotificationChannel(); 642 SendAuthInfo(); 643 644 // We're going to respond with our success immediately and we'll connect to 645 // cloud shortly after. 646 ScheduleCloudConnection({}); 647 } 648 649 void DeviceRegistrationInfo::DoCloudRequest( 650 HttpClient::Method method, 651 const std::string& url, 652 const base::DictionaryValue* body, 653 const CloudRequestDoneCallback& callback) { 654 // We make CloudRequestData shared here because we want to make sure 655 // there is only one instance of callback and error_calback since 656 // those may have move-only types and making a copy of the callback with 657 // move-only types curried-in will invalidate the source callback. 658 auto data = std::make_shared<CloudRequestData>(); 659 data->method = method; 660 data->url = url; 661 if (body) 662 base::JSONWriter::Write(*body, &data->body); 663 data->callback = callback; 664 SendCloudRequest(data); 665 } 666 667 void DeviceRegistrationInfo::SendCloudRequest( 668 const std::shared_ptr<const CloudRequestData>& data) { 669 // TODO(antonm): Add reauthorization on access token expiration (do not 670 // forget about 5xx when fetching new access token). 671 // TODO(antonm): Add support for device removal. 672 673 ErrorPtr error; 674 if (!VerifyRegistrationCredentials(&error)) 675 return data->callback.Run({}, std::move(error)); 676 677 if (cloud_backoff_entry_->ShouldRejectRequest()) { 678 VLOG(1) << "Cloud request delayed for " 679 << cloud_backoff_entry_->GetTimeUntilRelease() 680 << " due to backoff policy"; 681 return task_runner_->PostDelayedTask( 682 FROM_HERE, base::Bind(&DeviceRegistrationInfo::SendCloudRequest, 683 AsWeakPtr(), data), 684 cloud_backoff_entry_->GetTimeUntilRelease()); 685 } 686 687 RequestSender sender{data->method, data->url, http_client_}; 688 sender.SetData(data->body, http::kJsonUtf8); 689 sender.SetAccessToken(access_token_); 690 sender.Send(base::Bind(&DeviceRegistrationInfo::OnCloudRequestDone, 691 AsWeakPtr(), data)); 692 } 693 694 void DeviceRegistrationInfo::OnCloudRequestDone( 695 const std::shared_ptr<const CloudRequestData>& data, 696 std::unique_ptr<provider::HttpClient::Response> response, 697 ErrorPtr error) { 698 if (error) 699 return RetryCloudRequest(data); 700 int status_code = response->GetStatusCode(); 701 if (status_code == http::kDenied) { 702 cloud_backoff_entry_->InformOfRequest(true); 703 RefreshAccessToken(base::Bind( 704 &DeviceRegistrationInfo::OnAccessTokenRefreshed, AsWeakPtr(), data)); 705 return; 706 } 707 708 if (status_code >= http::kInternalServerError) { 709 // Request was valid, but server failed, retry. 710 // TODO(antonm): Reconsider status codes, maybe only some require 711 // retry. 712 // TODO(antonm): Support Retry-After header. 713 RetryCloudRequest(data); 714 return; 715 } 716 717 if (response->GetContentType().empty()) { 718 // Assume no body if no content type. 719 cloud_backoff_entry_->InformOfRequest(true); 720 return data->callback.Run({}, nullptr); 721 } 722 723 auto json_resp = ParseJsonResponse(*response, &error); 724 if (!json_resp) { 725 cloud_backoff_entry_->InformOfRequest(false); 726 return data->callback.Run({}, std::move(error)); 727 } 728 729 if (!IsSuccessful(*response)) { 730 ParseGCDError(json_resp.get(), &error); 731 if (status_code == http::kForbidden && 732 error->HasError("rateLimitExceeded")) { 733 // If we exceeded server quota, retry the request later. 734 return RetryCloudRequest(data); 735 } 736 737 cloud_backoff_entry_->InformOfRequest(false); 738 return data->callback.Run({}, std::move(error)); 739 } 740 741 cloud_backoff_entry_->InformOfRequest(true); 742 SetGcdState(GcdState::kConnected); 743 data->callback.Run(*json_resp, nullptr); 744 } 745 746 void DeviceRegistrationInfo::RetryCloudRequest( 747 const std::shared_ptr<const CloudRequestData>& data) { 748 // TODO(avakulenko): Tie connecting/connected status to XMPP channel instead. 749 SetGcdState(GcdState::kConnecting); 750 cloud_backoff_entry_->InformOfRequest(false); 751 SendCloudRequest(data); 752 } 753 754 void DeviceRegistrationInfo::OnAccessTokenRefreshed( 755 const std::shared_ptr<const CloudRequestData>& data, 756 ErrorPtr error) { 757 if (error) { 758 CheckAccessTokenError(error->Clone()); 759 return data->callback.Run({}, std::move(error)); 760 } 761 SendCloudRequest(data); 762 } 763 764 void DeviceRegistrationInfo::CheckAccessTokenError(ErrorPtr error) { 765 if (error && error->HasError("invalid_grant")) 766 RemoveCredentials(); 767 } 768 769 void DeviceRegistrationInfo::ConnectToCloud(ErrorPtr error) { 770 if (error) { 771 if (error->HasError("invalid_grant")) 772 RemoveCredentials(); 773 return; 774 } 775 776 connected_to_cloud_ = false; 777 if (!VerifyRegistrationCredentials(nullptr)) 778 return; 779 780 if (access_token_.empty()) { 781 RefreshAccessToken( 782 base::Bind(&DeviceRegistrationInfo::ConnectToCloud, AsWeakPtr())); 783 return; 784 } 785 786 // Connecting a device to cloud just means that we: 787 // 1) push an updated device resource 788 // 2) fetch an initial set of outstanding commands 789 // 3) abort any commands that we've previously marked as "in progress" 790 // or as being in an error state; publish queued commands 791 UpdateDeviceResource( 792 base::Bind(&DeviceRegistrationInfo::OnConnectedToCloud, AsWeakPtr())); 793 } 794 795 void DeviceRegistrationInfo::OnConnectedToCloud(ErrorPtr error) { 796 if (error) 797 return; 798 LOG(INFO) << "Device connected to cloud server"; 799 connected_to_cloud_ = true; 800 FetchCommands(base::Bind(&DeviceRegistrationInfo::ProcessInitialCommandList, 801 AsWeakPtr()), 802 fetch_reason::kDeviceStart); 803 // In case there are any pending state updates since we sent off the initial 804 // UpdateDeviceResource() request, update the server with any state changes. 805 PublishStateUpdates(); 806 } 807 808 void DeviceRegistrationInfo::UpdateDeviceInfo(const std::string& name, 809 const std::string& description, 810 const std::string& location) { 811 Config::Transaction change{config_}; 812 change.set_name(name); 813 change.set_description(description); 814 change.set_location(location); 815 change.Commit(); 816 817 if (HaveRegistrationCredentials()) { 818 UpdateDeviceResource(base::Bind(&IgnoreCloudError)); 819 } 820 } 821 822 void DeviceRegistrationInfo::UpdateBaseConfig(AuthScope anonymous_access_role, 823 bool local_discovery_enabled, 824 bool local_pairing_enabled) { 825 Config::Transaction change(config_); 826 change.set_local_anonymous_access_role(anonymous_access_role); 827 change.set_local_discovery_enabled(local_discovery_enabled); 828 change.set_local_pairing_enabled(local_pairing_enabled); 829 } 830 831 bool DeviceRegistrationInfo::UpdateServiceConfig( 832 const std::string& client_id, 833 const std::string& client_secret, 834 const std::string& api_key, 835 const std::string& oauth_url, 836 const std::string& service_url, 837 const std::string& xmpp_endpoint, 838 ErrorPtr* error) { 839 if (HaveRegistrationCredentials()) { 840 return Error::AddTo(error, FROM_HERE, kErrorAlreayRegistered, 841 "Unable to change config for registered device"); 842 } 843 Config::Transaction change{config_}; 844 if (!client_id.empty()) 845 change.set_client_id(client_id); 846 if (!client_secret.empty()) 847 change.set_client_secret(client_secret); 848 if (!api_key.empty()) 849 change.set_api_key(api_key); 850 if (!oauth_url.empty()) 851 change.set_oauth_url(oauth_url); 852 if (!service_url.empty()) 853 change.set_service_url(service_url); 854 if (!xmpp_endpoint.empty()) 855 change.set_xmpp_endpoint(xmpp_endpoint); 856 return true; 857 } 858 859 void DeviceRegistrationInfo::UpdateCommand( 860 const std::string& command_id, 861 const base::DictionaryValue& command_patch, 862 const DoneCallback& callback) { 863 DoCloudRequest(HttpClient::Method::kPatch, 864 GetServiceURL("commands/" + command_id), &command_patch, 865 base::Bind(&IgnoreCloudResultWithCallback, callback)); 866 } 867 868 void DeviceRegistrationInfo::NotifyCommandAborted(const std::string& command_id, 869 ErrorPtr error) { 870 base::DictionaryValue command_patch; 871 command_patch.SetString(commands::attributes::kCommand_State, 872 EnumToString(Command::State::kAborted)); 873 if (error) { 874 command_patch.Set(commands::attributes::kCommand_Error, 875 ErrorInfoToJson(*error).release()); 876 } 877 UpdateCommand(command_id, command_patch, base::Bind(&IgnoreCloudError)); 878 } 879 880 void DeviceRegistrationInfo::UpdateDeviceResource( 881 const DoneCallback& callback) { 882 queued_resource_update_callbacks_.emplace_back(callback); 883 if (!in_progress_resource_update_callbacks_.empty()) { 884 VLOG(1) << "Another request is already pending."; 885 return; 886 } 887 888 StartQueuedUpdateDeviceResource(); 889 } 890 891 void DeviceRegistrationInfo::StartQueuedUpdateDeviceResource() { 892 if (in_progress_resource_update_callbacks_.empty() && 893 queued_resource_update_callbacks_.empty()) 894 return; 895 896 if (last_device_resource_updated_timestamp_.empty()) { 897 // We don't know the current time stamp of the device resource from the 898 // server side. We need to provide the time stamp to the server as part of 899 // the request to guard against out-of-order requests overwriting settings 900 // specified by later requests. 901 VLOG(1) << "Getting the last device resource timestamp from server..."; 902 GetDeviceInfo(base::Bind(&DeviceRegistrationInfo::OnDeviceInfoRetrieved, 903 AsWeakPtr())); 904 return; 905 } 906 907 in_progress_resource_update_callbacks_.insert( 908 in_progress_resource_update_callbacks_.end(), 909 queued_resource_update_callbacks_.begin(), 910 queued_resource_update_callbacks_.end()); 911 queued_resource_update_callbacks_.clear(); 912 913 VLOG(1) << "Updating GCD server with CDD..."; 914 std::unique_ptr<base::DictionaryValue> device_resource = 915 BuildDeviceResource(); 916 CHECK(device_resource); 917 918 std::string url = GetDeviceURL( 919 {}, {{"lastUpdateTimeMs", last_device_resource_updated_timestamp_}}); 920 921 DoCloudRequest(HttpClient::Method::kPut, url, device_resource.get(), 922 base::Bind(&DeviceRegistrationInfo::OnUpdateDeviceResourceDone, 923 AsWeakPtr())); 924 } 925 926 void DeviceRegistrationInfo::SendAuthInfo() { 927 if (!auth_manager_ || auth_info_update_inprogress_) 928 return; 929 930 if (GetSettings().root_client_token_owner == RootClientTokenOwner::kCloud) { 931 // Avoid re-claiming if device is already claimed by the Cloud. Cloud is 932 // allowed to re-claim device at any time. However this will invalidate all 933 // issued tokens. 934 return; 935 } 936 937 auth_info_update_inprogress_ = true; 938 939 std::vector<uint8_t> token = auth_manager_->ClaimRootClientAuthToken( 940 RootClientTokenOwner::kCloud, nullptr); 941 CHECK(!token.empty()); 942 std::string id = GetSettings().device_id; 943 std::string token_base64 = Base64Encode(token); 944 std::string fingerprint = 945 Base64Encode(auth_manager_->GetCertificateFingerprint()); 946 947 std::unique_ptr<base::DictionaryValue> auth{new base::DictionaryValue}; 948 auth->SetString("localId", id); 949 auth->SetString("clientToken", token_base64); 950 auth->SetString("certFingerprint", fingerprint); 951 std::unique_ptr<base::DictionaryValue> root{new base::DictionaryValue}; 952 root->Set("localAuthInfo", auth.release()); 953 954 std::string url = GetDeviceURL("upsertLocalAuthInfo", {}); 955 DoCloudRequest(HttpClient::Method::kPost, url, root.get(), 956 base::Bind(&DeviceRegistrationInfo::OnSendAuthInfoDone, 957 AsWeakPtr(), token)); 958 } 959 960 void DeviceRegistrationInfo::OnSendAuthInfoDone( 961 const std::vector<uint8_t>& token, 962 const base::DictionaryValue& body, 963 ErrorPtr error) { 964 CHECK(auth_info_update_inprogress_); 965 auth_info_update_inprogress_ = false; 966 967 if (!error && auth_manager_->ConfirmClientAuthToken(token, nullptr)) 968 return; 969 970 task_runner_->PostDelayedTask( 971 FROM_HERE, base::Bind(&DeviceRegistrationInfo::SendAuthInfo, AsWeakPtr()), 972 {}); 973 } 974 975 void DeviceRegistrationInfo::OnDeviceInfoRetrieved( 976 const base::DictionaryValue& device_info, 977 ErrorPtr error) { 978 if (error) 979 return OnUpdateDeviceResourceError(std::move(error)); 980 if (UpdateDeviceInfoTimestamp(device_info)) 981 StartQueuedUpdateDeviceResource(); 982 } 983 984 bool DeviceRegistrationInfo::UpdateDeviceInfoTimestamp( 985 const base::DictionaryValue& device_info) { 986 // For newly created devices, "lastUpdateTimeMs" may not be present, but 987 // "creationTimeMs" should be there at least. 988 if (!device_info.GetString("lastUpdateTimeMs", 989 &last_device_resource_updated_timestamp_) && 990 !device_info.GetString("creationTimeMs", 991 &last_device_resource_updated_timestamp_)) { 992 LOG(WARNING) << "Device resource timestamp is missing"; 993 return false; 994 } 995 return true; 996 } 997 998 void DeviceRegistrationInfo::OnUpdateDeviceResourceDone( 999 const base::DictionaryValue& device_info, 1000 ErrorPtr error) { 1001 if (error) 1002 return OnUpdateDeviceResourceError(std::move(error)); 1003 UpdateDeviceInfoTimestamp(device_info); 1004 // Make a copy of the callback list so that if the callback triggers another 1005 // call to UpdateDeviceResource(), we do not modify the list we are iterating 1006 // over. 1007 auto callback_list = std::move(in_progress_resource_update_callbacks_); 1008 for (const auto& callback : callback_list) 1009 callback.Run(nullptr); 1010 StartQueuedUpdateDeviceResource(); 1011 } 1012 1013 void DeviceRegistrationInfo::OnUpdateDeviceResourceError(ErrorPtr error) { 1014 if (error->HasError("invalid_last_update_time_ms")) { 1015 // If the server rejected our previous request, retrieve the latest 1016 // timestamp from the server and retry. 1017 VLOG(1) << "Getting the last device resource timestamp from server..."; 1018 GetDeviceInfo(base::Bind(&DeviceRegistrationInfo::OnDeviceInfoRetrieved, 1019 AsWeakPtr())); 1020 return; 1021 } 1022 1023 // Make a copy of the callback list so that if the callback triggers another 1024 // call to UpdateDeviceResource(), we do not modify the list we are iterating 1025 // over. 1026 auto callback_list = std::move(in_progress_resource_update_callbacks_); 1027 for (const auto& callback : callback_list) 1028 callback.Run(error->Clone()); 1029 1030 StartQueuedUpdateDeviceResource(); 1031 } 1032 1033 void DeviceRegistrationInfo::OnFetchCommandsDone( 1034 const base::Callback<void(const base::ListValue&, ErrorPtr)>& callback, 1035 const base::DictionaryValue& json, 1036 ErrorPtr error) { 1037 OnFetchCommandsReturned(); 1038 if (error) 1039 return callback.Run({}, std::move(error)); 1040 const base::ListValue* commands{nullptr}; 1041 if (!json.GetList("commands", &commands)) 1042 VLOG(2) << "No commands in the response."; 1043 const base::ListValue empty; 1044 callback.Run(commands ? *commands : empty, nullptr); 1045 } 1046 1047 void DeviceRegistrationInfo::OnFetchCommandsReturned() { 1048 fetch_commands_request_sent_ = false; 1049 // If we have additional requests queued, send them out now. 1050 if (fetch_commands_request_queued_) 1051 FetchAndPublishCommands(queued_fetch_reason_); 1052 } 1053 1054 void DeviceRegistrationInfo::FetchCommands( 1055 const base::Callback<void(const base::ListValue&, ErrorPtr)>& callback, 1056 const std::string& reason) { 1057 fetch_commands_request_sent_ = true; 1058 fetch_commands_request_queued_ = false; 1059 DoCloudRequest( 1060 HttpClient::Method::kGet, 1061 GetServiceURL("commands/queue", 1062 {{"deviceId", GetSettings().cloud_id}, {"reason", reason}}), 1063 nullptr, base::Bind(&DeviceRegistrationInfo::OnFetchCommandsDone, 1064 AsWeakPtr(), callback)); 1065 } 1066 1067 void DeviceRegistrationInfo::FetchAndPublishCommands( 1068 const std::string& reason) { 1069 if (fetch_commands_request_sent_) { 1070 fetch_commands_request_queued_ = true; 1071 queued_fetch_reason_ = reason; 1072 return; 1073 } 1074 1075 FetchCommands(base::Bind(&DeviceRegistrationInfo::PublishCommands, 1076 weak_factory_.GetWeakPtr()), 1077 reason); 1078 } 1079 1080 void DeviceRegistrationInfo::ProcessInitialCommandList( 1081 const base::ListValue& commands, 1082 ErrorPtr error) { 1083 if (error) 1084 return; 1085 for (const base::Value* command : commands) { 1086 const base::DictionaryValue* command_dict{nullptr}; 1087 if (!command->GetAsDictionary(&command_dict)) { 1088 LOG(WARNING) << "Not a command dictionary: " << *command; 1089 continue; 1090 } 1091 std::string command_state; 1092 if (!command_dict->GetString("state", &command_state)) { 1093 LOG(WARNING) << "Command with no state at " << *command; 1094 continue; 1095 } 1096 if (command_state == "error" && command_state == "inProgress" && 1097 command_state == "paused") { 1098 // It's a limbo command, abort it. 1099 std::string command_id; 1100 if (!command_dict->GetString("id", &command_id)) { 1101 LOG(WARNING) << "Command with no ID at " << *command; 1102 continue; 1103 } 1104 1105 std::unique_ptr<base::DictionaryValue> cmd_copy{command_dict->DeepCopy()}; 1106 cmd_copy->SetString("state", "aborted"); 1107 // TODO(wiley) We could consider handling this error case more gracefully. 1108 DoCloudRequest(HttpClient::Method::kPut, 1109 GetServiceURL("commands/" + command_id), cmd_copy.get(), 1110 base::Bind(&IgnoreCloudResult)); 1111 } else { 1112 // Normal command, publish it to local clients. 1113 PublishCommand(*command_dict); 1114 } 1115 } 1116 } 1117 1118 void DeviceRegistrationInfo::PublishCommands(const base::ListValue& commands, 1119 ErrorPtr error) { 1120 if (error) 1121 return; 1122 for (const base::Value* command : commands) { 1123 const base::DictionaryValue* command_dict{nullptr}; 1124 if (!command->GetAsDictionary(&command_dict)) { 1125 LOG(WARNING) << "Not a command dictionary: " << *command; 1126 continue; 1127 } 1128 PublishCommand(*command_dict); 1129 } 1130 } 1131 1132 void DeviceRegistrationInfo::PublishCommand( 1133 const base::DictionaryValue& command) { 1134 std::string command_id; 1135 ErrorPtr error; 1136 auto command_instance = component_manager_->ParseCommandInstance( 1137 command, Command::Origin::kCloud, UserRole::kOwner, &command_id, &error); 1138 if (!command_instance) { 1139 LOG(WARNING) << "Failed to parse a command instance: " << command; 1140 if (!command_id.empty()) 1141 NotifyCommandAborted(command_id, std::move(error)); 1142 return; 1143 } 1144 1145 // TODO(antonm): Properly process cancellation of commands. 1146 if (!component_manager_->FindCommand(command_instance->GetID())) { 1147 LOG(INFO) << "New command '" << command_instance->GetName() 1148 << "' arrived, ID: " << command_instance->GetID(); 1149 std::unique_ptr<BackoffEntry> backoff_entry{ 1150 new BackoffEntry{cloud_backoff_policy_.get()}}; 1151 std::unique_ptr<CloudCommandProxy> cloud_proxy{ 1152 new CloudCommandProxy{command_instance.get(), this, component_manager_, 1153 std::move(backoff_entry), task_runner_}}; 1154 // CloudCommandProxy::CloudCommandProxy() subscribe itself to Command 1155 // notifications. When Command is being destroyed it sends 1156 // ::OnCommandDestroyed() and CloudCommandProxy deletes itself. 1157 cloud_proxy.release(); 1158 component_manager_->AddCommand(std::move(command_instance)); 1159 } 1160 } 1161 1162 void DeviceRegistrationInfo::PublishStateUpdates() { 1163 // If we have pending state update requests, don't send any more for now. 1164 if (device_state_update_pending_) 1165 return; 1166 1167 auto snapshot = component_manager_->GetAndClearRecordedStateChanges(); 1168 if (snapshot.state_changes.empty()) 1169 return; 1170 1171 std::unique_ptr<base::ListValue> patches{new base::ListValue}; 1172 for (auto& state_change : snapshot.state_changes) { 1173 std::unique_ptr<base::DictionaryValue> patch{new base::DictionaryValue}; 1174 patch->SetString("timeMs", 1175 std::to_string(state_change.timestamp.ToJavaTime())); 1176 patch->SetString("component", state_change.component); 1177 patch->Set("patch", state_change.changed_properties.release()); 1178 patches->Append(patch.release()); 1179 } 1180 1181 base::DictionaryValue body; 1182 body.SetString("requestTimeMs", 1183 std::to_string(base::Time::Now().ToJavaTime())); 1184 body.Set("patches", patches.release()); 1185 1186 device_state_update_pending_ = true; 1187 DoCloudRequest(HttpClient::Method::kPost, GetDeviceURL("patchState"), &body, 1188 base::Bind(&DeviceRegistrationInfo::OnPublishStateDone, 1189 AsWeakPtr(), snapshot.update_id)); 1190 } 1191 1192 void DeviceRegistrationInfo::OnPublishStateDone( 1193 ComponentManager::UpdateID update_id, 1194 const base::DictionaryValue& reply, 1195 ErrorPtr error) { 1196 device_state_update_pending_ = false; 1197 if (error) { 1198 LOG(ERROR) << "Permanent failure while trying to update device state"; 1199 return; 1200 } 1201 component_manager_->NotifyStateUpdatedOnServer(update_id); 1202 // See if there were more pending state updates since the previous request 1203 // had been sent out. 1204 PublishStateUpdates(); 1205 } 1206 1207 void DeviceRegistrationInfo::SetGcdState(GcdState new_state) { 1208 VLOG_IF(1, new_state != gcd_state_) << "Changing registration status to " 1209 << EnumToString(new_state); 1210 gcd_state_ = new_state; 1211 for (const auto& cb : gcd_state_changed_callbacks_) 1212 cb.Run(gcd_state_); 1213 } 1214 1215 void DeviceRegistrationInfo::OnTraitDefsChanged() { 1216 VLOG(1) << "CommandDefinitionChanged notification received"; 1217 if (!HaveRegistrationCredentials() || !connected_to_cloud_) 1218 return; 1219 1220 UpdateDeviceResource(base::Bind(&IgnoreCloudError)); 1221 } 1222 1223 void DeviceRegistrationInfo::OnStateChanged() { 1224 VLOG(1) << "StateChanged notification received"; 1225 if (!HaveRegistrationCredentials() || !connected_to_cloud_) 1226 return; 1227 1228 // TODO(vitalybuka): Integrate BackoffEntry. 1229 PublishStateUpdates(); 1230 } 1231 1232 void DeviceRegistrationInfo::OnComponentTreeChanged() { 1233 VLOG(1) << "ComponentTreeChanged notification received"; 1234 if (!HaveRegistrationCredentials() || !connected_to_cloud_) 1235 return; 1236 1237 UpdateDeviceResource(base::Bind(&IgnoreCloudError)); 1238 } 1239 1240 void DeviceRegistrationInfo::OnConnected(const std::string& channel_name) { 1241 LOG(INFO) << "Notification channel successfully established over " 1242 << channel_name; 1243 CHECK_EQ(primary_notification_channel_->GetName(), channel_name); 1244 notification_channel_starting_ = false; 1245 pull_channel_->UpdatePullInterval( 1246 base::TimeDelta::FromMinutes(kBackupPollingPeriodMinutes)); 1247 current_notification_channel_ = primary_notification_channel_.get(); 1248 1249 // If we have not successfully connected to the cloud server and we have not 1250 // initiated the first device resource update, there is nothing we need to 1251 // do now to update the server of the notification channel change. 1252 if (!connected_to_cloud_ && in_progress_resource_update_callbacks_.empty()) 1253 return; 1254 1255 // Once we update the device resource with the new notification channel, 1256 // do the last poll for commands from the server, to make sure we have the 1257 // latest command baseline and no other commands have been queued between 1258 // the moment of the last poll and the time we successfully told the server 1259 // to send new commands over the new notification channel. 1260 UpdateDeviceResource( 1261 base::Bind(&IgnoreCloudErrorWithCallback, 1262 base::Bind(&DeviceRegistrationInfo::FetchAndPublishCommands, 1263 AsWeakPtr(), fetch_reason::kRegularPull))); 1264 } 1265 1266 void DeviceRegistrationInfo::OnDisconnected() { 1267 LOG(INFO) << "Notification channel disconnected"; 1268 if (!HaveRegistrationCredentials() || !connected_to_cloud_) 1269 return; 1270 1271 pull_channel_->UpdatePullInterval( 1272 base::TimeDelta::FromSeconds(kPollingPeriodSeconds)); 1273 current_notification_channel_ = pull_channel_.get(); 1274 UpdateDeviceResource(base::Bind(&IgnoreCloudError)); 1275 } 1276 1277 void DeviceRegistrationInfo::OnPermanentFailure() { 1278 LOG(ERROR) << "Failed to establish notification channel."; 1279 notification_channel_starting_ = false; 1280 RefreshAccessToken( 1281 base::Bind(&DeviceRegistrationInfo::CheckAccessTokenError, AsWeakPtr())); 1282 } 1283 1284 void DeviceRegistrationInfo::OnCommandCreated( 1285 const base::DictionaryValue& command, 1286 const std::string& channel_name) { 1287 if (!connected_to_cloud_) 1288 return; 1289 1290 VLOG(1) << "Command notification received: " << command; 1291 1292 if (!command.empty()) { 1293 // GCD spec indicates that the command parameter in notification object 1294 // "may be empty if command size is too big". 1295 PublishCommand(command); 1296 return; 1297 } 1298 1299 // If this request comes from a Pull channel while the primary notification 1300 // channel (XMPP) is active, we are doing a backup poll, so mark the request 1301 // appropriately. 1302 bool just_in_case = 1303 (channel_name == kPullChannelName) && 1304 (current_notification_channel_ == primary_notification_channel_.get()); 1305 1306 std::string reason = 1307 just_in_case ? fetch_reason::kJustInCase : fetch_reason::kNewCommand; 1308 1309 // If the command was too big to be delivered over a notification channel, 1310 // or OnCommandCreated() was initiated from the Pull notification, 1311 // perform a manual command fetch from the server here. 1312 FetchAndPublishCommands(reason); 1313 } 1314 1315 void DeviceRegistrationInfo::OnDeviceDeleted(const std::string& cloud_id) { 1316 if (cloud_id != GetSettings().cloud_id) { 1317 LOG(WARNING) << "Unexpected device deletion notification for cloud ID '" 1318 << cloud_id << "'"; 1319 return; 1320 } 1321 RemoveCredentials(); 1322 } 1323 1324 void DeviceRegistrationInfo::RemoveCredentials() { 1325 if (!HaveRegistrationCredentials()) 1326 return; 1327 1328 connected_to_cloud_ = false; 1329 1330 LOG(INFO) << "Device is unregistered from the cloud. Deleting credentials"; 1331 if (auth_manager_) 1332 auth_manager_->SetAuthSecret({}, RootClientTokenOwner::kNone); 1333 1334 Config::Transaction change{config_}; 1335 // Keep cloud_id to switch to detect kInvalidCredentials after restart. 1336 change.set_robot_account(""); 1337 change.set_refresh_token(""); 1338 change.Commit(); 1339 1340 current_notification_channel_ = nullptr; 1341 if (primary_notification_channel_) { 1342 primary_notification_channel_->Stop(); 1343 primary_notification_channel_.reset(); 1344 } 1345 if (pull_channel_) { 1346 pull_channel_->Stop(); 1347 pull_channel_.reset(); 1348 } 1349 notification_channel_starting_ = false; 1350 SetGcdState(GcdState::kInvalidCredentials); 1351 } 1352 1353 } // namespace weave 1354