1// Copyright (c) 2012 The Chromium 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 "chrome/service/cloud_print/cloud_print_proxy.h"
6
7#include "base/bind.h"
8#include "base/command_line.h"
9#include "base/metrics/histogram.h"
10#include "base/path_service.h"
11#include "base/process/kill.h"
12#include "base/process/launch.h"
13#include "base/values.h"
14#include "chrome/common/chrome_switches.h"
15#include "chrome/common/cloud_print/cloud_print_constants.h"
16#include "chrome/common/cloud_print/cloud_print_proxy_info.h"
17#include "chrome/common/pref_names.h"
18#include "chrome/service/cloud_print/print_system.h"
19#include "chrome/service/service_process.h"
20#include "chrome/service/service_process_prefs.h"
21#include "google_apis/gaia/gaia_oauth_client.h"
22#include "google_apis/google_api_keys.h"
23#include "url/gurl.h"
24
25namespace {
26
27void LaunchBrowserProcessWithSwitch(const std::string& switch_string) {
28  DCHECK(g_service_process->io_thread()->message_loop_proxy()->
29      BelongsToCurrentThread());
30  base::FilePath exe_path;
31  PathService::Get(base::FILE_EXE, &exe_path);
32  if (exe_path.empty()) {
33    NOTREACHED() << "Unable to get browser process binary name.";
34  }
35  CommandLine cmd_line(exe_path);
36
37  const CommandLine& process_command_line = *CommandLine::ForCurrentProcess();
38  base::FilePath user_data_dir =
39      process_command_line.GetSwitchValuePath(switches::kUserDataDir);
40  if (!user_data_dir.empty())
41    cmd_line.AppendSwitchPath(switches::kUserDataDir, user_data_dir);
42  cmd_line.AppendSwitch(switch_string);
43
44#if defined(OS_POSIX) && !defined(OS_MACOSX)
45  base::ProcessHandle pid = 0;
46  base::LaunchProcess(cmd_line, base::LaunchOptions(), &pid);
47  base::EnsureProcessGetsReaped(pid);
48#else
49  base::LaunchOptions launch_options;
50#if defined(OS_WIN)
51  launch_options.force_breakaway_from_job_ = true;
52#endif  // OS_WIN
53  base::LaunchProcess(cmd_line, launch_options, NULL);
54#endif
55}
56
57void CheckCloudPrintProxyPolicyInBrowser() {
58  LaunchBrowserProcessWithSwitch(switches::kCheckCloudPrintConnectorPolicy);
59}
60
61}  // namespace
62
63namespace cloud_print {
64
65CloudPrintProxy::CloudPrintProxy()
66    : service_prefs_(NULL),
67      client_(NULL),
68      enabled_(false) {
69}
70
71CloudPrintProxy::~CloudPrintProxy() {
72  DCHECK(CalledOnValidThread());
73  ShutdownBackend();
74}
75
76void CloudPrintProxy::Initialize(ServiceProcessPrefs* service_prefs,
77                                 Client* client) {
78  DCHECK(CalledOnValidThread());
79  service_prefs_ = service_prefs;
80  client_ = client;
81}
82
83void CloudPrintProxy::EnableForUser() {
84  DCHECK(CalledOnValidThread());
85  if (!CreateBackend())
86    return;
87  DCHECK(backend_.get());
88  // Read persisted robot credentials because we may decide to reuse it if the
89  // passed in LSID belongs the same user.
90  std::string robot_refresh_token = service_prefs_->GetString(
91      prefs::kCloudPrintRobotRefreshToken, std::string());
92  std::string robot_email =
93      service_prefs_->GetString(prefs::kCloudPrintRobotEmail, std::string());
94  user_email_ = service_prefs_->GetString(prefs::kCloudPrintEmail, user_email_);
95
96  // See if we have persisted robot credentials.
97  if (!robot_refresh_token.empty()) {
98    DCHECK(!robot_email.empty());
99    backend_->InitializeWithRobotToken(robot_refresh_token, robot_email);
100  } else {
101    // Finally see if we have persisted user credentials (legacy case).
102    std::string cloud_print_token =
103        service_prefs_->GetString(prefs::kCloudPrintAuthToken, std::string());
104    DCHECK(!cloud_print_token.empty());
105    backend_->InitializeWithToken(cloud_print_token);
106  }
107  if (client_) {
108    client_->OnCloudPrintProxyEnabled(true);
109  }
110}
111
112void CloudPrintProxy::EnableForUserWithRobot(
113    const std::string& robot_auth_code,
114    const std::string& robot_email,
115    const std::string& user_email,
116    const base::DictionaryValue& user_settings) {
117  DCHECK(CalledOnValidThread());
118
119  ShutdownBackend();
120  std::string proxy_id(
121      service_prefs_->GetString(prefs::kCloudPrintProxyId, std::string()));
122  service_prefs_->RemovePref(prefs::kCloudPrintRoot);
123  if (!proxy_id.empty()) {
124    // Keep only proxy id;
125    service_prefs_->SetString(prefs::kCloudPrintProxyId, proxy_id);
126  }
127  service_prefs_->SetValue(prefs::kCloudPrintUserSettings,
128                           user_settings.DeepCopy());
129  service_prefs_->WritePrefs();
130
131  if (!CreateBackend())
132    return;
133  DCHECK(backend_.get());
134  user_email_ = user_email;
135  backend_->InitializeWithRobotAuthCode(robot_auth_code, robot_email);
136  if (client_) {
137    client_->OnCloudPrintProxyEnabled(true);
138  }
139}
140
141bool CloudPrintProxy::CreateBackend() {
142  DCHECK(CalledOnValidThread());
143  if (backend_.get())
144    return false;
145
146  settings_.InitFrom(service_prefs_);
147
148  // By default we don't poll for jobs when we lose XMPP connection. But this
149  // behavior can be overridden by a preference.
150  bool enable_job_poll =
151    service_prefs_->GetBoolean(prefs::kCloudPrintEnableJobPoll, false);
152
153  gaia::OAuthClientInfo oauth_client_info;
154  oauth_client_info.client_id =
155    google_apis::GetOAuth2ClientID(google_apis::CLIENT_CLOUD_PRINT);
156  oauth_client_info.client_secret =
157    google_apis::GetOAuth2ClientSecret(google_apis::CLIENT_CLOUD_PRINT);
158  oauth_client_info.redirect_uri = "oob";
159  backend_.reset(new CloudPrintProxyBackend(this, settings_, oauth_client_info,
160                                            enable_job_poll));
161  return true;
162}
163
164void CloudPrintProxy::UnregisterPrintersAndDisableForUser() {
165  DCHECK(CalledOnValidThread());
166  if (backend_.get()) {
167    // Try getting auth and printers info from the backend.
168    // We'll get notified in this case.
169    backend_->UnregisterPrinters();
170  } else {
171    // If no backend avaialble, disable connector immidiately.
172    DisableForUser();
173  }
174}
175
176void CloudPrintProxy::DisableForUser() {
177  DCHECK(CalledOnValidThread());
178  user_email_.clear();
179  enabled_ = false;
180  if (client_) {
181    client_->OnCloudPrintProxyDisabled(true);
182  }
183  ShutdownBackend();
184}
185
186void CloudPrintProxy::GetProxyInfo(CloudPrintProxyInfo* info) {
187  info->enabled = enabled_;
188  info->email.clear();
189  if (enabled_)
190    info->email = user_email();
191  info->proxy_id = settings_.proxy_id();
192  // If the Cloud Print service is not enabled, we may need to read the old
193  // value of proxy_id from prefs.
194  if (info->proxy_id.empty())
195    info->proxy_id =
196        service_prefs_->GetString(prefs::kCloudPrintProxyId, std::string());
197}
198
199void CloudPrintProxy::CheckCloudPrintProxyPolicy() {
200  g_service_process->io_thread()->message_loop_proxy()->PostTask(
201      FROM_HERE, base::Bind(&CheckCloudPrintProxyPolicyInBrowser));
202}
203
204void CloudPrintProxy::OnAuthenticated(
205    const std::string& robot_oauth_refresh_token,
206    const std::string& robot_email,
207    const std::string& user_email) {
208  DCHECK(CalledOnValidThread());
209  service_prefs_->SetString(prefs::kCloudPrintRobotRefreshToken,
210                            robot_oauth_refresh_token);
211  service_prefs_->SetString(prefs::kCloudPrintRobotEmail,
212                            robot_email);
213  // If authenticating from a robot, the user email will be empty.
214  if (!user_email.empty()) {
215    user_email_ = user_email;
216  }
217  service_prefs_->SetString(prefs::kCloudPrintEmail, user_email_);
218  enabled_ = true;
219  DCHECK(!user_email_.empty());
220  service_prefs_->WritePrefs();
221  // When this switch used we don't want connector continue running, we just
222  // need authentication.
223  if (CommandLine::ForCurrentProcess()->HasSwitch(
224          switches::kCloudPrintSetupProxy)) {
225    ShutdownBackend();
226    if (client_) {
227      client_->OnCloudPrintProxyDisabled(false);
228    }
229  }
230}
231
232void CloudPrintProxy::OnAuthenticationFailed() {
233  DCHECK(CalledOnValidThread());
234  // Don't disable permanently. Could be just connection issue.
235  ShutdownBackend();
236  if (client_) {
237    client_->OnCloudPrintProxyDisabled(false);
238  }
239}
240
241void CloudPrintProxy::OnPrintSystemUnavailable() {
242  // If the print system is unavailable, we want to shutdown the proxy and
243  // disable it non-persistently.
244  ShutdownBackend();
245  if (client_) {
246    client_->OnCloudPrintProxyDisabled(false);
247  }
248}
249
250void CloudPrintProxy::OnUnregisterPrinters(
251    const std::string& auth_token,
252    const std::list<std::string>& printer_ids) {
253  UMA_HISTOGRAM_COUNTS_10000("CloudPrint.UnregisterPrinters",
254                             printer_ids.size());
255  ShutdownBackend();
256  wipeout_.reset(new CloudPrintWipeout(this, settings_.server_url()));
257  wipeout_->UnregisterPrinters(auth_token, printer_ids);
258}
259
260void CloudPrintProxy::OnXmppPingUpdated(int ping_timeout) {
261  DCHECK(CalledOnValidThread());
262  service_prefs_->SetInt(prefs::kCloudPrintXmppPingTimeout, ping_timeout);
263  service_prefs_->WritePrefs();
264}
265
266void CloudPrintProxy::OnUnregisterPrintersComplete() {
267  wipeout_.reset();
268  // Finish disabling cloud print for this user.
269  DisableForUser();
270}
271
272void CloudPrintProxy::ShutdownBackend() {
273  DCHECK(CalledOnValidThread());
274  if (backend_.get())
275    backend_->Shutdown();
276  backend_.reset();
277}
278
279}  // namespace cloud_print
280