buffet: Handle XMPP authentication failures

For the nightly tests there is a fake GCD server used, and as a result
the XMPP client can't authenticate with the XMPP server (since the
access_token is also fake). This simply closes the XMPP connection if we
see a read fail, so that we don't get spammed with 0 size reads.

TEST=manual and FEATURES=test emerge-whirlwind buffet
BUG=brillo:338

Change-Id: Ie8db114fcc197f816f3cb56673d6f393fa05c8e5
Reviewed-on: https://chromium-review.googlesource.com/251587
Reviewed-by: David Zeuthen <zeuthen@chromium.org>
Reviewed-by: Alex Vakulenko <avakulenko@chromium.org>
Commit-Queue: Nathan Bullock <nathanbullock@google.com>
Tested-by: Nathan Bullock <nathanbullock@google.com>
diff --git a/buffet/device_registration_info.cc b/buffet/device_registration_info.cc
index 5851f04..3e8fd00 100644
--- a/buffet/device_registration_info.cc
+++ b/buffet/device_registration_info.cc
@@ -373,6 +373,17 @@
   xmpp_client_->StartStream();
 }
 
+void DeviceRegistrationInfo::OnFileCanReadWithoutBlocking(int fd) {
+  if (xmpp_client_ && xmpp_client_->GetFileDescriptor() == fd) {
+    if (!xmpp_client_->Read()) {
+      // Authentication failed or the socket was closed.
+      if (!fd_watcher_.StopWatchingFileDescriptor()) {
+        LOG(WARNING) << "Failed to stop the watcher";
+      }
+    }
+  }
+}
+
 std::unique_ptr<base::DictionaryValue>
 DeviceRegistrationInfo::BuildDeviceResource(chromeos::ErrorPtr* error) {
   std::unique_ptr<base::DictionaryValue> commands =
diff --git a/buffet/device_registration_info.h b/buffet/device_registration_info.h
index 780aeb3..4409d25 100644
--- a/buffet/device_registration_info.h
+++ b/buffet/device_registration_info.h
@@ -54,11 +54,7 @@
 
   ~DeviceRegistrationInfo() override;
 
-  void OnFileCanReadWithoutBlocking(int fd) override {
-    if (xmpp_client_ && xmpp_client_->GetFileDescriptor() == fd) {
-      xmpp_client_->Read();
-    }
-  }
+  void OnFileCanReadWithoutBlocking(int fd) override;
 
   void OnFileCanWriteWithoutBlocking(int fd) override {
     LOG(FATAL) << "No write watcher is configured";
diff --git a/buffet/xmpp/xmpp_client.cc b/buffet/xmpp/xmpp_client.cc
index d0f4a56..f98c6bb 100644
--- a/buffet/xmpp/xmpp_client.cc
+++ b/buffet/xmpp/xmpp_client.cc
@@ -61,11 +61,11 @@
 
 }  // namespace
 
-void XmppClient::Read() {
+bool XmppClient::Read() {
   std::string msg;
   if (!connection_->Read(&msg) || msg.size() <= 0) {
-    LOG(ERROR) << "Failed to read from stream";
-    return;
+    LOG(ERROR) << "Failed to read from stream. The socket was probably closed";
+    return false;
   }
 
   // TODO(nathanbullock): Need to add support for TLS (brillo:191).
@@ -82,6 +82,9 @@
       if (std::string::npos != msg.find("success")) {
         state_ = XmppState::kStreamRestartedPostAuthentication;
         connection_->Write(BuildXmppStartStreamCommand());
+      } else if (std::string::npos != msg.find("not-authorized")) {
+        state_ = XmppState::kAuthenticationFailed;
+        return false;
       }
       break;
     case XmppState::kStreamRestartedPostAuthentication:
@@ -114,6 +117,7 @@
     default:
       break;
   }
+  return true;
 }
 
 void XmppClient::StartStream() {
diff --git a/buffet/xmpp/xmpp_client.h b/buffet/xmpp/xmpp_client.h
index 9eb5826..6d847ca 100644
--- a/buffet/xmpp/xmpp_client.h
+++ b/buffet/xmpp/xmpp_client.h
@@ -31,7 +31,7 @@
   }
 
   // Needs to be called when new data is available from the connection.
-  void Read();
+  bool Read();
 
   // Start talking to the XMPP server (authenticate, etc.)
   void StartStream();
@@ -41,6 +41,7 @@
     kNotStarted,
     kStarted,
     kAuthenticationStarted,
+    kAuthenticationFailed,
     kStreamRestartedPostAuthentication,
     kBindSent,
     kSessionStarted,
diff --git a/buffet/xmpp/xmpp_client_unittest.cc b/buffet/xmpp/xmpp_client_unittest.cc
index 9736349..ef26f2e 100644
--- a/buffet/xmpp/xmpp_client_unittest.cc
+++ b/buffet/xmpp/xmpp_client_unittest.cc
@@ -34,8 +34,11 @@
     "<required/></starttls><mechanisms "
     "xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"><mechanism>X-OAUTH2</mechanism>"
     "<mechanism>X-GOOGLE-TOKEN</mechanism></mechanisms></stream:features>";
-constexpr char kAuthenticationResponse[] =
+constexpr char kAuthenticationSucceededResponse[] =
     "<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"/>";
+constexpr char kAuthenticationFailedResponse[] =
+    "<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"><not-authorized/>"
+    "</failure></stream:stream>";
 constexpr char kRestartStreamResponse[] =
     "<stream:stream from=\"clouddevices.gserviceaccount.com\" "
     "id=\"BE7D34E0B7589E2A\" version=\"1.0\" "
@@ -143,12 +146,13 @@
             XmppClient::XmppState::kAuthenticationStarted);
 }
 
-TEST_F(XmppClientTest, HandleAuthenticationResponse) {
+TEST_F(XmppClientTest, HandleAuthenticationSucceededResponse) {
   TestHelper::SetState(
       xmpp_client_.get(),
       XmppClient::XmppState::kAuthenticationStarted);
   EXPECT_CALL(*connection_, Read(_))
-      .WillOnce(DoAll(SetArgPointee<0>(kAuthenticationResponse), Return(true)));
+      .WillOnce(DoAll(SetArgPointee<0>(kAuthenticationSucceededResponse),
+                      Return(true)));
   EXPECT_CALL(*connection_, Write(kStartStreamMessage))
       .WillOnce(Return(true));
   xmpp_client_->Read();
@@ -156,6 +160,20 @@
             XmppClient::XmppState::kStreamRestartedPostAuthentication);
 }
 
+TEST_F(XmppClientTest, HandleAuthenticationFailedResponse) {
+  TestHelper::SetState(
+      xmpp_client_.get(),
+      XmppClient::XmppState::kAuthenticationStarted);
+  EXPECT_CALL(*connection_, Read(_))
+      .WillOnce(DoAll(SetArgPointee<0>(kAuthenticationFailedResponse),
+                      Return(true)));
+  EXPECT_CALL(*connection_, Write(_))
+      .Times(0);
+  xmpp_client_->Read();
+  EXPECT_EQ(TestHelper::GetState(*xmpp_client_),
+            XmppClient::XmppState::kAuthenticationFailed);
+}
+
 TEST_F(XmppClientTest, HandleStreamRestartedResponse) {
   TestHelper::SetState(
       xmpp_client_.get(),