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