Home | History | Annotate | Download | only in cloud_print
      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