blob: a1cb3740a6f5f0b55d245597ca313a7579289ff0 [file] [log] [blame]
Alex Vakulenko3cb466c2014-04-15 11:36:32 -07001// Copyright 2014 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "buffet/device_registration_info.h"
6
Christopher Wiley006e94e2014-05-02 13:44:48 -07007#include <memory>
Alex Vakulenkob3aac252014-05-07 17:35:24 -07008#include <utility>
9#include <vector>
Christopher Wiley006e94e2014-05-02 13:44:48 -070010
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070011#include <base/json/json_writer.h>
12#include <base/values.h>
Alex Vakulenkoe4879a22014-08-20 15:47:36 -070013#include <chromeos/data_encoding.h>
Alex Vakulenko3aeea1c2014-08-20 16:33:12 -070014#include <chromeos/mime_utils.h>
Alex Vakulenkob8fc1df2014-08-20 15:38:07 -070015#include <chromeos/string_utils.h>
Alex Vakulenkobd5b5442014-08-20 16:16:34 -070016#include <chromeos/url_utils.h>
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070017
Alex Vakulenko45109442014-07-29 11:07:10 -070018#include "buffet/commands/command_definition.h"
19#include "buffet/commands/command_manager.h"
Alex Vakulenko8e34d392014-04-29 11:02:56 -070020#include "buffet/device_registration_storage_keys.h"
Alex Vakulenkoa3062c52014-04-21 17:05:51 -070021#include "buffet/http_transport_curl.h"
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070022#include "buffet/http_utils.h"
Alex Vakulenko5841c302014-07-23 10:49:49 -070023#include "buffet/storage_impls.h"
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070024
Alex Vakulenkob3aac252014-05-07 17:35:24 -070025const char buffet::kErrorDomainOAuth2[] = "oauth2";
26const char buffet::kErrorDomainGCD[] = "gcd";
27const char buffet::kErrorDomainGCDServer[] = "gcd_server";
28const char buffet::kErrorDomainBuffet[] = "buffet";
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070029
Alex Vakulenko8e34d392014-04-29 11:02:56 -070030namespace buffet {
31namespace storage_keys {
32
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070033// Persistent keys
34const char kClientId[] = "client_id";
35const char kClientSecret[] = "client_secret";
36const char kApiKey[] = "api_key";
37const char kRefreshToken[] = "refresh_token";
38const char kDeviceId[] = "device_id";
39const char kOAuthURL[] = "oauth_url";
40const char kServiceURL[] = "service_url";
41const char kRobotAccount[] = "robot_account";
42// Transient keys
43const char kDeviceKind[] = "device_kind";
44const char kSystemName[] = "system_name";
45const char kDisplayName[] = "display_name";
46
Alex Vakulenko8e34d392014-04-29 11:02:56 -070047} // namespace storage_keys
48} // namespace buffet
49
50namespace {
51
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070052const base::FilePath::CharType kDeviceInfoFilePath[] =
53 FILE_PATH_LITERAL("/var/lib/buffet/device_reg_info");
54
55bool GetParamValue(
Alex Vakulenkoa9044342014-08-23 19:31:27 -070056 const std::map<std::string, std::string>& params,
Alex Vakulenkoa3062c52014-04-21 17:05:51 -070057 const std::string& param_name,
58 std::string* param_value) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070059 auto p = params.find(param_name);
60 if (p == params.end())
61 return false;
62
Alex Vakulenkoa9044342014-08-23 19:31:27 -070063 *param_value = p->second;
64 return true;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070065}
66
67std::pair<std::string, std::string> BuildAuthHeader(
68 const std::string& access_token_type,
69 const std::string& access_token) {
Alex Vakulenkoaf23b322014-05-08 16:25:45 -070070 std::string authorization =
Alex Vakulenkob8fc1df2014-08-20 15:38:07 -070071 chromeos::string_utils::Join(' ', access_token_type, access_token);
Alex Vakulenko96c84d32014-06-06 11:07:32 -070072 return {buffet::http::request_header::kAuthorization, authorization};
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070073}
74
75std::unique_ptr<base::DictionaryValue> ParseOAuthResponse(
Alex Vakulenko5f472062014-08-14 17:54:04 -070076 const buffet::http::Response* response, chromeos::ErrorPtr* error) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070077 int code = 0;
Alex Vakulenkoaf23b322014-05-08 16:25:45 -070078 auto resp = buffet::http::ParseJsonResponse(response, &code, error);
79 if (resp && code >= buffet::http::status_code::BadRequest) {
Alex Vakulenkob3aac252014-05-07 17:35:24 -070080 if (error) {
81 std::string error_code, error_message;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070082 if (resp->GetString("error", &error_code) &&
Alex Vakulenkob3aac252014-05-07 17:35:24 -070083 resp->GetString("error_description", &error_message)) {
Alex Vakulenko5f472062014-08-14 17:54:04 -070084 chromeos::Error::AddTo(error, buffet::kErrorDomainOAuth2, error_code,
85 error_message);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070086 } else {
Alex Vakulenko5f472062014-08-14 17:54:04 -070087 chromeos::Error::AddTo(error, buffet::kErrorDomainOAuth2,
88 "unexpected_response", "Unexpected OAuth error");
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070089 }
90 }
91 return std::unique_ptr<base::DictionaryValue>();
92 }
93 return resp;
94}
95
Alex Vakulenko5f472062014-08-14 17:54:04 -070096inline void SetUnexpectedError(chromeos::ErrorPtr* error) {
97 chromeos::Error::AddTo(error, buffet::kErrorDomainGCD, "unexpected_response",
98 "Unexpected GCD error");
Alex Vakulenkob3aac252014-05-07 17:35:24 -070099}
100
Alex Vakulenko5f472062014-08-14 17:54:04 -0700101void ParseGCDError(const base::DictionaryValue* json,
102 chromeos::ErrorPtr* error) {
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700103 if (!error)
104 return;
105
106 const base::Value* list_value = nullptr;
107 const base::ListValue* error_list = nullptr;
108 if (!json->Get("error.errors", &list_value) ||
109 !list_value->GetAsList(&error_list)) {
110 SetUnexpectedError(error);
111 return;
112 }
113
114 for (size_t i = 0; i < error_list->GetSize(); i++) {
115 const base::Value* error_value = nullptr;
116 const base::DictionaryValue* error_object = nullptr;
117 if (!error_list->Get(i, &error_value) ||
118 !error_value->GetAsDictionary(&error_object)) {
119 SetUnexpectedError(error);
120 continue;
121 }
122 std::string error_code, error_message;
123 if (error_object->GetString("reason", &error_code) &&
124 error_object->GetString("message", &error_message)) {
Alex Vakulenko5f472062014-08-14 17:54:04 -0700125 chromeos::Error::AddTo(error, buffet::kErrorDomainGCDServer,
126 error_code, error_message);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700127 } else {
128 SetUnexpectedError(error);
129 }
130 }
131}
132
Alex Vakulenkobda220a2014-04-18 15:25:44 -0700133std::string BuildURL(const std::string& url,
134 const std::vector<std::string>& subpaths,
Alex Vakulenkoe4879a22014-08-20 15:47:36 -0700135 const chromeos::data_encoding::WebParamList& params) {
Alex Vakulenkobd5b5442014-08-20 16:16:34 -0700136 std::string result = chromeos::url::CombineMultiple(url, subpaths);
137 return chromeos::url::AppendQueryParams(result, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700138}
139
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700140} // anonymous namespace
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700141
142namespace buffet {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700143
Alex Vakulenko1f30a622014-07-23 11:13:15 -0700144DeviceRegistrationInfo::DeviceRegistrationInfo(
145 const std::shared_ptr<CommandManager>& command_manager)
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700146 : transport_(new http::curl::Transport()),
Christopher Wiley006e94e2014-05-02 13:44:48 -0700147 // TODO(avakulenko): Figure out security implications of storing
148 // this data unencrypted.
Alex Vakulenko1f30a622014-07-23 11:13:15 -0700149 storage_(new FileStorage(base::FilePath(kDeviceInfoFilePath))),
150 command_manager_(command_manager) {
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700151}
152
153DeviceRegistrationInfo::DeviceRegistrationInfo(
Alex Vakulenko1f30a622014-07-23 11:13:15 -0700154 const std::shared_ptr<CommandManager>& command_manager,
155 const std::shared_ptr<http::Transport>& transport,
156 const std::shared_ptr<StorageInterface>& storage)
157 : transport_(transport),
158 storage_(storage),
159 command_manager_(command_manager) {
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700160}
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700161
162std::pair<std::string, std::string>
163 DeviceRegistrationInfo::GetAuthorizationHeader() const {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700164 return BuildAuthHeader("Bearer", access_token_);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700165}
166
167std::string DeviceRegistrationInfo::GetServiceURL(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700168 const std::string& subpath,
Alex Vakulenkoe4879a22014-08-20 15:47:36 -0700169 const chromeos::data_encoding::WebParamList& params) const {
Alex Vakulenkobda220a2014-04-18 15:25:44 -0700170 return BuildURL(service_url_, {subpath}, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700171}
172
173std::string DeviceRegistrationInfo::GetDeviceURL(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700174 const std::string& subpath,
Alex Vakulenkoe4879a22014-08-20 15:47:36 -0700175 const chromeos::data_encoding::WebParamList& params) const {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700176 CHECK(!device_id_.empty()) << "Must have a valid device ID";
Alex Vakulenkobda220a2014-04-18 15:25:44 -0700177 return BuildURL(service_url_, {"devices", device_id_, subpath}, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700178}
179
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700180std::string DeviceRegistrationInfo::GetOAuthURL(
181 const std::string& subpath,
Alex Vakulenkoe4879a22014-08-20 15:47:36 -0700182 const chromeos::data_encoding::WebParamList& params) const {
Alex Vakulenkobda220a2014-04-18 15:25:44 -0700183 return BuildURL(oauth_url_, {subpath}, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700184}
185
Alex Vakulenko5f472062014-08-14 17:54:04 -0700186std::string DeviceRegistrationInfo::GetDeviceId(chromeos::ErrorPtr* error) {
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700187 return CheckRegistration(error) ? device_id_ : std::string();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700188}
189
190bool DeviceRegistrationInfo::Load() {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700191 auto value = storage_->Load();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700192 const base::DictionaryValue* dict = nullptr;
193 if (!value || !value->GetAsDictionary(&dict))
194 return false;
195
196 // Get the values into temp variables first to make sure we can get
197 // all the data correctly before changing the state of this object.
198 std::string client_id;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700199 if (!dict->GetString(storage_keys::kClientId, &client_id))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700200 return false;
201 std::string client_secret;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700202 if (!dict->GetString(storage_keys::kClientSecret, &client_secret))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700203 return false;
204 std::string api_key;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700205 if (!dict->GetString(storage_keys::kApiKey, &api_key))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700206 return false;
207 std::string refresh_token;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700208 if (!dict->GetString(storage_keys::kRefreshToken, &refresh_token))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700209 return false;
210 std::string device_id;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700211 if (!dict->GetString(storage_keys::kDeviceId, &device_id))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700212 return false;
213 std::string oauth_url;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700214 if (!dict->GetString(storage_keys::kOAuthURL, &oauth_url))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700215 return false;
216 std::string service_url;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700217 if (!dict->GetString(storage_keys::kServiceURL, &service_url))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700218 return false;
219 std::string device_robot_account;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700220 if (!dict->GetString(storage_keys::kRobotAccount, &device_robot_account))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700221 return false;
222
223 client_id_ = client_id;
224 client_secret_ = client_secret;
225 api_key_ = api_key;
226 refresh_token_ = refresh_token;
227 device_id_ = device_id;
228 oauth_url_ = oauth_url;
229 service_url_ = service_url;
230 device_robot_account_ = device_robot_account;
231 return true;
232}
233
234bool DeviceRegistrationInfo::Save() const {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700235 base::DictionaryValue dict;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700236 dict.SetString(storage_keys::kClientId, client_id_);
237 dict.SetString(storage_keys::kClientSecret, client_secret_);
238 dict.SetString(storage_keys::kApiKey, api_key_);
239 dict.SetString(storage_keys::kRefreshToken, refresh_token_);
240 dict.SetString(storage_keys::kDeviceId, device_id_);
241 dict.SetString(storage_keys::kOAuthURL, oauth_url_);
242 dict.SetString(storage_keys::kServiceURL, service_url_);
243 dict.SetString(storage_keys::kRobotAccount, device_robot_account_);
244 return storage_->Save(&dict);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700245}
246
Alex Vakulenko5f472062014-08-14 17:54:04 -0700247bool DeviceRegistrationInfo::CheckRegistration(chromeos::ErrorPtr* error) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700248 LOG(INFO) << "Checking device registration record.";
249 if (refresh_token_.empty() ||
250 device_id_.empty() ||
251 device_robot_account_.empty()) {
252 LOG(INFO) << "No valid device registration record found.";
Alex Vakulenko5f472062014-08-14 17:54:04 -0700253 chromeos::Error::AddTo(error, kErrorDomainGCD, "device_not_registered",
254 "No valid device registration record found");
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700255 return false;
256 }
257
258 LOG(INFO) << "Device registration record found.";
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700259 return ValidateAndRefreshAccessToken(error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700260}
261
Alex Vakulenko5f472062014-08-14 17:54:04 -0700262bool DeviceRegistrationInfo::ValidateAndRefreshAccessToken(
263 chromeos::ErrorPtr* error) {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700264 LOG(INFO) << "Checking access token expiration.";
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700265 if (!access_token_.empty() &&
266 !access_token_expiration_.is_null() &&
267 access_token_expiration_ > base::Time::Now()) {
268 LOG(INFO) << "Access token is still valid.";
269 return true;
270 }
271
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700272 auto response = http::PostFormData(GetOAuthURL("token"), {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700273 {"refresh_token", refresh_token_},
274 {"client_id", client_id_},
275 {"client_secret", client_secret_},
276 {"grant_type", "refresh_token"},
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700277 }, transport_, error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700278 if (!response)
279 return false;
280
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700281 auto json = ParseOAuthResponse(response.get(), error);
282 if (!json)
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700283 return false;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700284
285 int expires_in = 0;
286 if (!json->GetString("access_token", &access_token_) ||
287 !json->GetInteger("expires_in", &expires_in) ||
288 access_token_.empty() ||
289 expires_in <= 0) {
290 LOG(ERROR) << "Access token unavailable.";
Alex Vakulenko5f472062014-08-14 17:54:04 -0700291 chromeos::Error::AddTo(error, kErrorDomainOAuth2,
292 "unexpected_server_response",
293 "Access token unavailable");
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700294 return false;
295 }
296
297 access_token_expiration_ = base::Time::Now() +
298 base::TimeDelta::FromSeconds(expires_in);
299
300 LOG(INFO) << "Access token is refreshed for additional " << expires_in
301 << " seconds.";
302 return true;
303}
304
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700305std::unique_ptr<base::Value> DeviceRegistrationInfo::GetDeviceInfo(
Alex Vakulenko5f472062014-08-14 17:54:04 -0700306 chromeos::ErrorPtr* error) {
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700307 if (!CheckRegistration(error))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700308 return std::unique_ptr<base::Value>();
309
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700310 auto response = http::Get(GetDeviceURL(),
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700311 {GetAuthorizationHeader()}, transport_, error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700312 int status_code = 0;
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700313 std::unique_ptr<base::DictionaryValue> json =
314 http::ParseJsonResponse(response.get(), &status_code, error);
315 if (json) {
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700316 if (status_code >= http::status_code::BadRequest) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700317 LOG(WARNING) << "Failed to retrieve the device info. Response code = "
318 << status_code;
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700319 ParseGCDError(json.get(), error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700320 return std::unique_ptr<base::Value>();
321 }
322 }
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700323 return std::unique_ptr<base::Value>(json.release());
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700324}
325
326bool CheckParam(const std::string& param_name,
327 const std::string& param_value,
Alex Vakulenko5f472062014-08-14 17:54:04 -0700328 chromeos::ErrorPtr* error) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700329 if (!param_value.empty())
330 return true;
331
Alex Vakulenko5f472062014-08-14 17:54:04 -0700332 chromeos::Error::AddToPrintf(error, kErrorDomainBuffet, "missing_parameter",
333 "Parameter %s not specified",
334 param_name.c_str());
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700335 return false;
336}
337
338std::string DeviceRegistrationInfo::StartRegistration(
Alex Vakulenkoa9044342014-08-23 19:31:27 -0700339 const std::map<std::string, std::string>& params,
Alex Vakulenko5f472062014-08-14 17:54:04 -0700340 chromeos::ErrorPtr* error) {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700341 GetParamValue(params, storage_keys::kClientId, &client_id_);
342 GetParamValue(params, storage_keys::kClientSecret, &client_secret_);
343 GetParamValue(params, storage_keys::kApiKey, &api_key_);
344 GetParamValue(params, storage_keys::kDeviceId, &device_id_);
345 GetParamValue(params, storage_keys::kDeviceKind, &device_kind_);
346 GetParamValue(params, storage_keys::kSystemName, &system_name_);
347 GetParamValue(params, storage_keys::kDisplayName, &display_name_);
348 GetParamValue(params, storage_keys::kOAuthURL, &oauth_url_);
349 GetParamValue(params, storage_keys::kServiceURL, &service_url_);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700350
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700351 if (!CheckParam(storage_keys::kClientId, client_id_, error))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700352 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700353 if (!CheckParam(storage_keys::kClientSecret, client_secret_, error))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700354 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700355 if (!CheckParam(storage_keys::kApiKey, api_key_, error))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700356 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700357 if (!CheckParam(storage_keys::kDeviceKind, device_kind_, error))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700358 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700359 if (!CheckParam(storage_keys::kSystemName, system_name_, error))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700360 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700361 if (!CheckParam(storage_keys::kOAuthURL, oauth_url_, error))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700362 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700363 if (!CheckParam(storage_keys::kServiceURL, service_url_, error))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700364 return std::string();
365
Alex Vakulenko45109442014-07-29 11:07:10 -0700366 std::unique_ptr<base::DictionaryValue> commands =
367 command_manager_->GetCommandDictionary().GetCommandsAsJson(true, error);
368 if (!commands)
369 return std::string();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700370
371 base::DictionaryValue req_json;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700372 req_json.SetString("oauthClientId", client_id_);
373 req_json.SetString("deviceDraft.deviceKind", device_kind_);
374 req_json.SetString("deviceDraft.systemName", system_name_);
375 req_json.SetString("deviceDraft.displayName", display_name_);
376 req_json.SetString("deviceDraft.channel.supportedType", "xmpp");
Alex Vakulenko45109442014-07-29 11:07:10 -0700377 req_json.Set("deviceDraft.commandDefs", commands.release());
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700378
379 std::string url = GetServiceURL("registrationTickets", {{"key", api_key_}});
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700380 auto resp_json = http::ParseJsonResponse(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700381 http::PostJson(url, &req_json, transport_, error).get(), nullptr, error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700382 if (!resp_json)
383 return std::string();
384
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700385 if (!resp_json->GetString("id", &ticket_id_)) {
Alex Vakulenko5f472062014-08-14 17:54:04 -0700386 chromeos::Error::AddTo(error, kErrorDomainGCD, "unexpected_response",
387 "Device ID missing");
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700388 return std::string();
389 }
390
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700391 std::string auth_url = GetOAuthURL("auth", {
392 {"scope", "https://www.googleapis.com/auth/clouddevices"},
393 {"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"},
394 {"response_type", "code"},
395 {"client_id", client_id_}
396 });
397
398 base::DictionaryValue json;
399 json.SetString("ticket_id", ticket_id_);
400 json.SetString("auth_url", auth_url);
401
402 std::string ret;
403 base::JSONWriter::Write(&json, &ret);
404 return ret;
405}
406
407bool DeviceRegistrationInfo::FinishRegistration(
Alex Vakulenko5f472062014-08-14 17:54:04 -0700408 const std::string& user_auth_code, chromeos::ErrorPtr* error) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700409 if (ticket_id_.empty()) {
410 LOG(ERROR) << "Finish registration without ticket ID";
Alex Vakulenko5f472062014-08-14 17:54:04 -0700411 chromeos::Error::AddTo(error, kErrorDomainBuffet,
412 "registration_not_started",
413 "Device registration not started");
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700414 return false;
415 }
416
417 std::string url = GetServiceURL("registrationTickets/" + ticket_id_);
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700418 std::unique_ptr<http::Response> response;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700419 if (!user_auth_code.empty()) {
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700420 response = http::PostFormData(GetOAuthURL("token"), {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700421 {"code", user_auth_code},
422 {"client_id", client_id_},
423 {"client_secret", client_secret_},
424 {"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"},
425 {"grant_type", "authorization_code"}
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700426 }, transport_, error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700427 if (!response)
428 return false;
429
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700430 auto json_resp = ParseOAuthResponse(response.get(), error);
431 if (!json_resp)
432 return false;
433
434 std::string user_access_token;
435 std::string token_type;
436 if (!json_resp->GetString("access_token", &user_access_token) ||
437 !json_resp->GetString("token_type", &token_type)) {
Alex Vakulenko5f472062014-08-14 17:54:04 -0700438 chromeos::Error::AddTo(error, kErrorDomainOAuth2, "unexpected_response",
439 "User access_token is missing in response");
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700440 return false;
441 }
442
443 base::DictionaryValue user_info;
444 user_info.SetString("userEmail", "me");
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700445 response = http::PatchJson(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700446 url, &user_info, {BuildAuthHeader(token_type, user_access_token)},
447 transport_, error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700448
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700449 auto json = http::ParseJsonResponse(response.get(), nullptr, error);
450 if (!json)
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700451 return false;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700452 }
453
454 std::string auth_code;
455 url += "/finalize?key=" + api_key_;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700456 LOG(INFO) << "Sending request to: " << url;
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700457 response = http::PostBinary(url, nullptr, 0, transport_, error);
458 if (!response)
459 return false;
460 auto json_resp = http::ParseJsonResponse(response.get(), nullptr, error);
461 if (!json_resp)
462 return false;
463 if (!response->IsSuccessful()) {
464 ParseGCDError(json_resp.get(), error);
465 return false;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700466 }
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700467 if (!json_resp->GetString("robotAccountEmail", &device_robot_account_) ||
468 !json_resp->GetString("robotAccountAuthorizationCode", &auth_code) ||
469 !json_resp->GetString("deviceDraft.id", &device_id_)) {
Alex Vakulenko5f472062014-08-14 17:54:04 -0700470 chromeos::Error::AddTo(error, kErrorDomainGCD, "unexpected_response",
471 "Device account missing in response");
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700472 return false;
473 }
474
475 // Now get access_token and refresh_token
476 response = http::PostFormData(GetOAuthURL("token"), {
477 {"code", auth_code},
478 {"client_id", client_id_},
479 {"client_secret", client_secret_},
480 {"redirect_uri", "oob"},
481 {"scope", "https://www.googleapis.com/auth/clouddevices"},
482 {"grant_type", "authorization_code"}
483 }, transport_, error);
484 if (!response)
485 return false;
486
487 json_resp = ParseOAuthResponse(response.get(), error);
488 int expires_in = 0;
489 if (!json_resp ||
490 !json_resp->GetString("access_token", &access_token_) ||
491 !json_resp->GetString("refresh_token", &refresh_token_) ||
492 !json_resp->GetInteger("expires_in", &expires_in) ||
493 access_token_.empty() ||
494 refresh_token_.empty() ||
495 expires_in <= 0) {
Alex Vakulenko5f472062014-08-14 17:54:04 -0700496 chromeos::Error::AddTo(error, kErrorDomainGCD, "unexpected_response",
497 "Device access_token missing in response");
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700498 return false;
499 }
500
501 access_token_expiration_ = base::Time::Now() +
502 base::TimeDelta::FromSeconds(expires_in);
503
504 Save();
505 return true;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700506}
507
508} // namespace buffet