Buffet: Phase 1 of GCD device registration workflow Implemented the basic device registration workflow in buffet daemon. Updated buffet_client to perform/test device registration: Check for device registration: buffet_client cr Getting registered device information: buffet_client di Begin registration (with all default values): buffet_client sr Begin registration with custom parameters: buffet_client sr "service_url=http://localhost/buffet&device_kind=coffeePot" Finalize registration: buffet_client fr 4/FsXprlpVsmPw6z7ro7aqU156Eh6V.0ktCYeVc3DwYEnp6UAPFm0GAey3PigI BUG=chromium:363348 TEST=unit tests passed. Change-Id: Id8a90b66fbdc366eaa9f62caa82a7cb0abc2e638 Reviewed-on: https://chromium-review.googlesource.com/195082 Tested-by: Alex Vakulenko <avakulenko@chromium.org> Reviewed-by: Chris Sosa <sosa@chromium.org> Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/buffet/buffet.conf b/buffet/buffet.conf index 7c3ee8c..66cd9ed 100644 --- a/buffet/buffet.conf +++ b/buffet/buffet.conf
@@ -9,4 +9,8 @@ stop on stopping system-services respawn +pre-start script + mkdir -p /var/lib/buffet +end script + exec buffet
diff --git a/buffet/buffet.gyp b/buffet/buffet.gyp index eb140be..12933d4 100644 --- a/buffet/buffet.gyp +++ b/buffet/buffet.gyp
@@ -32,6 +32,7 @@ 'dbus_constants.cc', 'dbus_manager.cc', 'dbus_utils.cc', + 'device_registration_info.cc', 'exported_property_set.cc', 'http_request.cc', 'http_transport_curl.cc', @@ -59,6 +60,9 @@ 'buffet_client.cc', 'dbus_constants.cc', ], + 'dependencies': [ + 'buffet_common', + ], }, { 'target_name': 'buffet_testrunner',
diff --git a/buffet/buffet_client.cc b/buffet/buffet_client.cc index edd6e87..dba2f4d 100644 --- a/buffet/buffet_client.cc +++ b/buffet/buffet_client.cc
@@ -8,17 +8,21 @@ #include <base/command_line.h> #include <base/logging.h> #include <base/memory/scoped_ptr.h> +#include <base/values.h> #include <dbus/bus.h> #include <dbus/object_proxy.h> #include <dbus/message.h> #include <sysexits.h> +#include <dbus/values_util.h> #include "buffet/dbus_constants.h" #include "buffet/dbus_manager.h" +#include "buffet/data_encoding.h" using namespace buffet::dbus_constants; namespace { +static const int default_timeout_ms = 1000; dbus::ObjectProxy* GetBuffetDBusProxy(dbus::Bus *bus, const std::string& object_path) { @@ -28,11 +32,10 @@ } bool CallTestMethod(dbus::ObjectProxy* proxy) { - int timeout_ms = 1000; dbus::MethodCall method_call(buffet::dbus_constants::kRootInterface, buffet::dbus_constants::kRootTestMethod); - scoped_ptr<dbus::Response> response(proxy->CallMethodAndBlock(&method_call, - timeout_ms)); + scoped_ptr<dbus::Response> response( + proxy->CallMethodAndBlock(&method_call, default_timeout_ms)); if (!response) { std::cout << "Failed to receive a response." << std::endl; return false; @@ -41,33 +44,119 @@ return true; } -bool CallManagerRegisterDevice(dbus::ObjectProxy* proxy, - const std::string& client_id, - const std::string& client_secret, - const std::string& api_key) { +bool CallManagerCheckDeviceRegistered(dbus::ObjectProxy* proxy) { dbus::MethodCall method_call( - buffet::dbus_constants::kManagerInterface, - buffet::dbus_constants::kManagerRegisterDeviceMethod); - dbus::MessageWriter writer(&method_call); - writer.AppendString(client_id); - writer.AppendString(client_secret); - writer.AppendString(api_key); - int timeout_ms = 1000; + buffet::dbus_constants::kManagerInterface, + buffet::dbus_constants::kManagerCheckDeviceRegistered); + scoped_ptr<dbus::Response> response( - proxy->CallMethodAndBlock(&method_call, timeout_ms)); + proxy->CallMethodAndBlock(&method_call, default_timeout_ms)); if (!response) { std::cout << "Failed to receive a response." << std::endl; return false; } dbus::MessageReader reader(response.get()); - std::string registration_id; - if (!reader.PopString(®istration_id)) { - std::cout << "No registration id in response." << std::endl; + std::string device_id; + if (!reader.PopString(&device_id)) { + std::cout << "No device ID in response." << std::endl; return false; } - std::cout << "Registration ID is " << registration_id << std::endl; + std::cout << "Device ID: " + << (device_id.empty() ? std::string("<unregistered>") : device_id) + << std::endl; + return true; +} + +bool CallManagerGetDeviceInfo(dbus::ObjectProxy* proxy) { + dbus::MethodCall method_call( + buffet::dbus_constants::kManagerInterface, + buffet::dbus_constants::kManagerGetDeviceInfo); + + scoped_ptr<dbus::Response> response( + proxy->CallMethodAndBlock(&method_call, default_timeout_ms)); + if (!response) { + std::cout << "Failed to receive a response." << std::endl; + return false; + } + + dbus::MessageReader reader(response.get()); + std::string device_info; + if (!reader.PopString(&device_info)) { + std::cout << "No device info in response." << std::endl; + return false; + } + + std::cout << "Device Info: " + << (device_info.empty() ? std::string("<unregistered>") : device_info) + << std::endl; + return true; +} + +bool CallManagerStartRegisterDevice( + dbus::ObjectProxy* proxy, + const std::map<std::string, std::shared_ptr<base::Value>>& params) { + dbus::MethodCall method_call( + buffet::dbus_constants::kManagerInterface, + buffet::dbus_constants::kManagerStartRegisterDevice); + dbus::MessageWriter writer(&method_call); + dbus::MessageWriter dict_writer(nullptr); + writer.OpenArray("{sv}", &dict_writer); + for (auto&& pair : params) { + dbus::MessageWriter dict_entry_writer(nullptr); + dict_writer.OpenDictEntry(&dict_entry_writer); + dict_entry_writer.AppendString(pair.first); + dbus::AppendBasicTypeValueDataAsVariant(&dict_entry_writer, + *pair.second.get()); + dict_writer.CloseContainer(&dict_entry_writer); + } + writer.CloseContainer(&dict_writer); + + static const int timeout_ms = 3000; + scoped_ptr<dbus::Response> response( + proxy->CallMethodAndBlock(&method_call, timeout_ms)); + if (!response) { + std::cout << "Failed to receive a response." << std::endl; + return false; + } + + dbus::MessageReader reader(response.get()); + std::string info; + if (!reader.PopString(&info)) { + std::cout << "No valid response." << std::endl; + return false; + } + + std::cout << "Registration started: " << info << std::endl; + return true; +} + +bool CallManagerFinishRegisterDevice(dbus::ObjectProxy* proxy, + const std::string& user_auth_code) { + dbus::MethodCall method_call( + buffet::dbus_constants::kManagerInterface, + buffet::dbus_constants::kManagerFinishRegisterDevice); + dbus::MessageWriter writer(&method_call); + writer.AppendString(user_auth_code); + static const int timeout_ms = 10000; + scoped_ptr<dbus::Response> response( + proxy->CallMethodAndBlock(&method_call, timeout_ms)); + if (!response) { + std::cout << "Failed to receive a response." << std::endl; + return false; + } + + dbus::MessageReader reader(response.get()); + std::string device_id; + if (!reader.PopString(&device_id)) { + std::cout << "No device ID in response." << std::endl; + return false; + } + + std::cout << "Device ID is " + << (device_id.empty() ? std::string("<unregistered>") : device_id) + << std::endl; return true; } @@ -78,9 +167,8 @@ buffet::dbus_constants::kManagerUpdateStateMethod); dbus::MessageWriter writer(&method_call); writer.AppendString(json_blob); - int timeout_ms = 1000; scoped_ptr<dbus::Response> response( - proxy->CallMethodAndBlock(&method_call, timeout_ms)); + proxy->CallMethodAndBlock(&method_call, default_timeout_ms)); if (!response) { std::cout << "Failed to receive a response." << std::endl; return false; @@ -91,8 +179,12 @@ void usage() { std::cerr << "Possible commands:" << std::endl; std::cerr << " " << kRootTestMethod << std::endl; - std::cerr << " " << kManagerRegisterDeviceMethod - << " " << " <client id> <client secret> <api key>" << std::endl; + std::cerr << " " << kManagerCheckDeviceRegistered << std::endl; + std::cerr << " " << kManagerGetDeviceInfo << std::endl; + std::cerr << " " << kManagerStartRegisterDevice + << " param1 = val1¶m2 = val2..." << std::endl; + std::cerr << " " << kManagerFinishRegisterDevice + << " device_id" << std::endl; std::cerr << " " << kManagerUpdateStateMethod << std::endl; } @@ -107,39 +199,86 @@ scoped_refptr<dbus::Bus> bus(new dbus::Bus(options)); CommandLine::StringVector args = cl->GetArgs(); - if (args.size() < 1) { + if (args.empty()) { usage(); return EX_USAGE; } // Pop the command off of the args list. - std::string command = args[0]; + std::string command = args.front(); args.erase(args.begin()); bool success = false; if (command.compare(kRootTestMethod) == 0) { auto proxy = GetBuffetDBusProxy( bus, buffet::dbus_constants::kRootServicePath); success = CallTestMethod(proxy); - } else if (command.compare(kManagerRegisterDeviceMethod) == 0) { - if (args.size() != 3) { + } else if (command.compare(kManagerCheckDeviceRegistered) == 0 || + command.compare("cr") == 0) { + if (!args.empty()) { std::cerr << "Invalid number of arguments for " - << "Manager.RegisterDevice" << std::endl; + << "Manager." << kManagerCheckDeviceRegistered << std::endl; + usage(); + return EX_USAGE; + } + auto proxy = GetBuffetDBusProxy( + bus, buffet::dbus_constants::kManagerServicePath); + success = CallManagerCheckDeviceRegistered(proxy); + } else if (command.compare(kManagerGetDeviceInfo) == 0 || + command.compare("di") == 0) { + if (!args.empty()) { + std::cerr << "Invalid number of arguments for " + << "Manager." << kManagerGetDeviceInfo << std::endl; + usage(); + return EX_USAGE; + } + auto proxy = GetBuffetDBusProxy( + bus, buffet::dbus_constants::kManagerServicePath); + success = CallManagerGetDeviceInfo(proxy); + } else if (command.compare(kManagerStartRegisterDevice) == 0 || + command.compare("sr") == 0) { + if (args.size() > 1) { + std::cerr << "Invalid number of arguments for " + << "Manager." << kManagerStartRegisterDevice << std::endl; usage(); return EX_USAGE; } auto proxy = GetBuffetDBusProxy( bus, buffet::dbus_constants::kManagerServicePath); - success = CallManagerRegisterDevice(proxy, args[0], args[1], args[2]); - } else if (command.compare(kManagerUpdateStateMethod) == 0) { + std::map<std::string, std::shared_ptr<base::Value>> params; + + if (!args.empty()) { + auto key_values = chromeos::data_encoding::WebParamsDecode(args.front()); + for (auto&& pair : key_values) { + params.insert(std::make_pair( + pair.first, std::shared_ptr<base::Value>( + base::Value::CreateStringValue(pair.second)))); + } + } + + success = CallManagerStartRegisterDevice(proxy, params); + } else if (command.compare(kManagerFinishRegisterDevice) == 0 || + command.compare("fr") == 0) { + if (args.size() > 1) { + std::cerr << "Invalid number of arguments for " + << "Manager." << kManagerFinishRegisterDevice << std::endl; + usage(); + return EX_USAGE; + } + auto proxy = GetBuffetDBusProxy( + bus, buffet::dbus_constants::kManagerServicePath); + success = CallManagerFinishRegisterDevice( + proxy, !args.empty() ? args.front() : std::string()); + } else if (command.compare(kManagerUpdateStateMethod) == 0 || + command.compare("us") == 0) { if (args.size() != 1) { std::cerr << "Invalid number of arguments for " - << "Manager.UpdateState" << std::endl; + << "Manager." << kManagerUpdateStateMethod << std::endl; usage(); return EX_USAGE; } auto proxy = GetBuffetDBusProxy( bus, buffet::dbus_constants::kManagerServicePath); - success = CallManagerUpdateState(proxy, args[0]); + success = CallManagerUpdateState(proxy, args.front()); } else { std::cerr << "Unknown command: " << command << std::endl; usage();
diff --git a/buffet/dbus_constants.cc b/buffet/dbus_constants.cc index 746058e..f352bbe 100644 --- a/buffet/dbus_constants.cc +++ b/buffet/dbus_constants.cc
@@ -18,8 +18,11 @@ const char kManagerInterface[] = "org.chromium.Buffet.Manager"; const char kManagerServicePath[] = "/org/chromium/Buffet/Manager"; -const char kManagerUpdateStateMethod[] = "UpdateState"; -const char kManagerRegisterDeviceMethod[] = "RegisterDevice"; +const char kManagerCheckDeviceRegistered[] = "CheckDeviceRegistered"; +const char kManagerGetDeviceInfo[] = "GetDeviceInfo"; +const char kManagerStartRegisterDevice[] = "StartRegisterDevice"; +const char kManagerFinishRegisterDevice[] = "FinishRegisterDevice"; +const char kManagerUpdateStateMethod[] = "UpdateState"; } // namespace dbus_constants
diff --git a/buffet/dbus_constants.h b/buffet/dbus_constants.h index e30fdde..13c8a71 100644 --- a/buffet/dbus_constants.h +++ b/buffet/dbus_constants.h
@@ -24,8 +24,11 @@ extern const char kManagerServicePath[]; // Methods exposed as part of kManagerInterface. +extern const char kManagerCheckDeviceRegistered[]; +extern const char kManagerGetDeviceInfo[]; +extern const char kManagerStartRegisterDevice[]; +extern const char kManagerFinishRegisterDevice[]; extern const char kManagerUpdateStateMethod[]; -extern const char kManagerRegisterDeviceMethod[]; } // namespace dbus_constants
diff --git a/buffet/device_registration_info.cc b/buffet/device_registration_info.cc new file mode 100644 index 0000000..c2b7780 --- /dev/null +++ b/buffet/device_registration_info.cc
@@ -0,0 +1,471 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "buffet/device_registration_info.h" + +#include <base/json/json_reader.h> +#include <base/json/json_writer.h> +#include <base/values.h> +#include <base/file_util.h> +#include <memory> + +#include "buffet/http_utils.h" +#include "buffet/mime_utils.h" +#include "buffet/string_utils.h" +#include "buffet/data_encoding.h" + +using namespace chromeos::http; +using namespace chromeos::data_encoding; + +namespace { +// Persistent keys +const char kClientId[] = "client_id"; +const char kClientSecret[] = "client_secret"; +const char kApiKey[] = "api_key"; +const char kRefreshToken[] = "refresh_token"; +const char kDeviceId[] = "device_id"; +const char kOAuthURL[] = "oauth_url"; +const char kServiceURL[] = "service_url"; +const char kRobotAccount[] = "robot_account"; +// Transient keys +const char kDeviceKind[] = "device_kind"; +const char kSystemName[] = "system_name"; +const char kDisplayName[] = "display_name"; + +const base::FilePath::CharType kDeviceInfoFilePath[] = + FILE_PATH_LITERAL("/var/lib/buffet/device_reg_info"); + +bool GetParamValue( + const std::map<std::string, std::shared_ptr<base::Value>>& params, + const std::string& param_name, + std::string* param_value) { + auto p = params.find(param_name); + if (p == params.end()) + return false; + + return p->second->GetAsString(param_value); +} + +std::pair<std::string, std::string> BuildAuthHeader( + const std::string& access_token_type, + const std::string& access_token) { + std::string authorization = chromeos::string_utils::Join(' ', + access_token_type, + access_token); + return {request_header::kAuthorization, authorization}; +} + +std::unique_ptr<base::DictionaryValue> ParseOAuthResponse( + const Response* response, std::string* error_message) { + int code = 0; + auto resp = ParseJsonResponse(response, &code, error_message); + if (resp && code >= status_code::BadRequest) { + if (error_message) { + error_message->clear(); + std::string error_code, error; + if (resp->GetString("error", &error_code) && + resp->GetString("error_description", &error)) { + *error_message = error_code + " (" + error + ")"; + } else { + *error_message = "Unexpected OAuth error"; + } + } + return std::unique_ptr<base::DictionaryValue>(); + } + return resp; +} + +std::string BuildURL(std::string url, + const std::string& subpath, + const WebParamList& params) { + if (!subpath.empty()) { + if (!url.empty() && url.back() != '/') + url += '/'; + url += subpath; + } + + if (!params.empty()) { + url += '?'; + url += WebParamsEncode(params); + } + return url; +} + + +} // anonymous namespace + +namespace buffet { + +std::pair<std::string, std::string> + DeviceRegistrationInfo::GetAuthorizationHeader() const { + return BuildAuthHeader(/*"Bearer"*/"OAuth", access_token_); +} + +std::string DeviceRegistrationInfo::GetServiceURL( + const std::string& subpath, const WebParamList& params) const { + return BuildURL(service_url_, subpath, params); +} + +std::string DeviceRegistrationInfo::GetDeviceURL( + const std::string& subpath, const WebParamList& params) const { + CHECK(!device_id_.empty()) << "Must have a valid device ID"; + std::string path = "devices/" + device_id_; + if (!subpath.empty()) { + path += '/' + subpath; + } + return GetServiceURL(path, params); +} + +std::string DeviceRegistrationInfo::GetOAuthURL(const std::string& subpath, + const WebParamList& params) const { + return BuildURL(oauth_url_, subpath, params); +} + +std::string DeviceRegistrationInfo::GetDeviceId() { + return CheckRegistration() ? device_id_ : std::string(); +} + +bool DeviceRegistrationInfo::Load() { + // TODO(avakulenko): Figure out security implications of storing + // this data unencrypted. + std::string json; + if (!base::ReadFileToString(base::FilePath(kDeviceInfoFilePath), &json)) + return false; + + auto value = std::unique_ptr<base::Value>(base::JSONReader::Read(json)); + const base::DictionaryValue* dict = nullptr; + if (!value || !value->GetAsDictionary(&dict)) + return false; + + // Get the values into temp variables first to make sure we can get + // all the data correctly before changing the state of this object. + std::string client_id; + if (!dict->GetString(kClientId, &client_id)) + return false; + std::string client_secret; + if (!dict->GetString(kClientSecret, &client_secret)) + return false; + std::string api_key; + if (!dict->GetString(kApiKey, &api_key)) + return false; + std::string refresh_token; + if (!dict->GetString(kRefreshToken, &refresh_token)) + return false; + std::string device_id; + if (!dict->GetString(kDeviceId, &device_id)) + return false; + std::string oauth_url; + if (!dict->GetString(kOAuthURL, &oauth_url)) + return false; + std::string service_url; + if (!dict->GetString(kServiceURL, &service_url)) + return false; + std::string device_robot_account; + if (!dict->GetString(kRobotAccount, &device_robot_account)) + return false; + + client_id_ = client_id; + client_secret_ = client_secret; + api_key_ = api_key; + refresh_token_ = refresh_token; + device_id_ = device_id; + oauth_url_ = oauth_url; + service_url_ = service_url; + device_robot_account_ = device_robot_account; + return true; +} + +bool DeviceRegistrationInfo::Save() const { + // TODO(avakulenko): Figure out security implications of storing + // this data unencrypted. + base::DictionaryValue dict; + dict.SetString(kClientId, client_id_); + dict.SetString(kClientSecret, client_secret_); + dict.SetString(kApiKey, api_key_); + dict.SetString(kRefreshToken, refresh_token_); + dict.SetString(kDeviceId, device_id_); + dict.SetString(kOAuthURL, oauth_url_); + dict.SetString(kServiceURL, service_url_); + dict.SetString(kRobotAccount, device_robot_account_); + + std::string json; + base::JSONWriter::WriteWithOptions(&dict, + base::JSONWriter::OPTIONS_PRETTY_PRINT, + &json); + int count = file_util::WriteFile(base::FilePath(kDeviceInfoFilePath), + json.data(), static_cast<int>(json.size())); + + return (count > 0); +} + +bool DeviceRegistrationInfo::CheckRegistration() { + LOG(INFO) << "Checking device registration record."; + if (refresh_token_.empty() || + device_id_.empty() || + device_robot_account_.empty()) { + LOG(INFO) << "No valid device registration record found."; + return false; + } + + LOG(INFO) << "Device registration record found."; + return ValidateAndRefreshAccessToken(); +} + +bool DeviceRegistrationInfo::ValidateAndRefreshAccessToken() { + LOG(INFO) << " Checking access token expiration."; + if (!access_token_.empty() && + !access_token_expiration_.is_null() && + access_token_expiration_ > base::Time::Now()) { + LOG(INFO) << "Access token is still valid."; + return true; + } + + auto response = PostFormData(GetOAuthURL("token"), { + {"refresh_token", refresh_token_}, + {"client_id", client_id_}, + {"client_secret", client_secret_}, + {"grant_type", "refresh_token"}, + }); + if (!response) + return false; + + std::string error; + auto json = ParseOAuthResponse(response.get(), &error); + if (!json) { + LOG(ERROR) << "Unable to refresh access token: " << error; + return false; + } + + int expires_in = 0; + if (!json->GetString("access_token", &access_token_) || + !json->GetInteger("expires_in", &expires_in) || + access_token_.empty() || + expires_in <= 0) { + LOG(ERROR) << "Access token unavailable."; + return false; + } + + access_token_expiration_ = base::Time::Now() + + base::TimeDelta::FromSeconds(expires_in); + + LOG(INFO) << "Access token is refreshed for additional " << expires_in + << " seconds."; + return true; +} + +std::unique_ptr<base::Value> DeviceRegistrationInfo::GetDeviceInfo() { + if (!CheckRegistration()) + return std::unique_ptr<base::Value>(); + + auto response = Get(GetDeviceURL(), {GetAuthorizationHeader()}); + int status_code = 0; + std::unique_ptr<base::Value> device_info = + ParseJsonResponse(response.get(), &status_code, nullptr); + + if (device_info) { + if (status_code >= status_code::BadRequest) { + LOG(WARNING) << "Failed to retrieve the device info. Response code = " + << status_code; + return std::unique_ptr<base::Value>(); + } + } + return device_info; +} + +bool CheckParam(const std::string& param_name, + const std::string& param_value, + std::string* error_msg) { + if (!param_value.empty()) + return true; + + if (error_msg) + *error_msg = "Parameter " + param_name + " not specified"; + return false; +} + +std::string DeviceRegistrationInfo::StartRegistration( + const std::map<std::string, std::shared_ptr<base::Value>>& params, + std::string* error_msg) { + GetParamValue(params, kClientId, &client_id_); + GetParamValue(params, kClientSecret, &client_secret_); + GetParamValue(params, kApiKey, &api_key_); + GetParamValue(params, kDeviceId, &device_id_); + GetParamValue(params, kDeviceKind, &device_kind_); + GetParamValue(params, kSystemName, &system_name_); + GetParamValue(params, kDisplayName, &display_name_); + GetParamValue(params, kOAuthURL, &oauth_url_); + GetParamValue(params, kServiceURL, &service_url_); + + if (!CheckParam(kClientId, client_id_, error_msg)) + return std::string(); + if (!CheckParam(kClientSecret, client_secret_, error_msg)) + return std::string(); + if (!CheckParam(kApiKey, api_key_, error_msg)) + return std::string(); + if (!CheckParam(kDeviceKind, device_kind_, error_msg)) + return std::string(); + if (!CheckParam(kSystemName, system_name_, error_msg)) + return std::string(); + if (!CheckParam(kOAuthURL, oauth_url_, error_msg)) + return std::string(); + if (!CheckParam(kServiceURL, service_url_, error_msg)) + return std::string(); + + std::vector<std::pair<std::string, std::vector<std::string>>> commands = { + {"SetDeviceConfiguration", {"data"}} + }; + + base::DictionaryValue req_json; + base::ListValue* set_device_configuration_params = new base::ListValue; + base::DictionaryValue* param1 = new base::DictionaryValue; + param1->SetString("name", "data"); + set_device_configuration_params->Append(param1); + + base::ListValue* vendor_commands = new base::ListValue; + for (auto&& pair : commands) { + base::ListValue* params = new base::ListValue; + for (auto&& param_name : pair.second) { + base::DictionaryValue* param = new base::DictionaryValue; + param->SetString("name", param_name); + params->Append(param); + } + base::DictionaryValue* command = new base::DictionaryValue; + command->SetString("name", pair.first); + command->Set("parameter", params); + vendor_commands->Append(command); + } + + req_json.SetString("oauthClientId", client_id_); + req_json.SetString("deviceDraft.deviceKind", device_kind_); + req_json.SetString("deviceDraft.systemName", system_name_); + req_json.SetString("deviceDraft.displayName", display_name_); + req_json.SetString("deviceDraft.channel.supportedType", "xmpp"); + req_json.Set("deviceDraft.commands.base.vendorCommands", vendor_commands); + + std::string url = GetServiceURL("registrationTickets", {{"key", api_key_}}); + auto resp_json = ParseJsonResponse(PostJson(url, &req_json).get(), + nullptr, error_msg); + if (!resp_json) + return std::string(); + + const base::DictionaryValue* resp_dict = nullptr; + if (!resp_json->GetAsDictionary(&resp_dict)) { + if (error_msg) + *error_msg = "Invalid response received"; + return std::string(); + } + + if (!resp_dict->GetString("id", &ticket_id_)) + return std::string(); + + std::string auth_url = GetOAuthURL("auth", { + {"scope", "https://www.googleapis.com/auth/clouddevices"}, + {"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"}, + {"response_type", "code"}, + {"client_id", client_id_} + }); + + base::DictionaryValue json; + json.SetString("ticket_id", ticket_id_); + json.SetString("auth_url", auth_url); + + std::string ret; + base::JSONWriter::Write(&json, &ret); + return ret; +} + +bool DeviceRegistrationInfo::FinishRegistration( + const std::string& user_auth_code) { + if (ticket_id_.empty()) { + LOG(ERROR) << "Finish registration without ticket ID"; + return false; + } + + std::string url = GetServiceURL("registrationTickets/" + ticket_id_); + std::unique_ptr<Response> response; + if (!user_auth_code.empty()) { + std::string user_access_token; + response = PostFormData(GetOAuthURL("token"), { + {"code", user_auth_code}, + {"client_id", client_id_}, + {"client_secret", client_secret_}, + {"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"}, + {"grant_type", "authorization_code"} + }); + if (!response) + return false; + + auto json_resp = ParseOAuthResponse(response.get(), nullptr); + if (!json_resp || + !json_resp->GetString("access_token", &user_access_token)) { + return false; + } + + base::DictionaryValue user_info; + user_info.SetString("userEmail", "me"); + response = PatchJson(url, &user_info, + {BuildAuthHeader("Bearer", user_access_token)}); + + std::string error; + auto json = ParseJsonResponse(response.get(), nullptr, &error); + if (!json) { + LOG(ERROR) << "Error populating user info: " << error; + return false; + } + } + + std::string auth_code; + url += "/finalize?key=" + api_key_; + do { + LOG(INFO) << "Sending request to: " << url; + response = PostBinary(url, nullptr, 0); + if (response) { + if (response->GetStatusCode() == status_code::BadRequest) + sleep(1); + } + } + while (response && + response->GetStatusCode() == status_code::BadRequest); + if (response && + response->GetStatusCode() == status_code::Ok) { + auto json_resp = ParseJsonResponse(response.get(), nullptr, nullptr); + if (json_resp && + json_resp->GetString("robotAccountEmail", &device_robot_account_) && + json_resp->GetString("robotAccountAuthorizationCode", &auth_code) && + json_resp->GetString("deviceDraft.id", &device_id_)) { + // Now get access_token and refresh_token + response = PostFormData(GetOAuthURL("token"), { + {"code", auth_code}, + {"client_id", client_id_}, + {"client_secret", client_secret_}, + {"redirect_uri", "oob"}, + {"scope", "https://www.googleapis.com/auth/clouddevices"}, + {"grant_type", "authorization_code"} + }); + if (!response) + return false; + + json_resp = ParseOAuthResponse(response.get(), nullptr); + int expires_in = 0; + if (!json_resp || + !json_resp->GetString("access_token", &access_token_) || + !json_resp->GetString("refresh_token", &refresh_token_) || + !json_resp->GetInteger("expires_in", &expires_in) || + access_token_.empty() || + refresh_token_.empty() || + expires_in <= 0) { + LOG(ERROR) << "Access token unavailable"; + return false; + } + + access_token_expiration_ = base::Time::Now() + + base::TimeDelta::FromSeconds(expires_in); + + Save(); + } + } + return true; +} + +} // namespace buffet
diff --git a/buffet/device_registration_info.h b/buffet/device_registration_info.h new file mode 100644 index 0000000..88a655b --- /dev/null +++ b/buffet/device_registration_info.h
@@ -0,0 +1,122 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUFFET_DEVICE_INFO_H_ +#define BUFFET_DEVICE_INFO_H_ + +#include <base/basictypes.h> +#include <base/time/time.h> +#include <string> +#include <map> +#include <memory> + +#include "buffet/data_encoding.h" + +namespace base { + class Value; +} // namespace base + +namespace buffet { + +// The DeviceRegistrationInfo class represents device registration information. + class DeviceRegistrationInfo { + public: + DeviceRegistrationInfo() = default; + + // Returns the authorization HTTP header that can be used to talk + // to GCD server for authenticated device communication. + // Make sure CheckRegistration() is called before this call. + std::pair<std::string, std::string> GetAuthorizationHeader() const; + + // Returns the GCD service request URL. If |subpath| is specified, it is + // appended to the base URL which is normally + // https://www.googleapis.com/clouddevices/v1/". + // If |params| are specified, each key-value pair is formatted using + // chromeos::data_encoding::WebParamsEncode() and appended to URL as a query + // string. + // So, calling: + // GetServiceURL("ticket", {{"key","apiKey"}}) + // will return something like: + // https://www.googleapis.com/clouddevices/v1/ticket?key=apiKey + std::string GetServiceURL( + const std::string& subpath = {}, + const chromeos::data_encoding::WebParamList& params = {}) const; + + // Returns a service URL to access the registered device on GCD server. + // The base URL used to construct the full URL looks like this: + // https://www.googleapis.com/clouddevices/v1/devices/<device_id>/ + std::string GetDeviceURL( + const std::string& subpath = {}, + const chromeos::data_encoding::WebParamList& params = {}) const; + + // Similar to GetServiceURL, GetOAuthURL() returns a URL of OAuth 2.0 server. + // The base URL used is https://accounts.google.com/o/oauth2/. + std::string GetOAuthURL( + const std::string& subpath = {}, + const chromeos::data_encoding::WebParamList& params = {}) const; + + // Returns the registered device ID (GUID) or empty string if failed + std::string GetDeviceId(); + + // Loads the device registration information from cache. + bool Load(); + + // Checks for the valid device registration as well as refreshes + // the device access token, if available. + bool CheckRegistration(); + + // Gets the full device description JSON object, or nullptr if + // the device is not registered or communication failure. + std::unique_ptr<base::Value> GetDeviceInfo(); + + // Starts device registration procedure. |params| are a list of + // key-value pairs of device information, such as client_id, client_secret, + // and so on. If a particular key-value pair is omitted, a default value + // is used when possible. Returns a device claim ID on success. + std::string StartRegistration( + const std::map<std::string, std::shared_ptr<base::Value>>& params, + std::string* error_msg); + + // Finalizes the device registration. If |user_auth_code| is provided, then + // the device record is populated with user email on user's behalf. Otherwise + // the user is responsible to issue a PATCH request to provide a valid + // email address before calling FinishRegistration. + bool FinishRegistration(const std::string& user_auth_code); + + private: + // Saves the device registration to cache. + bool Save() const; + + // Makes sure the access token is available and up-to-date. + bool ValidateAndRefreshAccessToken(); + + // Persistent data. Some of default values for testing purposes are used. + // TODO(avakulenko): remove these default values in the future. + // http://crbug.com/364692 + std::string client_id_ = + "583509257718-lnmeofvjef3b1tm33sbjmckfnumfvn8j.apps.googleusercontent.com"; + std::string client_secret_ = "6fzZwQhgnsHhvYYvvFdpv5SD"; + std::string api_key_ = "AIzaSyAp7KVig5m9g4LWWKr79mTS8sXWfUU6w9g"; + std::string refresh_token_; + std::string device_id_; + std::string device_robot_account_; + std::string oauth_url_ = "https://accounts.google.com/o/oauth2/"; + std::string service_url_ = + "https://www-googleapis-staging.sandbox.google.com/" + "clouddevices/v1/"; + + // Transient data + std::string access_token_; + base::Time access_token_expiration_; + std::string ticket_id_; + std::string device_kind_ = "vendor"; + std::string system_name_ = "coffee_pot"; + std::string display_name_ = "Coffee Pot"; + + DISALLOW_COPY_AND_ASSIGN(DeviceRegistrationInfo); +}; + +} // namespace buffet + +#endif // BUFFET_DEVICE_INFO_H_
diff --git a/buffet/http_transport_curl.cc b/buffet/http_transport_curl.cc index 2a9c228..be204d9 100644 --- a/buffet/http_transport_curl.cc +++ b/buffet/http_transport_curl.cc
@@ -185,10 +185,10 @@ curl_easy_setopt(curl_handle_, CURLOPT_CUSTOMREQUEST, method_.c_str()); } - VLOG_IF(2, !request_data_.empty()) << "Request data (" - << request_data_.size() << "): " + LOG(INFO) << "Request data (" << request_data_.size() << ")"; + VLOG_IF(2, !request_data_.empty()) << "Raw request data: " << std::string(reinterpret_cast<const char*>(request_data_.data()), - request_data_.size()); + request_data_.size()); // Setup HTTP response data. if (method_ != request_type::kHead) {
diff --git a/buffet/http_utils.cc b/buffet/http_utils.cc index 7529796..8210221 100644 --- a/buffet/http_utils.cc +++ b/buffet/http_utils.cc
@@ -16,13 +16,14 @@ namespace chromeos { namespace http { -std::unique_ptr<Response> Get(const std::string& url) { - Request request(url); - return request.GetResponse(); +std::unique_ptr<Response> Get(const std::string& url, + const HeaderList& headers) { + return SendRequest(request_type::kGet, url, nullptr, 0, nullptr, headers); } -std::string GetAsString(const std::string& url) { - auto resp = Get(url); +std::string GetAsString(const std::string& url, + const HeaderList& headers) { + auto resp = Get(url, headers); return resp ? resp->GetDataAsString() : std::string(); }
diff --git a/buffet/http_utils.h b/buffet/http_utils.h index 91e12b5..2979ed3 100644 --- a/buffet/http_utils.h +++ b/buffet/http_utils.h
@@ -37,12 +37,19 @@ const HeaderList& headers); // Performs a simple GET request and returns the data as a string. -std::string GetAsString(const std::string& url); +std::string GetAsString(const std::string& url, const HeaderList& headers); +inline std::string GetAsString(const std::string& url) { + return GetAsString(url, HeaderList()); +} // Performs a GET request. Success status, returned data and additional // information (such as returned HTTP headers) can be obtained from // the returned Response object. -std::unique_ptr<Response> Get(const std::string& url); +std::unique_ptr<Response> Get(const std::string& url, + const HeaderList& headers); +inline std::unique_ptr<Response> Get(const std::string& url) { + return Get(url, HeaderList()); +} // Performs a HEAD request. Success status and additional // information (such as returned HTTP headers) can be obtained from
diff --git a/buffet/manager.cc b/buffet/manager.cc index b23d639..a6c8d9f 100644 --- a/buffet/manager.cc +++ b/buffet/manager.cc
@@ -7,6 +7,8 @@ #include <base/bind.h> #include <base/bind_helpers.h> #include <dbus/object_path.h> +#include <dbus/values_util.h> +#include <base/json/json_writer.h> #include "buffet/async_event_sequencer.h" #include "buffet/dbus_constants.h" @@ -38,14 +40,47 @@ new dbus_utils::AsyncEventSequencer()); exported_object_->ExportMethod( dbus_constants::kManagerInterface, - dbus_constants::kManagerRegisterDeviceMethod, + dbus_constants::kManagerCheckDeviceRegistered, dbus_utils::GetExportableDBusMethod( - base::Bind(&Manager::HandleRegisterDevice, + base::Bind(&Manager::HandleCheckDeviceRegistered, base::Unretained(this))), sequencer->GetExportHandler( dbus_constants::kManagerInterface, - dbus_constants::kManagerRegisterDeviceMethod, - "Failed exporting RegisterDevice method", + dbus_constants::kManagerCheckDeviceRegistered, + "Failed exporting CheckDeviceRegistered method", + true)); + exported_object_->ExportMethod( + dbus_constants::kManagerInterface, + dbus_constants::kManagerGetDeviceInfo, + dbus_utils::GetExportableDBusMethod( + base::Bind(&Manager::HandleGetDeviceInfo, + base::Unretained(this))), + sequencer->GetExportHandler( + dbus_constants::kManagerInterface, + dbus_constants::kManagerGetDeviceInfo, + "Failed exporting GetDeviceInfo method", + true)); + exported_object_->ExportMethod( + dbus_constants::kManagerInterface, + dbus_constants::kManagerStartRegisterDevice, + dbus_utils::GetExportableDBusMethod( + base::Bind(&Manager::HandleStartRegisterDevice, + base::Unretained(this))), + sequencer->GetExportHandler( + dbus_constants::kManagerInterface, + dbus_constants::kManagerStartRegisterDevice, + "Failed exporting StartRegisterDevice method", + true)); + exported_object_->ExportMethod( + dbus_constants::kManagerInterface, + dbus_constants::kManagerFinishRegisterDevice, + dbus_utils::GetExportableDBusMethod( + base::Bind(&Manager::HandleFinishRegisterDevice, + base::Unretained(this))), + sequencer->GetExportHandler( + dbus_constants::kManagerInterface, + dbus_constants::kManagerFinishRegisterDevice, + "Failed exporting FinishRegisterDevice method", true)); exported_object_->ExportMethod( dbus_constants::kManagerInterface, @@ -65,38 +100,122 @@ properties_->Init( sequencer->GetHandler("Manager properties export failed.", true)); sequencer->OnAllTasksCompletedCall({cb}); + device_info_.Load(); } -scoped_ptr<dbus::Response> Manager::HandleRegisterDevice( +scoped_ptr<dbus::Response> Manager::HandleCheckDeviceRegistered( dbus::MethodCall* method_call) { // Read the parameters to the method. dbus::MessageReader reader(method_call); - if (!reader.HasMoreData()) { - return GetBadArgsError(method_call, "No parameters to RegisterDevice"); - } - std::string client_id, client_secret, api_key; - if (!reader.PopString(&client_id)) { - return GetBadArgsError(method_call, "Failed to read client_id"); - } - if (!reader.PopString(&client_secret)) { - return GetBadArgsError(method_call, "Failed to read client_secret"); - } - if (!reader.PopString(&api_key)) { - return GetBadArgsError(method_call, "Failed to read api_key"); - } if (reader.HasMoreData()) { - return GetBadArgsError( - method_call, "Too many parameters to RegisterDevice"); + return GetBadArgsError(method_call, + "Too many parameters to CheckDeviceRegistered"); } - LOG(INFO) << "Received call to Manager.RegisterDevice()"; - // TODO(wiley): Do something with these parameters to register the device. + LOG(INFO) << "Received call to Manager.CheckDeviceRegistered()"; + + bool registered = device_info_.CheckRegistration(); // Send back our response. scoped_ptr<dbus::Response> response( dbus::Response::FromMethodCall(method_call)); dbus::MessageWriter writer(response.get()); - writer.AppendString("<registration ticket id>"); + writer.AppendString(registered ? device_info_.GetDeviceId() : std::string()); + return response.Pass(); +} + +scoped_ptr<dbus::Response> Manager::HandleGetDeviceInfo( + dbus::MethodCall* method_call) { + // Read the parameters to the method. + dbus::MessageReader reader(method_call); + if (reader.HasMoreData()) { + return GetBadArgsError(method_call, + "Too many parameters to GetDeviceInfo"); + } + + LOG(INFO) << "Received call to Manager.GetDeviceInfo()"; + + std::string device_info_str; + std::unique_ptr<base::Value> device_info = device_info_.GetDeviceInfo(); + if (device_info) + base::JSONWriter::Write(device_info.get(), &device_info_str); + + // Send back our response. + scoped_ptr<dbus::Response> response( + dbus::Response::FromMethodCall(method_call)); + dbus::MessageWriter writer(response.get()); + writer.AppendString(device_info_str); + return response.Pass(); +} + +scoped_ptr<dbus::Response> Manager::HandleStartRegisterDevice( + dbus::MethodCall* method_call) { + // Read the parameters to the method. + dbus::MessageReader reader(method_call); + if (!reader.HasMoreData()) { + return GetBadArgsError(method_call, "No parameters to StartRegisterDevice"); + } + + dbus::MessageReader array_reader(nullptr); + if (!reader.PopArray(&array_reader)) + return GetBadArgsError(method_call, "Failed to read the parameter array"); + std::map<std::string, std::shared_ptr<base::Value>> params; + while (array_reader.HasMoreData()) { + dbus::MessageReader dict_entry_reader(nullptr); + if (!array_reader.PopDictEntry(&dict_entry_reader)) + return GetBadArgsError(method_call, "Failed to get a call parameter"); + std::string key; + if (!dict_entry_reader.PopString(&key)) + return GetBadArgsError(method_call, "Failed to read parameter key"); + base::Value* value = dbus::PopDataAsValue(&dict_entry_reader); + if (!value) + return GetBadArgsError(method_call, "Failed to read parameter value"); + params.insert(std::make_pair(key, std::shared_ptr<base::Value>(value))); + } + if (reader.HasMoreData()) + return GetBadArgsError(method_call, + "Too many parameters to StartRegisterDevice"); + + LOG(INFO) << "Received call to Manager.StartRegisterDevice()"; + + std::string error_msg; + std::string id = device_info_.StartRegistration(params, &error_msg); + if(id.empty()) + return GetBadArgsError(method_call, error_msg); + + // Send back our response. + scoped_ptr<dbus::Response> response( + dbus::Response::FromMethodCall(method_call)); + dbus::MessageWriter writer(response.get()); + writer.AppendString(id); + return response.Pass(); +} + +scoped_ptr<dbus::Response> Manager::HandleFinishRegisterDevice( + dbus::MethodCall* method_call) { + // Read the parameters to the method. + dbus::MessageReader reader(method_call); + if (!reader.HasMoreData()) { + return GetBadArgsError(method_call, + "No parameters to FinishRegisterDevice"); + } + std::string user_auth_code; + if (!reader.PopString(&user_auth_code)) { + return GetBadArgsError(method_call, "Failed to read UserAuthCode"); + } + if (reader.HasMoreData()) { + return GetBadArgsError(method_call, + "Too many parameters to FinishRegisterDevice"); + } + + LOG(INFO) << "Received call to Manager.FinishRegisterDevice()"; + bool success = device_info_.FinishRegistration(user_auth_code); + + // Send back our response. + scoped_ptr<dbus::Response> response( + dbus::Response::FromMethodCall(method_call)); + dbus::MessageWriter writer(response.get()); + writer.AppendString(success ? device_info_.GetDeviceId() : std::string()); return response.Pass(); }
diff --git a/buffet/manager.h b/buffet/manager.h index 95d0548..ad66252 100644 --- a/buffet/manager.h +++ b/buffet/manager.h
@@ -7,11 +7,14 @@ #include <base/basictypes.h> #include <base/memory/scoped_ptr.h> +#include <base/values.h> #include <dbus/message.h> #include <dbus/object_path.h> +#include <memory> #include "buffet/dbus_constants.h" #include "buffet/exported_property_set.h" +#include "buffet/device_registration_info.h" namespace buffet { @@ -40,8 +43,17 @@ virtual ~Properties() {} }; - // Handles calls to org.chromium.Buffet.Manager.RegisterDevice(). - scoped_ptr<dbus::Response> HandleRegisterDevice( + // Handles calls to org.chromium.Buffet.Manager.CheckDeviceRegistered(). + scoped_ptr<dbus::Response> HandleCheckDeviceRegistered( + dbus::MethodCall* method_call); + // Handles calls to org.chromium.Buffet.Manager.GetDeviceInfo(). + scoped_ptr<dbus::Response> HandleGetDeviceInfo( + dbus::MethodCall* method_call); + // Handles calls to org.chromium.Buffet.Manager.StartRegisterDevice(). + scoped_ptr<dbus::Response> HandleStartRegisterDevice( + dbus::MethodCall* method_call); + // Handles calls to org.chromium.Buffet.Manager.FinishRegisterDevice(). + scoped_ptr<dbus::Response> HandleFinishRegisterDevice( dbus::MethodCall* method_call); // Handles calls to org.chromium.Buffet.Manager.UpdateState(). scoped_ptr<dbus::Response> HandleUpdateState( @@ -51,6 +63,8 @@ dbus::ExportedObject* exported_object_; // weak; owned by the Bus object. scoped_ptr<Properties> properties_; + DeviceRegistrationInfo device_info_; + DISALLOW_COPY_AND_ASSIGN(Manager); };