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