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  ConnectorSettings settings;
147  settings.InitFrom(service_prefs_);
148
149  // By default we don't poll for jobs when we lose XMPP connection. But this
150  // behavior can be overridden by a preference.
151  bool enable_job_poll =
152    service_prefs_->GetBoolean(prefs::kCloudPrintEnableJobPoll, false);
153
154  gaia::OAuthClientInfo oauth_client_info;
155  oauth_client_info.client_id =
156    google_apis::GetOAuth2ClientID(google_apis::CLIENT_CLOUD_PRINT);
157  oauth_client_info.client_secret =
158    google_apis::GetOAuth2ClientSecret(google_apis::CLIENT_CLOUD_PRINT);
159  oauth_client_info.redirect_uri = "oob";
160  backend_.reset(new CloudPrintProxyBackend(
161      this, settings, oauth_client_info, enable_job_poll));
162  return true;
163}
164
165void CloudPrintProxy::UnregisterPrintersAndDisableForUser() {
166  DCHECK(CalledOnValidThread());
167  if (backend_.get()) {
168    // Try getting auth and printers info from the backend.
169    // We'll get notified in this case.
170    backend_->UnregisterPrinters();
171  } else {
172    // If no backend avaialble, disable connector immidiately.
173    DisableForUser();
174  }
175}
176
177void CloudPrintProxy::DisableForUser() {
178  DCHECK(CalledOnValidThread());
179  user_email_.clear();
180  enabled_ = false;
181  if (client_) {
182    client_->OnCloudPrintProxyDisabled(true);
183  }
184  ShutdownBackend();
185}
186
187void CloudPrintProxy::GetProxyInfo(CloudPrintProxyInfo* info) {
188  info->enabled = enabled_;
189  info->email.clear();
190  if (enabled_)
191    info->email = user_email();
192  ConnectorSettings settings;
193  settings.InitFrom(service_prefs_);
194  info->proxy_id = settings.proxy_id();
195}
196
197void CloudPrintProxy::GetPrinters(std::vector<std::string>* printers) {
198  ConnectorSettings settings;
199  settings.InitFrom(service_prefs_);
200  scoped_refptr<PrintSystem> print_system =
201      PrintSystem::CreateInstance(settings.print_system_settings());
202  if (!print_system.get())
203    return;
204  PrintSystem::PrintSystemResult result = print_system->Init();
205  if (!result.succeeded())
206    return;
207  printing::PrinterList printer_list;
208  print_system->EnumeratePrinters(&printer_list);
209  for (size_t i = 0; i < printer_list.size(); ++i)
210    printers->push_back(printer_list[i].printer_name);
211}
212
213void CloudPrintProxy::CheckCloudPrintProxyPolicy() {
214  g_service_process->io_thread()->message_loop_proxy()->PostTask(
215      FROM_HERE, base::Bind(&CheckCloudPrintProxyPolicyInBrowser));
216}
217
218void CloudPrintProxy::OnAuthenticated(
219    const std::string& robot_oauth_refresh_token,
220    const std::string& robot_email,
221    const std::string& user_email) {
222  DCHECK(CalledOnValidThread());
223  service_prefs_->SetString(prefs::kCloudPrintRobotRefreshToken,
224                            robot_oauth_refresh_token);
225  service_prefs_->SetString(prefs::kCloudPrintRobotEmail,
226                            robot_email);
227  // If authenticating from a robot, the user email will be empty.
228  if (!user_email.empty()) {
229    user_email_ = user_email;
230  }
231  service_prefs_->SetString(prefs::kCloudPrintEmail, user_email_);
232  enabled_ = true;
233  DCHECK(!user_email_.empty());
234  service_prefs_->WritePrefs();
235  // When this switch used we don't want connector continue running, we just
236  // need authentication.
237  if (CommandLine::ForCurrentProcess()->HasSwitch(
238          switches::kCloudPrintSetupProxy)) {
239    ShutdownBackend();
240    if (client_) {
241      client_->OnCloudPrintProxyDisabled(false);
242    }
243  }
244}
245
246void CloudPrintProxy::OnAuthenticationFailed() {
247  DCHECK(CalledOnValidThread());
248  // Don't disable permanently. Could be just connection issue.
249  ShutdownBackend();
250  if (client_) {
251    client_->OnCloudPrintProxyDisabled(false);
252  }
253}
254
255void CloudPrintProxy::OnPrintSystemUnavailable() {
256  // If the print system is unavailable, we want to shutdown the proxy and
257  // disable it non-persistently.
258  ShutdownBackend();
259  if (client_) {
260    client_->OnCloudPrintProxyDisabled(false);
261  }
262}
263
264void CloudPrintProxy::OnUnregisterPrinters(
265    const std::string& auth_token,
266    const std::list<std::string>& printer_ids) {
267  UMA_HISTOGRAM_COUNTS_10000("CloudPrint.UnregisterPrinters",
268                             printer_ids.size());
269  ShutdownBackend();
270  ConnectorSettings settings;
271  settings.InitFrom(service_prefs_);
272  wipeout_.reset(new CloudPrintWipeout(this, settings.server_url()));
273  wipeout_->UnregisterPrinters(auth_token, printer_ids);
274}
275
276void CloudPrintProxy::OnXmppPingUpdated(int ping_timeout) {
277  DCHECK(CalledOnValidThread());
278  service_prefs_->SetInt(prefs::kCloudPrintXmppPingTimeout, ping_timeout);
279  service_prefs_->WritePrefs();
280}
281
282void CloudPrintProxy::OnUnregisterPrintersComplete() {
283  wipeout_.reset();
284  // Finish disabling cloud print for this user.
285  DisableForUser();
286}
287
288void CloudPrintProxy::ShutdownBackend() {
289  DCHECK(CalledOnValidThread());
290  if (backend_.get())
291    backend_->Shutdown();
292  backend_.reset();
293}
294
295}  // namespace cloud_print
296