Validates service state file and ask user to update information if necessarily.

Connects to Google servers to get authentication tokens.

BUG=125026
TEST=service_state_unittest.cc

Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=138294

Review URL: https://chromiumcodereview.appspot.com/10414020

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@138371 0039d316-1c4b-4281-b951-d872f2087c98
parent 8d1597c7
include_rules = [
"+net",
]
......@@ -10,9 +10,12 @@
#include "base/at_exit.h"
#include "base/command_line.h"
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/path_service.h"
#include "base/string_util.h"
#include "base/win/scoped_handle.h"
#include "cloud_print/service/win/resource.h"
#include "cloud_print/service/win/service_state.h"
namespace {
......@@ -26,6 +29,8 @@ const char kServiceSwitch[] = "service";
const char kUserDataDirSwitch[] = "user-data-dir";
const char kQuietSwitch[] = "quiet";
const wchar_t kServiceStateFileName[] = L"Service State";
// The traits class for Windows Service.
class ServiceHandleTraits {
public:
......@@ -98,6 +103,34 @@ void InvalidUsage() {
std::cout << "\n";
}
std::string GetOption(const std::string& name, const std::string& default,
bool secure) {
std::cout << "Input \'" << name << "\'";
if (!default.empty()) {
std::cout << ", press [ENTER] to keep '";
std::cout << default;
std::cout << "'";
}
std::cout << ":";
std::string tmp;
if (secure) {
DWORD saved_mode = 0;
// Don't close.
HANDLE stdin_handle = ::GetStdHandle(STD_INPUT_HANDLE);
::GetConsoleMode(stdin_handle, &saved_mode);
::SetConsoleMode(stdin_handle, saved_mode & ~ENABLE_ECHO_INPUT);
std::getline(std::cin, tmp);
::SetConsoleMode(stdin_handle, saved_mode);
std::cout << "\n";
} else {
std::getline(std::cin, tmp);
}
if (tmp.empty())
return default;
return tmp;
}
} // namespace
class CloudPrintServiceModule
......@@ -118,7 +151,7 @@ class CloudPrintServiceModule
HRESULT Install(const FilePath& user_data_dir) {
// TODO(vitalybuka): consider "lite" version if we don't want unregister
// printers here.
if (!Uninstall())
if (!Uninstall())
return E_FAIL;
FilePath service_path;
......@@ -196,7 +229,11 @@ class CloudPrintServiceModule
}
FilePath data_dir = command_line.GetSwitchValuePath(kUserDataDirSwitch);
HRESULT hr = Install(data_dir);
HRESULT hr = ProcessServiceState(data_dir,
command_line.HasSwitch(kQuietSwitch));
if (FAILED(hr))
return hr;
hr = Install(data_dir);
if (SUCCEEDED(hr) && command_line.HasSwitch(kStartSwitch))
return StartService();
......@@ -216,6 +253,61 @@ class CloudPrintServiceModule
return S_FALSE;
}
HRESULT ProcessServiceState(const FilePath& user_data_dir, bool quiet) {
FilePath file = user_data_dir.Append(kServiceStateFileName);
for (;;) {
std::string contents;
ServiceState service_state;
bool is_valid = file_util::ReadFileToString(file, &contents) &&
service_state.FromString(contents);
if (!quiet) {
std::cout << file.value() << ":\n";
std::cout << contents << "\n";
}
if (!is_valid)
LOG(ERROR) << "Invalid file: " << file.value();
if (quiet)
return is_valid ? S_OK : HRESULT_FROM_WIN32(ERROR_FILE_INVALID);
std::cout << "Do you want to use this file [y/n]:";
for (;;) {
std::string input;
std::getline(std::cin, input);
StringToLowerASCII(&input);
if (input == "y") {
return S_OK;
} else if (input == "n") {
is_valid = false;
break;
}
}
while (!is_valid) {
std::string email = GetOption("email", service_state.email(), false);
std::string password = GetOption("password", "", true);
std::string proxy_id = GetOption("connector_id",
service_state.proxy_id(), false);
is_valid = service_state.Configure(email, password, proxy_id);
if (is_valid) {
std::string new_contents = service_state.ToString();
if (new_contents != contents) {
if (file_util::WriteFile(file, new_contents.c_str(),
new_contents.size()) <= 0) {
return HResultFromLastError();
}
}
}
}
}
return S_OK;
}
HRESULT OpenServiceManager(ServiceHandle* service_manager) {
if (!service_manager)
return E_POINTER;
......
......@@ -11,6 +11,19 @@
],
},
'targets': [
{
'target_name': 'cloud_print_service_lib',
'type': 'static_library',
'dependencies': [
'../../../base/base.gyp:base',
'../../../net/net.gyp:net',
'../../../build/temp_gyp/googleurl.gyp:googleurl',
],
'sources': [
'service_state.cc',
'service_state.h',
]
},
{
'target_name': 'cloud_print_service',
'type': 'executable',
......@@ -21,7 +34,7 @@
'resource.h',
],
'dependencies': [
'../../../base/base.gyp:base',
'cloud_print_service_lib'
],
'msvs_settings': {
'VCLinkerTool': {
......@@ -30,5 +43,20 @@
},
},
},
{
'target_name': 'cloud_print_service_unittests',
'type': 'executable',
'sources': [
'service_state_unittest.cc',
],
'dependencies': [
'../../../base/base.gyp:run_all_unittests',
'../../../base/base.gyp:base',
'../../../base/base.gyp:test_support_base',
'../../../testing/gmock.gyp:gmock',
'../../../testing/gtest.gyp:gtest',
'cloud_print_service_lib',
],
},
],
}
// Copyright (c) 2012 The Chromium 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 "cloud_print/service/win/service_state.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/message_loop.h"
#include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "net/base/escape.h"
#include "net/base/io_buffer.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_builder.h"
namespace {
const char kCloudPrintJsonName[] = "cloud_print";
const char kEnabledOptionName[] = "enabled";
const char kEmailOptionName[] = "email";
const char kPasswordOptionName[] = "password";
const char kProxyIdOptionName[] = "proxy_id";
const char kRobotEmailOptionName[] = "robot_email";
const char kRobotTokenOptionName[] = "robot_refresh_token";
const char kAuthTokenOptionName[] = "auth_token";
const char kXmppAuthTokenOptionName[] = "xmpp_auth_token";
const char kClientLoginUrl[] = "https://www.google.com/accounts/ClientLogin";
const int64 kRequestTimeoutMs = 10 * 1000;
class ServiceStateURLRequestDelegate : public net::URLRequest::Delegate {
public:
virtual void OnResponseStarted(net::URLRequest* request) {
if (request->GetResponseCode() == 200) {
Read(request);
if (request->status().is_io_pending())
return;
}
request->Cancel();
};
virtual void OnReadCompleted(net::URLRequest* request, int bytes_read) {
Read(request);
if (!request->status().is_io_pending())
MessageLoop::current()->Quit();
};
const std::string& data() const {
return data_;
}
private:
void Read(net::URLRequest* request) {
// Read as many bytes as are available synchronously.
const int kBufSize = 100000;
scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kBufSize));
int num_bytes = 0;
while (request->Read(buf, kBufSize, &num_bytes)) {
data_.append(buf->data(), buf->data() + num_bytes);
}
}
std::string data_;
};
void SetNotEmptyJsonString(base::DictionaryValue* dictionary,
const std::string& name,
const std::string& value) {
if (!value.empty())
dictionary->SetString(name, value);
}
} // namespace
ServiceState::ServiceState() {
Reset();
}
ServiceState::~ServiceState() {
}
void ServiceState::Reset() {
email_.clear();
proxy_id_.clear();
robot_email_.clear();
robot_token_.clear();
auth_token_.clear();
xmpp_auth_token_.clear();
}
bool ServiceState::FromString(const std::string& json) {
Reset();
scoped_ptr<base::Value> data(base::JSONReader::Read(json));
if (!data.get())
return false;
const base::DictionaryValue* services = NULL;
if (!data->GetAsDictionary(&services))
return false;
base::DictionaryValue* cloud_print = NULL;
if (!services->GetDictionary(kCloudPrintJsonName, &cloud_print))
return false;
bool valid_file = true;
// Don't exit on fail. Collect all data for re-use by user later.
if (!cloud_print->GetBoolean(kEnabledOptionName, &valid_file))
valid_file = false;
cloud_print->GetString(kEmailOptionName, &email_);
cloud_print->GetString(kProxyIdOptionName, &proxy_id_);
cloud_print->GetString(kRobotEmailOptionName, &robot_email_);
cloud_print->GetString(kRobotTokenOptionName, &robot_token_);
cloud_print->GetString(kAuthTokenOptionName, &auth_token_);
cloud_print->GetString(kXmppAuthTokenOptionName, &xmpp_auth_token_);
return valid_file && IsValid();
}
bool ServiceState::IsValid() const {
if (email_.empty() || proxy_id_.empty())
return false;
bool valid_robot = !robot_email_.empty() && !robot_token_.empty();
bool valid_auth = !auth_token_.empty() && !xmpp_auth_token_.empty();
return valid_robot || valid_auth;
}
std::string ServiceState::ToString() {
scoped_ptr<base::DictionaryValue> services(new DictionaryValue());
scoped_ptr<base::DictionaryValue> cloud_print(new DictionaryValue());
cloud_print->SetBoolean(kEnabledOptionName, true);
SetNotEmptyJsonString(cloud_print.get(), kEmailOptionName, email_);
SetNotEmptyJsonString(cloud_print.get(), kProxyIdOptionName, proxy_id_);
SetNotEmptyJsonString(cloud_print.get(), kRobotEmailOptionName, robot_email_);
SetNotEmptyJsonString(cloud_print.get(), kRobotTokenOptionName, robot_token_);
SetNotEmptyJsonString(cloud_print.get(), kAuthTokenOptionName, auth_token_);
SetNotEmptyJsonString(cloud_print.get(), kXmppAuthTokenOptionName,
xmpp_auth_token_);
services->Set(kCloudPrintJsonName, cloud_print.release());
std::string json;
base::JSONWriter::WriteWithOptions(services.get(),
base::JSONWriter::OPTIONS_PRETTY_PRINT,
&json);
return json;
}
std::string ServiceState::LoginToGoogle(const std::string& service,
const std::string& email,
const std::string& password) {
MessageLoop loop(MessageLoop::TYPE_IO);
net::URLRequestContextBuilder builder;
scoped_ptr<net::URLRequestContext> context(builder.Build());
ServiceStateURLRequestDelegate fetcher_delegate;
GURL url(kClientLoginUrl);
std::string post_body;
post_body += "accountType=GOOGLE";
post_body += "&Email=" + net::EscapeUrlEncodedData(email, true);
post_body += "&Passwd=" + net::EscapeUrlEncodedData(password, true);
post_body += "&source=" + net::EscapeUrlEncodedData("CP-Service", true);
post_body += "&service=" + net::EscapeUrlEncodedData(service, true);
net::URLRequest request(url, &fetcher_delegate);
request.AppendBytesToUpload(post_body.c_str(), post_body.size());
request.SetExtraRequestHeaderByName(
"Content-Type", "application/x-www-form-urlencoded", true);
request.set_context(context.get());
request.set_method("POST");
request.Start();
MessageLoop::current()->PostDelayedTask(
FROM_HERE, MessageLoop::QuitClosure(), kRequestTimeoutMs);
MessageLoop::current()->Run();
const char kAuthStart[] = "Auth=";
std::vector<std::string> lines;
Tokenize(fetcher_delegate.data(), "\r\n", &lines);
for (size_t i = 0; i < lines.size(); ++i) {
std::vector<std::string> tokens;
if (StartsWithASCII(lines[i], kAuthStart, false))
return lines[i].substr(arraysize(kAuthStart) - 1);
}
return std::string();
}
bool ServiceState::Configure(const std::string& email,
const std::string& password,
const std::string& proxy_id) {
robot_token_.clear();
robot_email_.clear();
email_ = email;
proxy_id_ = proxy_id;
auth_token_ = LoginToGoogle("cloudprint", email_, password);
xmpp_auth_token_ = LoginToGoogle("chromiumsync", email_, password);
return IsValid();
}
// Copyright (c) 2012 The Chromium 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 CLOUD_PRINT_SERVICE_SERVICE_STATE_H_
#define CLOUD_PRINT_SERVICE_SERVICE_STATE_H_
#include <string>
#include "base/file_path.h"
#include "base/memory/scoped_ptr.h"
#include "base/values.h"
class FilePath;
// Manages Cloud Print part of Service State.
class ServiceState {
public:
ServiceState();
virtual ~ServiceState();
void Reset();
// Initialize object from json.
bool FromString(const std::string& json);
// Returns object state as json.
std::string ToString();
// Setups object using data provided by delegate.
bool Configure(const std::string& email,
const std::string& password,
const std::string& proxy_id);
// Returns authentication token provided by Google server.
virtual std::string LoginToGoogle(const std::string& service,
const std::string& email,
const std::string& password);
// Returns true of object state is valid.
bool IsValid() const;
std::string email() const {
return email_;
};
std::string proxy_id() const {
return proxy_id_;
};
std::string robot_email() const {
return robot_email_;
};
std::string robot_token() const {
return robot_token_;
};
std::string auth_token() const {
return auth_token_;
};
std::string xmpp_auth_token() const {
return xmpp_auth_token_;
};
void set_email(const std::string& value) {
email_ = value;
};
void set_proxy_id(const std::string& value) {
proxy_id_ = value;
};
void set_robot_email(const std::string& value) {
robot_email_ = value;
};
void set_robot_token(const std::string& value) {
robot_token_ = value;
};
void set_auth_token(const std::string& value) {
auth_token_ = value;
};
void set_xmpp_auth_token(const std::string& value) {
xmpp_auth_token_ = value;
};
private:
std::string email_;
std::string proxy_id_;
std::string robot_email_;
std::string robot_token_;
std::string auth_token_;
std::string xmpp_auth_token_;
DISALLOW_COPY_AND_ASSIGN(ServiceState);
};
#endif // CLOUD_PRINT_SERVICE_SERVICE_STATE_H_
// Copyright (c) 2012 The Chromium 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 "cloud_print/service/win/service_state.h"
#include "base/string_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::Exactly;
using ::testing::Return;
TEST(ServiceStateTest, Empty) {
ServiceState state;
EXPECT_FALSE(state.IsValid());
}
TEST(ServiceStateTest, ToString) {
ServiceState state;
EXPECT_STREQ("{\"cloud_print\": {\"enabled\": true}}",
CollapseWhitespaceASCII(state.ToString(), true).c_str());
state.set_email("test@gmail.com");
state.set_proxy_id("proxy");
state.set_robot_email("robot@gmail.com");
state.set_robot_token("abc");
state.set_auth_token("token1");
state.set_xmpp_auth_token("token2");
EXPECT_TRUE(state.IsValid());
EXPECT_STREQ("{\"cloud_print\": {\"auth_token\": \"token1\",\"email\": "
"\"test@gmail.com\",\"enabled\": true,\"proxy_id\": \"proxy\","
"\"robot_email\": \"robot@gmail.com\",\"robot_refresh_token\": "
"\"abc\",\"xmpp_auth_token\": \"token2\"}}",
CollapseWhitespaceASCII(state.ToString(), true).c_str());
}
TEST(ServiceStateTest, FromString) {
ServiceState state;
// Syntax error.
EXPECT_FALSE(state.FromString("<\"cloud_print\": {\"enabled\": true}}"));
// No data.
EXPECT_FALSE(state.FromString("{\"cloud_print\": {\"enabled\": true}}"));
EXPECT_FALSE(state.FromString(
"{\"cloud_print\": {\"email\": \"test@gmail.com\"}}"));
EXPECT_STREQ("test@gmail.com", state.email().c_str());
// Good state.
EXPECT_TRUE(state.FromString(
"{\"cloud_print\": {\"email\": \"test2@gmail.com\",\"enabled\": true,\""
"proxy_id\": \"proxy\",\"robot_email\": \"robot@gmail.com\",\""
"robot_refresh_token\": \"abc\"}}"));
EXPECT_STREQ("test2@gmail.com", state.email().c_str());
EXPECT_STREQ("proxy", state.proxy_id().c_str());
EXPECT_STREQ("robot@gmail.com", state.robot_email().c_str());
EXPECT_STREQ("abc", state.robot_token().c_str());
}
class ServiceStateMock : public ServiceState {
public:
ServiceStateMock() {}
MOCK_METHOD3(LoginToGoogle,
std::string(const std::string& service,
const std::string& email,
const std::string& password));
private:
DISALLOW_COPY_AND_ASSIGN(ServiceStateMock);
};
TEST(ServiceStateTest, Configure) {
ServiceStateMock state;
state.set_email("test1@gmail.com");
state.set_proxy_id("id1");
EXPECT_CALL(state, LoginToGoogle("cloudprint", "test2@gmail.com", "abc"))
.Times(Exactly(1))
.WillOnce(Return("auth1"));
EXPECT_CALL(state, LoginToGoogle("chromiumsync", "test2@gmail.com", "abc"))
.Times(Exactly(1))
.WillOnce(Return("auth2"));
EXPECT_TRUE(state.Configure("test2@gmail.com", "abc", "id2"));
EXPECT_STREQ("id2", state.proxy_id().c_str());
EXPECT_STREQ("auth1", state.auth_token().c_str());
EXPECT_STREQ("auth2", state.xmpp_auth_token().c_str());
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment