blob: 54477261b09f18134c55a474e78d401c0281fbb4 [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 Vakulenko3cb466c2014-04-15 11:36:32 -070013
Alex Vakulenkoa3062c52014-04-21 17:05:51 -070014#include "buffet/data_encoding.h"
Alex Vakulenko8e34d392014-04-29 11:02:56 -070015#include "buffet/device_registration_storage_keys.h"
Christopher Wiley006e94e2014-05-02 13:44:48 -070016#include "buffet/storage_impls.h"
Alex Vakulenkoa3062c52014-04-21 17:05:51 -070017#include "buffet/http_transport_curl.h"
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070018#include "buffet/http_utils.h"
19#include "buffet/mime_utils.h"
20#include "buffet/string_utils.h"
Alex Vakulenkobda220a2014-04-18 15:25:44 -070021#include "buffet/url_utils.h"
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070022
Alex Vakulenkob3aac252014-05-07 17:35:24 -070023const char buffet::kErrorDomainOAuth2[] = "oauth2";
24const char buffet::kErrorDomainGCD[] = "gcd";
25const char buffet::kErrorDomainGCDServer[] = "gcd_server";
26const char buffet::kErrorDomainBuffet[] = "buffet";
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070027
Alex Vakulenko8e34d392014-04-29 11:02:56 -070028namespace buffet {
29namespace storage_keys {
30
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070031// Persistent keys
32const char kClientId[] = "client_id";
33const char kClientSecret[] = "client_secret";
34const char kApiKey[] = "api_key";
35const char kRefreshToken[] = "refresh_token";
36const char kDeviceId[] = "device_id";
37const char kOAuthURL[] = "oauth_url";
38const char kServiceURL[] = "service_url";
39const char kRobotAccount[] = "robot_account";
40// Transient keys
41const char kDeviceKind[] = "device_kind";
42const char kSystemName[] = "system_name";
43const char kDisplayName[] = "display_name";
44
Alex Vakulenko8e34d392014-04-29 11:02:56 -070045} // namespace storage_keys
46} // namespace buffet
47
48namespace {
49
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070050const base::FilePath::CharType kDeviceInfoFilePath[] =
51 FILE_PATH_LITERAL("/var/lib/buffet/device_reg_info");
52
53bool GetParamValue(
Alex Vakulenkoa3062c52014-04-21 17:05:51 -070054 const std::map<std::string, std::shared_ptr<base::Value>>& params,
55 const std::string& param_name,
56 std::string* param_value) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070057 auto p = params.find(param_name);
58 if (p == params.end())
59 return false;
60
61 return p->second->GetAsString(param_value);
62}
63
64std::pair<std::string, std::string> BuildAuthHeader(
65 const std::string& access_token_type,
66 const std::string& access_token) {
Alex Vakulenkoaf23b322014-05-08 16:25:45 -070067 std::string authorization =
68 buffet::string_utils::Join(' ', access_token_type, access_token);
Alex Vakulenko96c84d32014-06-06 11:07:32 -070069 return {buffet::http::request_header::kAuthorization, authorization};
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070070}
71
72std::unique_ptr<base::DictionaryValue> ParseOAuthResponse(
Alex Vakulenkoaf23b322014-05-08 16:25:45 -070073 const buffet::http::Response* response, buffet::ErrorPtr* error) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070074 int code = 0;
Alex Vakulenkoaf23b322014-05-08 16:25:45 -070075 auto resp = buffet::http::ParseJsonResponse(response, &code, error);
76 if (resp && code >= buffet::http::status_code::BadRequest) {
Alex Vakulenkob3aac252014-05-07 17:35:24 -070077 if (error) {
78 std::string error_code, error_message;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070079 if (resp->GetString("error", &error_code) &&
Alex Vakulenkob3aac252014-05-07 17:35:24 -070080 resp->GetString("error_description", &error_message)) {
Alex Vakulenkoaf23b322014-05-08 16:25:45 -070081 buffet::Error::AddTo(error, buffet::kErrorDomainOAuth2, error_code,
82 error_message);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070083 } else {
Alex Vakulenkoaf23b322014-05-08 16:25:45 -070084 buffet::Error::AddTo(error, buffet::kErrorDomainOAuth2,
85 "unexpected_response", "Unexpected OAuth error");
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070086 }
87 }
88 return std::unique_ptr<base::DictionaryValue>();
89 }
90 return resp;
91}
92
Alex Vakulenkoaf23b322014-05-08 16:25:45 -070093inline void SetUnexpectedError(buffet::ErrorPtr* error) {
94 buffet::Error::AddTo(error, buffet::kErrorDomainGCD, "unexpected_response",
95 "Unexpected GCD error");
Alex Vakulenkob3aac252014-05-07 17:35:24 -070096}
97
Alex Vakulenkoaf23b322014-05-08 16:25:45 -070098void ParseGCDError(const base::DictionaryValue* json, buffet::ErrorPtr* error) {
Alex Vakulenkob3aac252014-05-07 17:35:24 -070099 if (!error)
100 return;
101
102 const base::Value* list_value = nullptr;
103 const base::ListValue* error_list = nullptr;
104 if (!json->Get("error.errors", &list_value) ||
105 !list_value->GetAsList(&error_list)) {
106 SetUnexpectedError(error);
107 return;
108 }
109
110 for (size_t i = 0; i < error_list->GetSize(); i++) {
111 const base::Value* error_value = nullptr;
112 const base::DictionaryValue* error_object = nullptr;
113 if (!error_list->Get(i, &error_value) ||
114 !error_value->GetAsDictionary(&error_object)) {
115 SetUnexpectedError(error);
116 continue;
117 }
118 std::string error_code, error_message;
119 if (error_object->GetString("reason", &error_code) &&
120 error_object->GetString("message", &error_message)) {
Alex Vakulenkoaf23b322014-05-08 16:25:45 -0700121 buffet::Error::AddTo(error, buffet::kErrorDomainGCDServer,
122 error_code, error_message);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700123 } else {
124 SetUnexpectedError(error);
125 }
126 }
127}
128
Alex Vakulenkobda220a2014-04-18 15:25:44 -0700129std::string BuildURL(const std::string& url,
130 const std::vector<std::string>& subpaths,
Alex Vakulenkoaf23b322014-05-08 16:25:45 -0700131 const buffet::data_encoding::WebParamList& params) {
132 std::string result = buffet::url::CombineMultiple(url, subpaths);
133 return buffet::url::AppendQueryParams(result, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700134}
135
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700136} // anonymous namespace
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700137
138namespace buffet {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700139
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700140DeviceRegistrationInfo::DeviceRegistrationInfo()
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700141 : transport_(new http::curl::Transport()),
Christopher Wiley006e94e2014-05-02 13:44:48 -0700142 // TODO(avakulenko): Figure out security implications of storing
143 // this data unencrypted.
144 storage_(new FileStorage(base::FilePath(kDeviceInfoFilePath))) {
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700145}
146
147DeviceRegistrationInfo::DeviceRegistrationInfo(
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700148 std::shared_ptr<http::Transport> transport,
149 std::shared_ptr<StorageInterface> storage) : transport_(transport),
150 storage_(storage) {
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700151}
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700152
153std::pair<std::string, std::string>
154 DeviceRegistrationInfo::GetAuthorizationHeader() const {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700155 return BuildAuthHeader("Bearer", access_token_);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700156}
157
158std::string DeviceRegistrationInfo::GetServiceURL(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700159 const std::string& subpath,
160 const data_encoding::WebParamList& params) const {
Alex Vakulenkobda220a2014-04-18 15:25:44 -0700161 return BuildURL(service_url_, {subpath}, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700162}
163
164std::string DeviceRegistrationInfo::GetDeviceURL(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700165 const std::string& subpath,
166 const data_encoding::WebParamList& params) const {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700167 CHECK(!device_id_.empty()) << "Must have a valid device ID";
Alex Vakulenkobda220a2014-04-18 15:25:44 -0700168 return BuildURL(service_url_, {"devices", device_id_, subpath}, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700169}
170
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700171std::string DeviceRegistrationInfo::GetOAuthURL(
172 const std::string& subpath,
173 const data_encoding::WebParamList& params) const {
Alex Vakulenkobda220a2014-04-18 15:25:44 -0700174 return BuildURL(oauth_url_, {subpath}, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700175}
176
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700177std::string DeviceRegistrationInfo::GetDeviceId(ErrorPtr* error) {
178 return CheckRegistration(error) ? device_id_ : std::string();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700179}
180
181bool DeviceRegistrationInfo::Load() {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700182 auto value = storage_->Load();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700183 const base::DictionaryValue* dict = nullptr;
184 if (!value || !value->GetAsDictionary(&dict))
185 return false;
186
187 // Get the values into temp variables first to make sure we can get
188 // all the data correctly before changing the state of this object.
189 std::string client_id;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700190 if (!dict->GetString(storage_keys::kClientId, &client_id))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700191 return false;
192 std::string client_secret;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700193 if (!dict->GetString(storage_keys::kClientSecret, &client_secret))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700194 return false;
195 std::string api_key;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700196 if (!dict->GetString(storage_keys::kApiKey, &api_key))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700197 return false;
198 std::string refresh_token;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700199 if (!dict->GetString(storage_keys::kRefreshToken, &refresh_token))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700200 return false;
201 std::string device_id;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700202 if (!dict->GetString(storage_keys::kDeviceId, &device_id))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700203 return false;
204 std::string oauth_url;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700205 if (!dict->GetString(storage_keys::kOAuthURL, &oauth_url))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700206 return false;
207 std::string service_url;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700208 if (!dict->GetString(storage_keys::kServiceURL, &service_url))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700209 return false;
210 std::string device_robot_account;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700211 if (!dict->GetString(storage_keys::kRobotAccount, &device_robot_account))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700212 return false;
213
214 client_id_ = client_id;
215 client_secret_ = client_secret;
216 api_key_ = api_key;
217 refresh_token_ = refresh_token;
218 device_id_ = device_id;
219 oauth_url_ = oauth_url;
220 service_url_ = service_url;
221 device_robot_account_ = device_robot_account;
222 return true;
223}
224
225bool DeviceRegistrationInfo::Save() const {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700226 base::DictionaryValue dict;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700227 dict.SetString(storage_keys::kClientId, client_id_);
228 dict.SetString(storage_keys::kClientSecret, client_secret_);
229 dict.SetString(storage_keys::kApiKey, api_key_);
230 dict.SetString(storage_keys::kRefreshToken, refresh_token_);
231 dict.SetString(storage_keys::kDeviceId, device_id_);
232 dict.SetString(storage_keys::kOAuthURL, oauth_url_);
233 dict.SetString(storage_keys::kServiceURL, service_url_);
234 dict.SetString(storage_keys::kRobotAccount, device_robot_account_);
235 return storage_->Save(&dict);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700236}
237
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700238bool DeviceRegistrationInfo::CheckRegistration(ErrorPtr* error) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700239 LOG(INFO) << "Checking device registration record.";
240 if (refresh_token_.empty() ||
241 device_id_.empty() ||
242 device_robot_account_.empty()) {
243 LOG(INFO) << "No valid device registration record found.";
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700244 Error::AddTo(error, kErrorDomainGCD, "device_not_registered",
245 "No valid device registration record found");
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700246 return false;
247 }
248
249 LOG(INFO) << "Device registration record found.";
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700250 return ValidateAndRefreshAccessToken(error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700251}
252
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700253bool DeviceRegistrationInfo::ValidateAndRefreshAccessToken(ErrorPtr* error) {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700254 LOG(INFO) << "Checking access token expiration.";
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700255 if (!access_token_.empty() &&
256 !access_token_expiration_.is_null() &&
257 access_token_expiration_ > base::Time::Now()) {
258 LOG(INFO) << "Access token is still valid.";
259 return true;
260 }
261
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700262 auto response = http::PostFormData(GetOAuthURL("token"), {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700263 {"refresh_token", refresh_token_},
264 {"client_id", client_id_},
265 {"client_secret", client_secret_},
266 {"grant_type", "refresh_token"},
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700267 }, transport_, error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700268 if (!response)
269 return false;
270
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700271 auto json = ParseOAuthResponse(response.get(), error);
272 if (!json)
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700273 return false;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700274
275 int expires_in = 0;
276 if (!json->GetString("access_token", &access_token_) ||
277 !json->GetInteger("expires_in", &expires_in) ||
278 access_token_.empty() ||
279 expires_in <= 0) {
280 LOG(ERROR) << "Access token unavailable.";
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700281 Error::AddTo(error, kErrorDomainOAuth2, "unexpected_server_response",
282 "Access token unavailable");
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700283 return false;
284 }
285
286 access_token_expiration_ = base::Time::Now() +
287 base::TimeDelta::FromSeconds(expires_in);
288
289 LOG(INFO) << "Access token is refreshed for additional " << expires_in
290 << " seconds.";
291 return true;
292}
293
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700294std::unique_ptr<base::Value> DeviceRegistrationInfo::GetDeviceInfo(
295 ErrorPtr* error) {
296 if (!CheckRegistration(error))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700297 return std::unique_ptr<base::Value>();
298
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700299 auto response = http::Get(GetDeviceURL(),
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700300 {GetAuthorizationHeader()}, transport_, error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700301 int status_code = 0;
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700302 std::unique_ptr<base::DictionaryValue> json =
303 http::ParseJsonResponse(response.get(), &status_code, error);
304 if (json) {
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700305 if (status_code >= http::status_code::BadRequest) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700306 LOG(WARNING) << "Failed to retrieve the device info. Response code = "
307 << status_code;
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700308 ParseGCDError(json.get(), error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700309 return std::unique_ptr<base::Value>();
310 }
311 }
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700312 return std::unique_ptr<base::Value>(json.release());
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700313}
314
315bool CheckParam(const std::string& param_name,
316 const std::string& param_value,
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700317 ErrorPtr* error) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700318 if (!param_value.empty())
319 return true;
320
Alex Vakulenko96c84d32014-06-06 11:07:32 -0700321 Error::AddToPrintf(error, kErrorDomainBuffet, "missing_parameter",
322 "Parameter %s not specified", param_name.c_str());
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700323 return false;
324}
325
326std::string DeviceRegistrationInfo::StartRegistration(
327 const std::map<std::string, std::shared_ptr<base::Value>>& params,
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700328 ErrorPtr* error) {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700329 GetParamValue(params, storage_keys::kClientId, &client_id_);
330 GetParamValue(params, storage_keys::kClientSecret, &client_secret_);
331 GetParamValue(params, storage_keys::kApiKey, &api_key_);
332 GetParamValue(params, storage_keys::kDeviceId, &device_id_);
333 GetParamValue(params, storage_keys::kDeviceKind, &device_kind_);
334 GetParamValue(params, storage_keys::kSystemName, &system_name_);
335 GetParamValue(params, storage_keys::kDisplayName, &display_name_);
336 GetParamValue(params, storage_keys::kOAuthURL, &oauth_url_);
337 GetParamValue(params, storage_keys::kServiceURL, &service_url_);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700338
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700339 if (!CheckParam(storage_keys::kClientId, client_id_, error))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700340 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700341 if (!CheckParam(storage_keys::kClientSecret, client_secret_, error))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700342 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700343 if (!CheckParam(storage_keys::kApiKey, api_key_, error))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700344 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700345 if (!CheckParam(storage_keys::kDeviceKind, device_kind_, error))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700346 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700347 if (!CheckParam(storage_keys::kSystemName, system_name_, error))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700348 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700349 if (!CheckParam(storage_keys::kOAuthURL, oauth_url_, error))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700350 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700351 if (!CheckParam(storage_keys::kServiceURL, service_url_, error))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700352 return std::string();
353
354 std::vector<std::pair<std::string, std::vector<std::string>>> commands = {
355 {"SetDeviceConfiguration", {"data"}}
356 };
357
358 base::DictionaryValue req_json;
359 base::ListValue* set_device_configuration_params = new base::ListValue;
360 base::DictionaryValue* param1 = new base::DictionaryValue;
361 param1->SetString("name", "data");
362 set_device_configuration_params->Append(param1);
363
364 base::ListValue* vendor_commands = new base::ListValue;
Alex Vakulenko96c84d32014-06-06 11:07:32 -0700365 for (const auto& pair : commands) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700366 base::ListValue* params = new base::ListValue;
Alex Vakulenko96c84d32014-06-06 11:07:32 -0700367 for (const auto& param_name : pair.second) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700368 base::DictionaryValue* param = new base::DictionaryValue;
369 param->SetString("name", param_name);
370 params->Append(param);
371 }
372 base::DictionaryValue* command = new base::DictionaryValue;
373 command->SetString("name", pair.first);
374 command->Set("parameter", params);
375 vendor_commands->Append(command);
376 }
377
378 req_json.SetString("oauthClientId", client_id_);
379 req_json.SetString("deviceDraft.deviceKind", device_kind_);
380 req_json.SetString("deviceDraft.systemName", system_name_);
381 req_json.SetString("deviceDraft.displayName", display_name_);
382 req_json.SetString("deviceDraft.channel.supportedType", "xmpp");
383 req_json.Set("deviceDraft.commands.base.vendorCommands", vendor_commands);
384
385 std::string url = GetServiceURL("registrationTickets", {{"key", api_key_}});
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700386 auto resp_json = http::ParseJsonResponse(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700387 http::PostJson(url, &req_json, transport_, error).get(), nullptr, error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700388 if (!resp_json)
389 return std::string();
390
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700391 if (!resp_json->GetString("id", &ticket_id_)) {
392 Error::AddTo(error, kErrorDomainGCD, "unexpected_response",
393 "Device ID missing");
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700394 return std::string();
395 }
396
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700397 std::string auth_url = GetOAuthURL("auth", {
398 {"scope", "https://www.googleapis.com/auth/clouddevices"},
399 {"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"},
400 {"response_type", "code"},
401 {"client_id", client_id_}
402 });
403
404 base::DictionaryValue json;
405 json.SetString("ticket_id", ticket_id_);
406 json.SetString("auth_url", auth_url);
407
408 std::string ret;
409 base::JSONWriter::Write(&json, &ret);
410 return ret;
411}
412
413bool DeviceRegistrationInfo::FinishRegistration(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700414 const std::string& user_auth_code, ErrorPtr* error) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700415 if (ticket_id_.empty()) {
416 LOG(ERROR) << "Finish registration without ticket ID";
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700417 Error::AddTo(error, kErrorDomainBuffet, "registration_not_started",
418 "Device registration not started");
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700419 return false;
420 }
421
422 std::string url = GetServiceURL("registrationTickets/" + ticket_id_);
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700423 std::unique_ptr<http::Response> response;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700424 if (!user_auth_code.empty()) {
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700425 response = http::PostFormData(GetOAuthURL("token"), {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700426 {"code", user_auth_code},
427 {"client_id", client_id_},
428 {"client_secret", client_secret_},
429 {"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"},
430 {"grant_type", "authorization_code"}
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700431 }, transport_, error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700432 if (!response)
433 return false;
434
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700435 auto json_resp = ParseOAuthResponse(response.get(), error);
436 if (!json_resp)
437 return false;
438
439 std::string user_access_token;
440 std::string token_type;
441 if (!json_resp->GetString("access_token", &user_access_token) ||
442 !json_resp->GetString("token_type", &token_type)) {
443 Error::AddTo(error, kErrorDomainOAuth2, "unexpected_response",
444 "User access_token is missing in response");
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700445 return false;
446 }
447
448 base::DictionaryValue user_info;
449 user_info.SetString("userEmail", "me");
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700450 response = http::PatchJson(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700451 url, &user_info, {BuildAuthHeader(token_type, user_access_token)},
452 transport_, error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700453
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700454 auto json = http::ParseJsonResponse(response.get(), nullptr, error);
455 if (!json)
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700456 return false;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700457 }
458
459 std::string auth_code;
460 url += "/finalize?key=" + api_key_;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700461 LOG(INFO) << "Sending request to: " << url;
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700462 response = http::PostBinary(url, nullptr, 0, transport_, error);
463 if (!response)
464 return false;
465 auto json_resp = http::ParseJsonResponse(response.get(), nullptr, error);
466 if (!json_resp)
467 return false;
468 if (!response->IsSuccessful()) {
469 ParseGCDError(json_resp.get(), error);
470 return false;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700471 }
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700472 if (!json_resp->GetString("robotAccountEmail", &device_robot_account_) ||
473 !json_resp->GetString("robotAccountAuthorizationCode", &auth_code) ||
474 !json_resp->GetString("deviceDraft.id", &device_id_)) {
475 Error::AddTo(error, kErrorDomainGCD, "unexpected_response",
476 "Device account missing in response");
477 return false;
478 }
479
480 // Now get access_token and refresh_token
481 response = http::PostFormData(GetOAuthURL("token"), {
482 {"code", auth_code},
483 {"client_id", client_id_},
484 {"client_secret", client_secret_},
485 {"redirect_uri", "oob"},
486 {"scope", "https://www.googleapis.com/auth/clouddevices"},
487 {"grant_type", "authorization_code"}
488 }, transport_, error);
489 if (!response)
490 return false;
491
492 json_resp = ParseOAuthResponse(response.get(), error);
493 int expires_in = 0;
494 if (!json_resp ||
495 !json_resp->GetString("access_token", &access_token_) ||
496 !json_resp->GetString("refresh_token", &refresh_token_) ||
497 !json_resp->GetInteger("expires_in", &expires_in) ||
498 access_token_.empty() ||
499 refresh_token_.empty() ||
500 expires_in <= 0) {
501 Error::AddTo(error, kErrorDomainGCD, "unexpected_response",
502 "Device access_token missing in response");
503 return false;
504 }
505
506 access_token_expiration_ = base::Time::Now() +
507 base::TimeDelta::FromSeconds(expires_in);
508
509 Save();
510 return true;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700511}
512
513} // namespace buffet