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/cloud_print/cloud_print_constants.h" 17 #include "chrome/service/cloud_print/cloud_print_auth.h" 18 #include "chrome/service/cloud_print/cloud_print_connector.h" 19 #include "chrome/service/cloud_print/cloud_print_service_helpers.h" 20 #include "chrome/service/cloud_print/cloud_print_token_store.h" 21 #include "chrome/service/cloud_print/connector_settings.h" 22 #include "chrome/service/net/service_url_request_context.h" 23 #include "chrome/service/service_process.h" 24 #include "components/cloud_devices/common/cloud_devices_switches.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 notifier_options.xmpp_host_port = net::HostPortPair::FromString( 345 CommandLine::ForCurrentProcess()->GetSwitchValueASCII( 346 switches::kCloudPrintXmppEndpoint)); 347 push_client_ = notifier::PushClient::CreateDefault(notifier_options); 348 push_client_->AddObserver(this); 349 notifier::Subscription subscription; 350 subscription.channel = kCloudPrintPushNotificationsSource; 351 subscription.from = kCloudPrintPushNotificationsSource; 352 push_client_->UpdateSubscriptions( 353 notifier::SubscriptionList(1, subscription)); 354 push_client_->UpdateCredentials(robot_email, access_token); 355 } 356 357 void CloudPrintProxyBackend::Core::DoShutdown() { 358 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 359 VLOG(1) << "CP_CONNECTOR: Shutdown connector, id: " << settings_.proxy_id(); 360 361 if (connector_->IsRunning()) 362 connector_->Stop(); 363 364 // Important to delete the PushClient on this thread. 365 if (push_client_.get()) { 366 push_client_->RemoveObserver(this); 367 } 368 push_client_.reset(); 369 notifications_enabled_ = false; 370 notifications_enabled_since_ = base::TimeTicks(); 371 token_store_.reset(); 372 373 DestroyAuthAndConnector(); 374 } 375 376 void CloudPrintProxyBackend::Core::DoUnregisterPrinters() { 377 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 378 379 std::string access_token = GetTokenStore()->token(); 380 381 std::list<std::string> printer_ids; 382 connector_->GetPrinterIds(&printer_ids); 383 384 backend_->frontend_loop_->PostTask( 385 FROM_HERE, 386 base::Bind(&Core::NotifyUnregisterPrinters, 387 this, access_token, printer_ids)); 388 } 389 390 void CloudPrintProxyBackend::Core::HandlePrinterNotification( 391 const std::string& notification) { 392 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 393 394 size_t pos = notification.rfind(kNotificationUpdateSettings); 395 if (pos == std::string::npos) { 396 VLOG(1) << "CP_CONNECTOR: Handle printer notification, id: " 397 << notification; 398 connector_->CheckForJobs(kJobFetchReasonNotified, notification); 399 } else { 400 DCHECK(pos == notification.length() - strlen(kNotificationUpdateSettings)); 401 std::string printer_id = notification.substr(0, pos); 402 VLOG(1) << "CP_CONNECTOR: Update printer settings, id: " << printer_id; 403 connector_->UpdatePrinterSettings(printer_id); 404 } 405 } 406 407 void CloudPrintProxyBackend::Core::PollForJobs() { 408 VLOG(1) << "CP_CONNECTOR: Polling for jobs."; 409 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 410 // Check all printers for jobs. 411 connector_->CheckForJobs(kJobFetchReasonPoll, std::string()); 412 413 job_poll_scheduled_ = false; 414 // If we don't have notifications and job polling is enabled, poll again 415 // after a while. 416 if (!notifications_enabled_ && enable_job_poll_) 417 ScheduleJobPoll(); 418 } 419 420 void CloudPrintProxyBackend::Core::ScheduleJobPoll() { 421 if (!job_poll_scheduled_) { 422 base::TimeDelta interval = base::TimeDelta::FromSeconds( 423 base::RandInt(kMinJobPollIntervalSecs, kMaxJobPollIntervalSecs)); 424 base::MessageLoop::current()->PostDelayedTask( 425 FROM_HERE, 426 base::Bind(&CloudPrintProxyBackend::Core::PollForJobs, this), 427 interval); 428 job_poll_scheduled_ = true; 429 } 430 } 431 432 void CloudPrintProxyBackend::Core::PingXmppServer() { 433 xmpp_ping_scheduled_ = false; 434 435 if (!push_client_.get()) 436 return; 437 438 push_client_->SendPing(); 439 440 pending_xmpp_pings_++; 441 if (pending_xmpp_pings_ >= kMaxFailedXmppPings) { 442 // Check ping status when we close to the limit. 443 base::MessageLoop::current()->PostDelayedTask( 444 FROM_HERE, 445 base::Bind(&CloudPrintProxyBackend::Core::CheckXmppPingStatus, this), 446 base::TimeDelta::FromSeconds(kXmppPingCheckIntervalSecs)); 447 } 448 449 // Schedule next ping if needed. 450 if (notifications_enabled_) 451 ScheduleXmppPing(); 452 } 453 454 void CloudPrintProxyBackend::Core::ScheduleXmppPing() { 455 // settings_.xmpp_ping_enabled() is obsolete, we are now control 456 // XMPP pings from Cloud Print server. 457 if (!xmpp_ping_scheduled_) { 458 base::TimeDelta interval = base::TimeDelta::FromSeconds( 459 base::RandInt(settings_.xmpp_ping_timeout_sec() * 0.9, 460 settings_.xmpp_ping_timeout_sec() * 1.1)); 461 base::MessageLoop::current()->PostDelayedTask( 462 FROM_HERE, 463 base::Bind(&CloudPrintProxyBackend::Core::PingXmppServer, this), 464 interval); 465 xmpp_ping_scheduled_ = true; 466 } 467 } 468 469 void CloudPrintProxyBackend::Core::CheckXmppPingStatus() { 470 if (pending_xmpp_pings_ >= kMaxFailedXmppPings) { 471 UMA_HISTOGRAM_COUNTS_100("CloudPrint.XmppPingTry", 99); // Max on fail. 472 // Reconnect to XMPP. 473 pending_xmpp_pings_ = 0; 474 push_client_.reset(); 475 InitNotifications(robot_email_, GetTokenStore()->token()); 476 } 477 } 478 479 CloudPrintTokenStore* CloudPrintProxyBackend::Core::GetTokenStore() { 480 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 481 if (!token_store_.get()) 482 token_store_.reset(new CloudPrintTokenStore); 483 return token_store_.get(); 484 } 485 486 void CloudPrintProxyBackend::Core::NotifyAuthenticated( 487 const std::string& robot_oauth_refresh_token, 488 const std::string& robot_email, 489 const std::string& user_email) { 490 DCHECK(base::MessageLoop::current() == backend_->frontend_loop_); 491 backend_->frontend_ 492 ->OnAuthenticated(robot_oauth_refresh_token, robot_email, user_email); 493 } 494 495 void CloudPrintProxyBackend::Core::NotifyAuthenticationFailed() { 496 DCHECK(base::MessageLoop::current() == backend_->frontend_loop_); 497 backend_->frontend_->OnAuthenticationFailed(); 498 } 499 500 void CloudPrintProxyBackend::Core::NotifyPrintSystemUnavailable() { 501 DCHECK(base::MessageLoop::current() == backend_->frontend_loop_); 502 backend_->frontend_->OnPrintSystemUnavailable(); 503 } 504 505 void CloudPrintProxyBackend::Core::NotifyUnregisterPrinters( 506 const std::string& auth_token, 507 const std::list<std::string>& printer_ids) { 508 DCHECK(base::MessageLoop::current() == backend_->frontend_loop_); 509 backend_->frontend_->OnUnregisterPrinters(auth_token, printer_ids); 510 } 511 512 void CloudPrintProxyBackend::Core::NotifyXmppPingUpdated(int ping_timeout) { 513 DCHECK(base::MessageLoop::current() == backend_->frontend_loop_); 514 backend_->frontend_->OnXmppPingUpdated(ping_timeout); 515 } 516 517 void CloudPrintProxyBackend::Core::OnNotificationsEnabled() { 518 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 519 notifications_enabled_ = true; 520 notifications_enabled_since_ = base::TimeTicks::Now(); 521 VLOG(1) << "Notifications for connector " << settings_.proxy_id() 522 << " were enabled at " 523 << notifications_enabled_since_.ToInternalValue(); 524 // Notifications just got re-enabled. In this case we want to schedule 525 // a poll once for jobs we might have missed when we were dark. 526 // Note that ScheduleJobPoll will not schedule again if a job poll task is 527 // already scheduled. 528 ScheduleJobPoll(); 529 530 // Schedule periodic ping for XMPP notification channel. 531 ScheduleXmppPing(); 532 } 533 534 void CloudPrintProxyBackend::Core::OnNotificationsDisabled( 535 notifier::NotificationsDisabledReason reason) { 536 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 537 notifications_enabled_ = false; 538 LOG(ERROR) << "Notifications for connector " << settings_.proxy_id() 539 << " disabled."; 540 notifications_enabled_since_ = base::TimeTicks(); 541 // We just lost notifications. This this case we want to schedule a 542 // job poll if enable_job_poll_ is true. 543 if (enable_job_poll_) 544 ScheduleJobPoll(); 545 } 546 547 548 void CloudPrintProxyBackend::Core::OnIncomingNotification( 549 const notifier::Notification& notification) { 550 // Since we got some notification from the server, 551 // reset pending ping counter to 0. 552 pending_xmpp_pings_ = 0; 553 554 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 555 VLOG(1) << "CP_CONNECTOR: Incoming notification."; 556 if (0 == base::strcasecmp(kCloudPrintPushNotificationsSource, 557 notification.channel.c_str())) 558 HandlePrinterNotification(notification.data); 559 } 560 561 void CloudPrintProxyBackend::Core::OnPingResponse() { 562 UMA_HISTOGRAM_COUNTS_100("CloudPrint.XmppPingTry", pending_xmpp_pings_); 563 pending_xmpp_pings_ = 0; 564 VLOG(1) << "CP_CONNECTOR: Ping response received."; 565 } 566 567 } // namespace cloud_print 568