blob: 6c21429d909b953da89677e8e3a8ce4ff403043d [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 Vakulenko45109442014-07-29 11:07:10 -070014#include "buffet/commands/command_definition.h"
15#include "buffet/commands/command_manager.h"
Alex Vakulenkoa3062c52014-04-21 17:05:51 -070016#include "buffet/data_encoding.h"
Alex Vakulenko8e34d392014-04-29 11:02:56 -070017#include "buffet/device_registration_storage_keys.h"
Alex Vakulenkoa3062c52014-04-21 17:05:51 -070018#include "buffet/http_transport_curl.h"
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070019#include "buffet/http_utils.h"
20#include "buffet/mime_utils.h"
Alex Vakulenko5841c302014-07-23 10:49:49 -070021#include "buffet/storage_impls.h"
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070022#include "buffet/string_utils.h"
Alex Vakulenkobda220a2014-04-18 15:25:44 -070023#include "buffet/url_utils.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 Vakulenkoa3062c52014-04-21 17:05:51 -070056 const std::map<std::string, std::shared_ptr<base::Value>>& params,
57 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
63 return p->second->GetAsString(param_value);
64}
65
66std::pair<std::string, std::string> BuildAuthHeader(
67 const std::string& access_token_type,
68 const std::string& access_token) {
Alex Vakulenkoaf23b322014-05-08 16:25:45 -070069 std::string authorization =
70 buffet::string_utils::Join(' ', access_token_type, access_token);
Alex Vakulenko96c84d32014-06-06 11:07:32 -070071 return {buffet::http::request_header::kAuthorization, authorization};
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070072}
73
74std::unique_ptr<base::DictionaryValue> ParseOAuthResponse(
Alex Vakulenkoaf23b322014-05-08 16:25:45 -070075 const buffet::http::Response* response, buffet::ErrorPtr* error) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070076 int code = 0;
Alex Vakulenkoaf23b322014-05-08 16:25:45 -070077 auto resp = buffet::http::ParseJsonResponse(response, &code, error);
78 if (resp && code >= buffet::http::status_code::BadRequest) {
Alex Vakulenkob3aac252014-05-07 17:35:24 -070079 if (error) {
80 std::string error_code, error_message;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070081 if (resp->GetString("error", &error_code) &&
Alex Vakulenkob3aac252014-05-07 17:35:24 -070082 resp->GetString("error_description", &error_message)) {
Alex Vakulenkoaf23b322014-05-08 16:25:45 -070083 buffet::Error::AddTo(error, buffet::kErrorDomainOAuth2, error_code,
84 error_message);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070085 } else {
Alex Vakulenkoaf23b322014-05-08 16:25:45 -070086 buffet::Error::AddTo(error, buffet::kErrorDomainOAuth2,
87 "unexpected_response", "Unexpected OAuth error");
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070088 }
89 }
90 return std::unique_ptr<base::DictionaryValue>();
91 }
92 return resp;
93}
94
Alex Vakulenkoaf23b322014-05-08 16:25:45 -070095inline void SetUnexpectedError(buffet::ErrorPtr* error) {
96 buffet::Error::AddTo(error, buffet::kErrorDomainGCD, "unexpected_response",
97 "Unexpected GCD error");
Alex Vakulenkob3aac252014-05-07 17:35:24 -070098}
99
Alex Vakulenkoaf23b322014-05-08 16:25:45 -0700100void ParseGCDError(const base::DictionaryValue* json, buffet::ErrorPtr* error) {
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700101 if (!error)
102 return;
103
104 const base::Value* list_value = nullptr;
105 const base::ListValue* error_list = nullptr;
106 if (!json->Get("error.errors", &list_value) ||
107 !list_value->GetAsList(&error_list)) {
108 SetUnexpectedError(error);
109 return;
110 }
111
112 for (size_t i = 0; i < error_list->GetSize(); i++) {
113 const base::Value* error_value = nullptr;
114 const base::DictionaryValue* error_object = nullptr;
115 if (!error_list->Get(i, &error_value) ||
116 !error_value->GetAsDictionary(&error_object)) {
117 SetUnexpectedError(error);
118 continue;
119 }
120 std::string error_code, error_message;
121 if (error_object->GetString("reason", &error_code) &&
122 error_object->GetString("message", &error_message)) {
Alex Vakulenkoaf23b322014-05-08 16:25:45 -0700123 buffet::Error::AddTo(error, buffet::kErrorDomainGCDServer,
124 error_code, error_message);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700125 } else {
126 SetUnexpectedError(error);
127 }
128 }
129}
130
Alex Vakulenkobda220a2014-04-18 15:25:44 -0700131std::string BuildURL(const std::string& url,
132 const std::vector<std::string>& subpaths,
Alex Vakulenkoaf23b322014-05-08 16:25:45 -0700133 const buffet::data_encoding::WebParamList& params) {
134 std::string result = buffet::url::CombineMultiple(url, subpaths);
135 return buffet::url::AppendQueryParams(result, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700136}
137
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700138} // anonymous namespace
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700139
140namespace buffet {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700141
Alex Vakulenko1f30a622014-07-23 11:13:15 -0700142DeviceRegistrationInfo::DeviceRegistrationInfo(
143 const std::shared_ptr<CommandManager>& command_manager)
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700144 : transport_(new http::curl::Transport()),
Christopher Wiley006e94e2014-05-02 13:44:48 -0700145 // TODO(avakulenko): Figure out security implications of storing
146 // this data unencrypted.
Alex Vakulenko1f30a622014-07-23 11:13:15 -0700147 storage_(new FileStorage(base::FilePath(kDeviceInfoFilePath))),
148 command_manager_(command_manager) {
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700149}
150
151DeviceRegistrationInfo::DeviceRegistrationInfo(
Alex Vakulenko1f30a622014-07-23 11:13:15 -0700152 const std::shared_ptr<CommandManager>& command_manager,
153 const std::shared_ptr<http::Transport>& transport,
154 const std::shared_ptr<StorageInterface>& storage)
155 : transport_(transport),
156 storage_(storage),
157 command_manager_(command_manager) {
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700158}
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700159
160std::pair<std::string, std::string>
161 DeviceRegistrationInfo::GetAuthorizationHeader() const {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700162 return BuildAuthHeader("Bearer", access_token_);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700163}
164
165std::string DeviceRegistrationInfo::GetServiceURL(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700166 const std::string& subpath,
167 const data_encoding::WebParamList& params) const {
Alex Vakulenkobda220a2014-04-18 15:25:44 -0700168 return BuildURL(service_url_, {subpath}, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700169}
170
171std::string DeviceRegistrationInfo::GetDeviceURL(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700172 const std::string& subpath,
173 const data_encoding::WebParamList& params) const {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700174 CHECK(!device_id_.empty()) << "Must have a valid device ID";
Alex Vakulenkobda220a2014-04-18 15:25:44 -0700175 return BuildURL(service_url_, {"devices", device_id_, subpath}, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700176}
177
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700178std::string DeviceRegistrationInfo::GetOAuthURL(
179 const std::string& subpath,
180 const data_encoding::WebParamList& params) const {
Alex Vakulenkobda220a2014-04-18 15:25:44 -0700181 return BuildURL(oauth_url_, {subpath}, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700182}
183
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700184std::string DeviceRegistrationInfo::GetDeviceId(ErrorPtr* error) {
185 return CheckRegistration(error) ? device_id_ : std::string();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700186}
187
188bool DeviceRegistrationInfo::Load() {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700189 auto value = storage_->Load();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700190 const base::DictionaryValue* dict = nullptr;
191 if (!value || !value->GetAsDictionary(&dict))
192 return false;
193
194 // Get the values into temp variables first to make sure we can get
195 // all the data correctly before changing the state of this object.
196 std::string client_id;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700197 if (!dict->GetString(storage_keys::kClientId, &client_id))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700198 return false;
199 std::string client_secret;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700200 if (!dict->GetString(storage_keys::kClientSecret, &client_secret))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700201 return false;
202 std::string api_key;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700203 if (!dict->GetString(storage_keys::kApiKey, &api_key))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700204 return false;
205 std::string refresh_token;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700206 if (!dict->GetString(storage_keys::kRefreshToken, &refresh_token))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700207 return false;
208 std::string device_id;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700209 if (!dict->GetString(storage_keys::kDeviceId, &device_id))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700210 return false;
211 std::string oauth_url;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700212 if (!dict->GetString(storage_keys::kOAuthURL, &oauth_url))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700213 return false;
214 std::string service_url;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700215 if (!dict->GetString(storage_keys::kServiceURL, &service_url))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700216 return false;
217 std::string device_robot_account;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700218 if (!dict->GetString(storage_keys::kRobotAccount, &device_robot_account))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700219 return false;
220
221 client_id_ = client_id;
222 client_secret_ = client_secret;
223 api_key_ = api_key;
224 refresh_token_ = refresh_token;
225 device_id_ = device_id;
226 oauth_url_ = oauth_url;
227 service_url_ = service_url;
228 device_robot_account_ = device_robot_account;
229 return true;
230}
231
232bool DeviceRegistrationInfo::Save() const {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700233 base::DictionaryValue dict;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700234 dict.SetString(storage_keys::kClientId, client_id_);
235 dict.SetString(storage_keys::kClientSecret, client_secret_);
236 dict.SetString(storage_keys::kApiKey, api_key_);
237 dict.SetString(storage_keys::kRefreshToken, refresh_token_);
238 dict.SetString(storage_keys::kDeviceId, device_id_);
239 dict.SetString(storage_keys::kOAuthURL, oauth_url_);
240 dict.SetString(storage_keys::kServiceURL, service_url_);
241 dict.SetString(storage_keys::kRobotAccount, device_robot_account_);
242 return storage_->Save(&dict);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700243}
244
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700245bool DeviceRegistrationInfo::CheckRegistration(ErrorPtr* error) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700246 LOG(INFO) << "Checking device registration record.";
247 if (refresh_token_.empty() ||
248 device_id_.empty() ||
249 device_robot_account_.empty()) {
250 LOG(INFO) << "No valid device registration record found.";
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700251 Error::AddTo(error, kErrorDomainGCD, "device_not_registered",
252 "No valid device registration record found");
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700253 return false;
254 }
255
256 LOG(INFO) << "Device registration record found.";
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700257 return ValidateAndRefreshAccessToken(error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700258}
259
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700260bool DeviceRegistrationInfo::ValidateAndRefreshAccessToken(ErrorPtr* error) {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700261 LOG(INFO) << "Checking access token expiration.";
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700262 if (!access_token_.empty() &&
263 !access_token_expiration_.is_null() &&
264 access_token_expiration_ > base::Time::Now()) {
265 LOG(INFO) << "Access token is still valid.";
266 return true;
267 }
268
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700269 auto response = http::PostFormData(GetOAuthURL("token"), {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700270 {"refresh_token", refresh_token_},
271 {"client_id", client_id_},
272 {"client_secret", client_secret_},
273 {"grant_type", "refresh_token"},
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700274 }, transport_, error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700275 if (!response)
276 return false;
277
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700278 auto json = ParseOAuthResponse(response.get(), error);
279 if (!json)
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700280 return false;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700281
282 int expires_in = 0;
283 if (!json->GetString("access_token", &access_token_) ||
284 !json->GetInteger("expires_in", &expires_in) ||
285 access_token_.empty() ||
286 expires_in <= 0) {
287 LOG(ERROR) << "Access token unavailable.";
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700288 Error::AddTo(error, kErrorDomainOAuth2, "unexpected_server_response",
289 "Access token unavailable");
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700290 return false;
291 }
292
293 access_token_expiration_ = base::Time::Now() +
294 base::TimeDelta::FromSeconds(expires_in);
295
296 LOG(INFO) << "Access token is refreshed for additional " << expires_in
297 << " seconds.";
298 return true;
299}
300
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700301std::unique_ptr<base::Value> DeviceRegistrationInfo::GetDeviceInfo(
302 ErrorPtr* error) {
303 if (!CheckRegistration(error))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700304 return std::unique_ptr<base::Value>();
305
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700306 auto response = http::Get(GetDeviceURL(),
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700307 {GetAuthorizationHeader()}, transport_, error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700308 int status_code = 0;
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700309 std::unique_ptr<base::DictionaryValue> json =
310 http::ParseJsonResponse(response.get(), &status_code, error);
311 if (json) {
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700312 if (status_code >= http::status_code::BadRequest) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700313 LOG(WARNING) << "Failed to retrieve the device info. Response code = "
314 << status_code;
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700315 ParseGCDError(json.get(), error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700316 return std::unique_ptr<base::Value>();
317 }
318 }
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700319 return std::unique_ptr<base::Value>(json.release());
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700320}
321
322bool CheckParam(const std::string& param_name,
323 const std::string& param_value,
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700324 ErrorPtr* error) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700325 if (!param_value.empty())
326 return true;
327
Alex Vakulenko96c84d32014-06-06 11:07:32 -0700328 Error::AddToPrintf(error, kErrorDomainBuffet, "missing_parameter",
329 "Parameter %s not specified", param_name.c_str());
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700330 return false;
331}
332
333std::string DeviceRegistrationInfo::StartRegistration(
334 const std::map<std::string, std::shared_ptr<base::Value>>& params,
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700335 ErrorPtr* error) {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700336 GetParamValue(params, storage_keys::kClientId, &client_id_);
337 GetParamValue(params, storage_keys::kClientSecret, &client_secret_);
338 GetParamValue(params, storage_keys::kApiKey, &api_key_);
339 GetParamValue(params, storage_keys::kDeviceId, &device_id_);
340 GetParamValue(params, storage_keys::kDeviceKind, &device_kind_);
341 GetParamValue(params, storage_keys::kSystemName, &system_name_);
342 GetParamValue(params, storage_keys::kDisplayName, &display_name_);
343 GetParamValue(params, storage_keys::kOAuthURL, &oauth_url_);
344 GetParamValue(params, storage_keys::kServiceURL, &service_url_);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700345
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700346 if (!CheckParam(storage_keys::kClientId, client_id_, error))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700347 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700348 if (!CheckParam(storage_keys::kClientSecret, client_secret_, error))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700349 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700350 if (!CheckParam(storage_keys::kApiKey, api_key_, error))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700351 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700352 if (!CheckParam(storage_keys::kDeviceKind, device_kind_, error))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700353 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700354 if (!CheckParam(storage_keys::kSystemName, system_name_, error))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700355 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700356 if (!CheckParam(storage_keys::kOAuthURL, oauth_url_, error))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700357 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700358 if (!CheckParam(storage_keys::kServiceURL, service_url_, error))
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700359 return std::string();
360
Alex Vakulenko45109442014-07-29 11:07:10 -0700361 std::unique_ptr<base::DictionaryValue> commands =
362 command_manager_->GetCommandDictionary().GetCommandsAsJson(true, error);
363 if (!commands)
364 return std::string();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700365
366 base::DictionaryValue req_json;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700367 req_json.SetString("oauthClientId", client_id_);
368 req_json.SetString("deviceDraft.deviceKind", device_kind_);
369 req_json.SetString("deviceDraft.systemName", system_name_);
370 req_json.SetString("deviceDraft.displayName", display_name_);
371 req_json.SetString("deviceDraft.channel.supportedType", "xmpp");
Alex Vakulenko45109442014-07-29 11:07:10 -0700372 req_json.Set("deviceDraft.commandDefs", commands.release());
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700373
374 std::string url = GetServiceURL("registrationTickets", {{"key", api_key_}});
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700375 auto resp_json = http::ParseJsonResponse(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700376 http::PostJson(url, &req_json, transport_, error).get(), nullptr, error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700377 if (!resp_json)
378 return std::string();
379
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700380 if (!resp_json->GetString("id", &ticket_id_)) {
381 Error::AddTo(error, kErrorDomainGCD, "unexpected_response",
382 "Device ID missing");
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700383 return std::string();
384 }
385
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700386 std::string auth_url = GetOAuthURL("auth", {
387 {"scope", "https://www.googleapis.com/auth/clouddevices"},
388 {"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"},
389 {"response_type", "code"},
390 {"client_id", client_id_}
391 });
392
393 base::DictionaryValue json;
394 json.SetString("ticket_id", ticket_id_);
395 json.SetString("auth_url", auth_url);
396
397 std::string ret;
398 base::JSONWriter::Write(&json, &ret);
399 return ret;
400}
401
402bool DeviceRegistrationInfo::FinishRegistration(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700403 const std::string& user_auth_code, ErrorPtr* error) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700404 if (ticket_id_.empty()) {
405 LOG(ERROR) << "Finish registration without ticket ID";
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700406 Error::AddTo(error, kErrorDomainBuffet, "registration_not_started",
407 "Device registration not started");
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700408 return false;
409 }
410
411 std::string url = GetServiceURL("registrationTickets/" + ticket_id_);
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700412 std::unique_ptr<http::Response> response;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700413 if (!user_auth_code.empty()) {
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700414 response = http::PostFormData(GetOAuthURL("token"), {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700415 {"code", user_auth_code},
416 {"client_id", client_id_},
417 {"client_secret", client_secret_},
418 {"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"},
419 {"grant_type", "authorization_code"}
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700420 }, transport_, error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700421 if (!response)
422 return false;
423
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700424 auto json_resp = ParseOAuthResponse(response.get(), error);
425 if (!json_resp)
426 return false;
427
428 std::string user_access_token;
429 std::string token_type;
430 if (!json_resp->GetString("access_token", &user_access_token) ||
431 !json_resp->GetString("token_type", &token_type)) {
432 Error::AddTo(error, kErrorDomainOAuth2, "unexpected_response",
433 "User access_token is missing in response");
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700434 return false;
435 }
436
437 base::DictionaryValue user_info;
438 user_info.SetString("userEmail", "me");
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700439 response = http::PatchJson(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700440 url, &user_info, {BuildAuthHeader(token_type, user_access_token)},
441 transport_, error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700442
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700443 auto json = http::ParseJsonResponse(response.get(), nullptr, error);
444 if (!json)
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700445 return false;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700446 }
447
448 std::string auth_code;
449 url += "/finalize?key=" + api_key_;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700450 LOG(INFO) << "Sending request to: " << url;
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700451 response = http::PostBinary(url, nullptr, 0, transport_, error);
452 if (!response)
453 return false;
454 auto json_resp = http::ParseJsonResponse(response.get(), nullptr, error);
455 if (!json_resp)
456 return false;
457 if (!response->IsSuccessful()) {
458 ParseGCDError(json_resp.get(), error);
459 return false;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700460 }
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700461 if (!json_resp->GetString("robotAccountEmail", &device_robot_account_) ||
462 !json_resp->GetString("robotAccountAuthorizationCode", &auth_code) ||
463 !json_resp->GetString("deviceDraft.id", &device_id_)) {
464 Error::AddTo(error, kErrorDomainGCD, "unexpected_response",
465 "Device account missing in response");
466 return false;
467 }
468
469 // Now get access_token and refresh_token
470 response = http::PostFormData(GetOAuthURL("token"), {
471 {"code", auth_code},
472 {"client_id", client_id_},
473 {"client_secret", client_secret_},
474 {"redirect_uri", "oob"},
475 {"scope", "https://www.googleapis.com/auth/clouddevices"},
476 {"grant_type", "authorization_code"}
477 }, transport_, error);
478 if (!response)
479 return false;
480
481 json_resp = ParseOAuthResponse(response.get(), error);
482 int expires_in = 0;
483 if (!json_resp ||
484 !json_resp->GetString("access_token", &access_token_) ||
485 !json_resp->GetString("refresh_token", &refresh_token_) ||
486 !json_resp->GetInteger("expires_in", &expires_in) ||
487 access_token_.empty() ||
488 refresh_token_.empty() ||
489 expires_in <= 0) {
490 Error::AddTo(error, kErrorDomainGCD, "unexpected_response",
491 "Device access_token missing in response");
492 return false;
493 }
494
495 access_token_expiration_ = base::Time::Now() +
496 base::TimeDelta::FromSeconds(expires_in);
497
498 Save();
499 return true;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700500}
501
502} // namespace buffet