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_backend.h" 6 7 #include <map> 8 #include <vector> 9 10 #include "base/bind.h" 11 #include "base/command_line.h" 12 #include "base/compiler_specific.h" 13 #include "base/metrics/histogram.h" 14 #include "base/rand_util.h" 15 #include "base/values.h" 16 #include "chrome/common/chrome_switches.h" 17 #include "chrome/common/cloud_print/cloud_print_constants.h" 18 #include "chrome/service/cloud_print/cloud_print_auth.h" 19 #include "chrome/service/cloud_print/cloud_print_connector.h" 20 #include "chrome/service/cloud_print/cloud_print_service_helpers.h" 21 #include "chrome/service/cloud_print/cloud_print_token_store.h" 22 #include "chrome/service/cloud_print/connector_settings.h" 23 #include "chrome/service/net/service_url_request_context.h" 24 #include "chrome/service/service_process.h" 25 #include "google_apis/gaia/gaia_oauth_client.h" 26 #include "google_apis/gaia/gaia_urls.h" 27 #include "grit/generated_resources.h" 28 #include "jingle/notifier/base/notifier_options.h" 29 #include "jingle/notifier/listener/push_client.h" 30 #include "jingle/notifier/listener/push_client_observer.h" 31 #include "ui/base/l10n/l10n_util.h" 32 #include "url/gurl.h" 33 34 namespace cloud_print { 35 36 // The real guts of CloudPrintProxyBackend, to keep the public client API clean. 37 class CloudPrintProxyBackend::Core 38 : public base::RefCountedThreadSafe<CloudPrintProxyBackend::Core>, 39 public CloudPrintAuth::Client, 40 public CloudPrintConnector::Client, 41 public notifier::PushClientObserver { 42 public: 43 // It is OK for print_server_url to be empty. In this case system should 44 // use system default (local) print server. 45 Core(CloudPrintProxyBackend* backend, 46 const ConnectorSettings& settings, 47 const gaia::OAuthClientInfo& oauth_client_info, 48 bool enable_job_poll); 49 50 // Note: 51 // 52 // The Do* methods are the various entry points from CloudPrintProxyBackend 53 // It calls us on a dedicated thread to actually perform synchronous 54 // (and potentially blocking) operations. 55 void DoInitializeWithToken(const std::string& cloud_print_token); 56 void DoInitializeWithRobotToken(const std::string& robot_oauth_refresh_token, 57 const std::string& robot_email); 58 void DoInitializeWithRobotAuthCode(const std::string& robot_oauth_auth_code, 59 const std::string& robot_email); 60 61 // Called on the CloudPrintProxyBackend core_thread_ to perform 62 // shutdown. 63 void DoShutdown(); 64 void DoRegisterSelectedPrinters( 65 const printing::PrinterList& printer_list); 66 void DoUnregisterPrinters(); 67 68 // CloudPrintAuth::Client implementation. 69 virtual void OnAuthenticationComplete( 70 const std::string& access_token, 71 const std::string& robot_oauth_refresh_token, 72 const std::string& robot_email, 73 const std::string& user_email) OVERRIDE; 74 virtual void OnInvalidCredentials() OVERRIDE; 75 76 // CloudPrintConnector::Client implementation. 77 virtual void OnAuthFailed() OVERRIDE; 78 virtual void OnXmppPingUpdated(int ping_timeout) OVERRIDE; 79 80 // notifier::PushClientObserver implementation. 81 virtual void OnNotificationsEnabled() OVERRIDE; 82 virtual void OnNotificationsDisabled( 83 notifier::NotificationsDisabledReason reason) OVERRIDE; 84 virtual void OnIncomingNotification( 85 const notifier::Notification& notification) OVERRIDE; 86 virtual void OnPingResponse() OVERRIDE; 87 88 private: 89 friend class base::RefCountedThreadSafe<Core>; 90 91 virtual ~Core() {} 92 93 void CreateAuthAndConnector(); 94 void DestroyAuthAndConnector(); 95 96 // NotifyXXX is how the Core communicates with the frontend across 97 // threads. 98 void NotifyPrinterListAvailable( 99 const printing::PrinterList& printer_list); 100 void NotifyAuthenticated( 101 const std::string& robot_oauth_refresh_token, 102 const std::string& robot_email, 103 const std::string& user_email); 104 void NotifyAuthenticationFailed(); 105 void NotifyPrintSystemUnavailable(); 106 void NotifyUnregisterPrinters(const std::string& auth_token, 107 const std::list<std::string>& printer_ids); 108 void NotifyXmppPingUpdated(int ping_timeout); 109 110 // Init XMPP channel 111 void InitNotifications(const std::string& robot_email, 112 const std::string& access_token); 113 114 void HandlePrinterNotification(const std::string& notification); 115 void PollForJobs(); 116 // Schedules a task to poll for jobs. Does nothing if a task is already 117 // scheduled. 118 void ScheduleJobPoll(); 119 void PingXmppServer(); 120 void ScheduleXmppPing(); 121 void CheckXmppPingStatus(); 122 123 CloudPrintTokenStore* GetTokenStore(); 124 125 // Our parent CloudPrintProxyBackend 126 CloudPrintProxyBackend* backend_; 127 128 // Cloud Print authenticator. 129 scoped_refptr<CloudPrintAuth> auth_; 130 131 // Cloud Print connector. 132 scoped_refptr<CloudPrintConnector> connector_; 133 134 // OAuth client info. 135 gaia::OAuthClientInfo oauth_client_info_; 136 // Notification (xmpp) handler. 137 scoped_ptr<notifier::PushClient> push_client_; 138 // Indicates whether XMPP notifications are currently enabled. 139 bool notifications_enabled_; 140 // The time when notifications were enabled. Valid only when 141 // notifications_enabled_ is true. 142 base::TimeTicks notifications_enabled_since_; 143 // Indicates whether a task to poll for jobs has been scheduled. 144 bool job_poll_scheduled_; 145 // Indicates whether we should poll for jobs when we lose XMPP connection. 146 bool enable_job_poll_; 147 // Indicates whether a task to ping xmpp server has been scheduled. 148 bool xmpp_ping_scheduled_; 149 // Number of XMPP pings pending reply from the server. 150 int pending_xmpp_pings_; 151 // Connector settings. 152 ConnectorSettings settings_; 153 std::string robot_email_; 154 scoped_ptr<CloudPrintTokenStore> token_store_; 155 156 DISALLOW_COPY_AND_ASSIGN(Core); 157 }; 158 159 CloudPrintProxyBackend::CloudPrintProxyBackend( 160 CloudPrintProxyFrontend* frontend, 161 const ConnectorSettings& settings, 162 const gaia::OAuthClientInfo& oauth_client_info, 163 bool enable_job_poll) 164 : core_thread_("Chrome_CloudPrintProxyCoreThread"), 165 frontend_loop_(base::MessageLoop::current()), 166 frontend_(frontend) { 167 DCHECK(frontend_); 168 core_ = new Core(this, settings, oauth_client_info, enable_job_poll); 169 } 170 171 CloudPrintProxyBackend::~CloudPrintProxyBackend() { DCHECK(!core_.get()); } 172 173 bool CloudPrintProxyBackend::InitializeWithToken( 174 const std::string& cloud_print_token) { 175 if (!core_thread_.Start()) 176 return false; 177 core_thread_.message_loop()->PostTask( 178 FROM_HERE, 179 base::Bind(&CloudPrintProxyBackend::Core::DoInitializeWithToken, 180 core_.get(), cloud_print_token)); 181 return true; 182 } 183 184 bool CloudPrintProxyBackend::InitializeWithRobotToken( 185 const std::string& robot_oauth_refresh_token, 186 const std::string& robot_email) { 187 if (!core_thread_.Start()) 188 return false; 189 core_thread_.message_loop()->PostTask( 190 FROM_HERE, 191 base::Bind(&CloudPrintProxyBackend::Core::DoInitializeWithRobotToken, 192 core_.get(), robot_oauth_refresh_token, robot_email)); 193 return true; 194 } 195 196 bool CloudPrintProxyBackend::InitializeWithRobotAuthCode( 197 const std::string& robot_oauth_auth_code, 198 const std::string& robot_email) { 199 if (!core_thread_.Start()) 200 return false; 201 core_thread_.message_loop()->PostTask( 202 FROM_HERE, 203 base::Bind(&CloudPrintProxyBackend::Core::DoInitializeWithRobotAuthCode, 204 core_.get(), robot_oauth_auth_code, robot_email)); 205 return true; 206 } 207 208 void CloudPrintProxyBackend::Shutdown() { 209 core_thread_.message_loop()->PostTask( 210 FROM_HERE, 211 base::Bind(&CloudPrintProxyBackend::Core::DoShutdown, core_.get())); 212 core_thread_.Stop(); 213 core_ = NULL; // Releases reference to core_. 214 } 215 216 void CloudPrintProxyBackend::UnregisterPrinters() { 217 core_thread_.message_loop()->PostTask( 218 FROM_HERE, 219 base::Bind(&CloudPrintProxyBackend::Core::DoUnregisterPrinters, 220 core_.get())); 221 } 222 223 CloudPrintProxyBackend::Core::Core( 224 CloudPrintProxyBackend* backend, 225 const ConnectorSettings& settings, 226 const gaia::OAuthClientInfo& oauth_client_info, 227 bool enable_job_poll) 228 : backend_(backend), 229 oauth_client_info_(oauth_client_info), 230 notifications_enabled_(false), 231 job_poll_scheduled_(false), 232 enable_job_poll_(enable_job_poll), 233 xmpp_ping_scheduled_(false), 234 pending_xmpp_pings_(0) { 235 settings_.CopyFrom(settings); 236 } 237 238 void CloudPrintProxyBackend::Core::CreateAuthAndConnector() { 239 if (!auth_.get()) { 240 auth_ = new CloudPrintAuth(this, settings_.server_url(), oauth_client_info_, 241 settings_.proxy_id()); 242 } 243 244 if (!connector_.get()) { 245 connector_ = new CloudPrintConnector(this, settings_); 246 } 247 } 248 249 void CloudPrintProxyBackend::Core::DestroyAuthAndConnector() { 250 auth_ = NULL; 251 connector_ = NULL; 252 } 253 254 void CloudPrintProxyBackend::Core::DoInitializeWithToken( 255 const std::string& cloud_print_token) { 256 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 257 CreateAuthAndConnector(); 258 auth_->AuthenticateWithToken(cloud_print_token); 259 } 260 261 void CloudPrintProxyBackend::Core::DoInitializeWithRobotToken( 262 const std::string& robot_oauth_refresh_token, 263 const std::string& robot_email) { 264 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 265 CreateAuthAndConnector(); 266 auth_->AuthenticateWithRobotToken(robot_oauth_refresh_token, robot_email); 267 } 268 269 void CloudPrintProxyBackend::Core::DoInitializeWithRobotAuthCode( 270 const std::string& robot_oauth_auth_code, 271 const std::string& robot_email) { 272 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 273 CreateAuthAndConnector(); 274 auth_->AuthenticateWithRobotAuthCode(robot_oauth_auth_code, robot_email); 275 } 276 277 void CloudPrintProxyBackend::Core::OnAuthenticationComplete( 278 const std::string& access_token, 279 const std::string& robot_oauth_refresh_token, 280 const std::string& robot_email, 281 const std::string& user_email) { 282 CloudPrintTokenStore* token_store = GetTokenStore(); 283 bool first_time = token_store->token().empty(); 284 token_store->SetToken(access_token); 285 robot_email_ = robot_email; 286 // Let the frontend know that we have authenticated. 287 backend_->frontend_loop_->PostTask( 288 FROM_HERE, 289 base::Bind(&Core::NotifyAuthenticated, this, robot_oauth_refresh_token, 290 robot_email, user_email)); 291 if (first_time) { 292 InitNotifications(robot_email, access_token); 293 } else { 294 // If we are refreshing a token, update the XMPP token too. 295 DCHECK(push_client_.get()); 296 push_client_->UpdateCredentials(robot_email, access_token); 297 } 298 // Start cloud print connector if needed. 299 if (!connector_->IsRunning()) { 300 if (!connector_->Start()) { 301 // Let the frontend know that we do not have a print system. 302 backend_->frontend_loop_->PostTask( 303 FROM_HERE, base::Bind(&Core::NotifyPrintSystemUnavailable, this)); 304 } 305 } 306 } 307 308 void CloudPrintProxyBackend::Core::OnInvalidCredentials() { 309 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 310 VLOG(1) << "CP_CONNECTOR: Auth Error"; 311 backend_->frontend_loop_->PostTask( 312 FROM_HERE, base::Bind(&Core::NotifyAuthenticationFailed, this)); 313 } 314 315 void CloudPrintProxyBackend::Core::OnAuthFailed() { 316 VLOG(1) << "CP_CONNECTOR: Authentication failed in connector."; 317 // Let's stop connecter and refresh token. We'll restart connecter once 318 // new token available. 319 if (connector_->IsRunning()) 320 connector_->Stop(); 321 322 // Refresh Auth token. 323 auth_->RefreshAccessToken(); 324 } 325 326 void CloudPrintProxyBackend::Core::OnXmppPingUpdated(int ping_timeout) { 327 settings_.SetXmppPingTimeoutSec(ping_timeout); 328 backend_->frontend_loop_->PostTask( 329 FROM_HERE, 330 base::Bind(&Core::NotifyXmppPingUpdated, this, ping_timeout)); 331 } 332 333 void CloudPrintProxyBackend::Core::InitNotifications( 334 const std::string& robot_email, 335 const std::string& access_token) { 336 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 337 338 pending_xmpp_pings_ = 0; 339 notifier::NotifierOptions notifier_options; 340 notifier_options.request_context_getter = 341 g_service_process->GetServiceURLRequestContextGetter(); 342 notifier_options.auth_mechanism = "X-OAUTH2"; 343 notifier_options.try_ssltcp_first = true; 344 push_client_ = notifier::PushClient::CreateDefault(notifier_options); 345 push_client_->AddObserver(this); 346 notifier::Subscription subscription; 347 subscription.channel = kCloudPrintPushNotificationsSource; 348 subscription.from = kCloudPrintPushNotificationsSource; 349 push_client_->UpdateSubscriptions( 350 notifier::SubscriptionList(1, subscription)); 351 push_client_->UpdateCredentials(robot_email, access_token); 352 } 353 354 void CloudPrintProxyBackend::Core::DoShutdown() { 355 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 356 VLOG(1) << "CP_CONNECTOR: Shutdown connector, id: " << settings_.proxy_id(); 357 358 if (connector_->IsRunning()) 359 connector_->Stop(); 360 361 // Important to delete the PushClient on this thread. 362 if (push_client_.get()) { 363 push_client_->RemoveObserver(this); 364 } 365 push_client_.reset(); 366 notifications_enabled_ = false; 367 notifications_enabled_since_ = base::TimeTicks(); 368 token_store_.reset(); 369 370 DestroyAuthAndConnector(); 371 } 372 373 void CloudPrintProxyBackend::Core::DoUnregisterPrinters() { 374 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 375 376 std::string access_token = GetTokenStore()->token(); 377 378 std::list<std::string> printer_ids; 379 connector_->GetPrinterIds(&printer_ids); 380 381 backend_->frontend_loop_->PostTask( 382 FROM_HERE, 383 base::Bind(&Core::NotifyUnregisterPrinters, 384 this, access_token, printer_ids)); 385 } 386 387 void CloudPrintProxyBackend::Core::HandlePrinterNotification( 388 const std::string& notification) { 389 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 390 391 size_t pos = notification.rfind(kNotificationUpdateSettings); 392 if (pos == std::string::npos) { 393 VLOG(1) << "CP_CONNECTOR: Handle printer notification, id: " 394 << notification; 395 connector_->CheckForJobs(kJobFetchReasonNotified, notification); 396 } else { 397 DCHECK(pos == notification.length() - strlen(kNotificationUpdateSettings)); 398 std::string printer_id = notification.substr(0, pos); 399 VLOG(1) << "CP_CONNECTOR: Update printer settings, id: " << printer_id; 400 connector_->UpdatePrinterSettings(printer_id); 401 } 402 } 403 404 void CloudPrintProxyBackend::Core::PollForJobs() { 405 VLOG(1) << "CP_CONNECTOR: Polling for jobs."; 406 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 407 // Check all printers for jobs. 408 connector_->CheckForJobs(kJobFetchReasonPoll, std::string()); 409 410 job_poll_scheduled_ = false; 411 // If we don't have notifications and job polling is enabled, poll again 412 // after a while. 413 if (!notifications_enabled_ && enable_job_poll_) 414 ScheduleJobPoll(); 415 } 416 417 void CloudPrintProxyBackend::Core::ScheduleJobPoll() { 418 if (!job_poll_scheduled_) { 419 base::TimeDelta interval = base::TimeDelta::FromSeconds( 420 base::RandInt(kMinJobPollIntervalSecs, kMaxJobPollIntervalSecs)); 421 base::MessageLoop::current()->PostDelayedTask( 422 FROM_HERE, 423 base::Bind(&CloudPrintProxyBackend::Core::PollForJobs, this), 424 interval); 425 job_poll_scheduled_ = true; 426 } 427 } 428 429 void CloudPrintProxyBackend::Core::PingXmppServer() { 430 xmpp_ping_scheduled_ = false; 431 432 if (!push_client_.get()) 433 return; 434 435 push_client_->SendPing(); 436 437 pending_xmpp_pings_++; 438 if (pending_xmpp_pings_ >= kMaxFailedXmppPings) { 439 // Check ping status when we close to the limit. 440 base::MessageLoop::current()->PostDelayedTask( 441 FROM_HERE, 442 base::Bind(&CloudPrintProxyBackend::Core::CheckXmppPingStatus, this), 443 base::TimeDelta::FromSeconds(kXmppPingCheckIntervalSecs)); 444 } 445 446 // Schedule next ping if needed. 447 if (notifications_enabled_) 448 ScheduleXmppPing(); 449 } 450 451 void CloudPrintProxyBackend::Core::ScheduleXmppPing() { 452 // settings_.xmpp_ping_enabled() is obsolete, we are now control 453 // XMPP pings from Cloud Print server. 454 if (!xmpp_ping_scheduled_) { 455 base::TimeDelta interval = base::TimeDelta::FromSeconds( 456 base::RandInt(settings_.xmpp_ping_timeout_sec() * 0.9, 457 settings_.xmpp_ping_timeout_sec() * 1.1)); 458 base::MessageLoop::current()->PostDelayedTask( 459 FROM_HERE, 460 base::Bind(&CloudPrintProxyBackend::Core::PingXmppServer, this), 461 interval); 462 xmpp_ping_scheduled_ = true; 463 } 464 } 465 466 void CloudPrintProxyBackend::Core::CheckXmppPingStatus() { 467 if (pending_xmpp_pings_ >= kMaxFailedXmppPings) { 468 UMA_HISTOGRAM_COUNTS_100("CloudPrint.XmppPingTry", 99); // Max on fail. 469 // Reconnect to XMPP. 470 pending_xmpp_pings_ = 0; 471 push_client_.reset(); 472 InitNotifications(robot_email_, GetTokenStore()->token()); 473 } 474 } 475 476 CloudPrintTokenStore* CloudPrintProxyBackend::Core::GetTokenStore() { 477 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 478 if (!token_store_.get()) 479 token_store_.reset(new CloudPrintTokenStore); 480 return token_store_.get(); 481 } 482 483 void CloudPrintProxyBackend::Core::NotifyAuthenticated( 484 const std::string& robot_oauth_refresh_token, 485 const std::string& robot_email, 486 const std::string& user_email) { 487 DCHECK(base::MessageLoop::current() == backend_->frontend_loop_); 488 backend_->frontend_ 489 ->OnAuthenticated(robot_oauth_refresh_token, robot_email, user_email); 490 } 491 492 void CloudPrintProxyBackend::Core::NotifyAuthenticationFailed() { 493 DCHECK(base::MessageLoop::current() == backend_->frontend_loop_); 494 backend_->frontend_->OnAuthenticationFailed(); 495 } 496 497 void CloudPrintProxyBackend::Core::NotifyPrintSystemUnavailable() { 498 DCHECK(base::MessageLoop::current() == backend_->frontend_loop_); 499 backend_->frontend_->OnPrintSystemUnavailable(); 500 } 501 502 void CloudPrintProxyBackend::Core::NotifyUnregisterPrinters( 503 const std::string& auth_token, 504 const std::list<std::string>& printer_ids) { 505 DCHECK(base::MessageLoop::current() == backend_->frontend_loop_); 506 backend_->frontend_->OnUnregisterPrinters(auth_token, printer_ids); 507 } 508 509 void CloudPrintProxyBackend::Core::NotifyXmppPingUpdated(int ping_timeout) { 510 DCHECK(base::MessageLoop::current() == backend_->frontend_loop_); 511 backend_->frontend_->OnXmppPingUpdated(ping_timeout); 512 } 513 514 void CloudPrintProxyBackend::Core::OnNotificationsEnabled() { 515 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 516 notifications_enabled_ = true; 517 notifications_enabled_since_ = base::TimeTicks::Now(); 518 VLOG(1) << "Notifications for connector " << settings_.proxy_id() 519 << " were enabled at " 520 << notifications_enabled_since_.ToInternalValue(); 521 // Notifications just got re-enabled. In this case we want to schedule 522 // a poll once for jobs we might have missed when we were dark. 523 // Note that ScheduleJobPoll will not schedule again if a job poll task is 524 // already scheduled. 525 ScheduleJobPoll(); 526 527 // Schedule periodic ping for XMPP notification channel. 528 ScheduleXmppPing(); 529 } 530 531 void CloudPrintProxyBackend::Core::OnNotificationsDisabled( 532 notifier::NotificationsDisabledReason reason) { 533 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 534 notifications_enabled_ = false; 535 LOG(ERROR) << "Notifications for connector " << settings_.proxy_id() 536 << " disabled."; 537 notifications_enabled_since_ = base::TimeTicks(); 538 // We just lost notifications. This this case we want to schedule a 539 // job poll if enable_job_poll_ is true. 540 if (enable_job_poll_) 541 ScheduleJobPoll(); 542 } 543 544 545 void CloudPrintProxyBackend::Core::OnIncomingNotification( 546 const notifier::Notification& notification) { 547 // Since we got some notification from the server, 548 // reset pending ping counter to 0. 549 pending_xmpp_pings_ = 0; 550 551 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 552 VLOG(1) << "CP_CONNECTOR: Incoming notification."; 553 if (0 == base::strcasecmp(kCloudPrintPushNotificationsSource, 554 notification.channel.c_str())) 555 HandlePrinterNotification(notification.data); 556 } 557 558 void CloudPrintProxyBackend::Core::OnPingResponse() { 559 UMA_HISTOGRAM_COUNTS_100("CloudPrint.XmppPingTry", pending_xmpp_pings_); 560 pending_xmpp_pings_ = 0; 561 VLOG(1) << "CP_CONNECTOR: Ping response received."; 562 } 563 564 } // namespace cloud_print 565