// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "remoting/protocol/validating_authenticator.h"

#include <memory>
#include <string>
#include <tuple>
#include <utility>

#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "remoting/protocol/authenticator.h"
#include "remoting/protocol/protocol_mock_objects.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace remoting::protocol {

namespace {

using testing::_;
using testing::Return;

using ValidationResult = ValidatingAuthenticator::Result;

constexpr char kRemoteTestJid[] = "ficticious_jid_for_testing";

}  // namespace

class ValidatingAuthenticatorTest : public testing::Test {
 public:
  ValidatingAuthenticatorTest() = default;

  ValidatingAuthenticatorTest(const ValidatingAuthenticatorTest&) = delete;
  ValidatingAuthenticatorTest& operator=(const ValidatingAuthenticatorTest&) =
      delete;

  ~ValidatingAuthenticatorTest() override = default;

  void ValidateCallback(const std::string& remote_jid,
                        ValidatingAuthenticator::ResultCallback callback);

 protected:
  // testing::Test overrides.
  void SetUp() override;

  // Calls ProcessMessage() on |validating_authenticator_| and blocks until
  // the result callback is called.
  void SendMessageAndWaitForCallback();

  // Used to set up our mock behaviors on the MockAuthenticator object passed
  // to |validating_authenticator_|.  Lifetime of the object is controlled by
  // |validating_authenticator_| so this pointer is no longer valid once
  // the owner is destroyed.
  raw_ptr<testing::NiceMock<MockAuthenticator>, DanglingUntriaged>
      mock_authenticator_ = nullptr;

  // This member is used to drive behavior in |validating_authenticator_| when
  // its validation complete callback is run.
  ValidationResult validation_result_ = ValidationResult::SUCCESS;

  // Tracks whether our validation callback has been called or not.
  bool validate_complete_called_ = false;

  // The object under test.
  std::unique_ptr<ValidatingAuthenticator> validating_authenticator_;

 private:
  base::test::SingleThreadTaskEnvironment task_environment_;
};

void ValidatingAuthenticatorTest::ValidateCallback(
    const std::string& remote_jid,
    ValidatingAuthenticator::ResultCallback callback) {
  validate_complete_called_ = true;
  std::move(callback).Run(validation_result_);
}

void ValidatingAuthenticatorTest::SetUp() {
  mock_authenticator_ = new testing::NiceMock<MockAuthenticator>();
  std::unique_ptr<Authenticator> authenticator(mock_authenticator_);

  validating_authenticator_ = std::make_unique<ValidatingAuthenticator>(
      kRemoteTestJid,
      base::BindRepeating(&ValidatingAuthenticatorTest::ValidateCallback,
                          base::Unretained(this)),
      std::move(authenticator));
}

void ValidatingAuthenticatorTest::SendMessageAndWaitForCallback() {
  base::RunLoop run_loop;
  JingleAuthentication first_message;
  validating_authenticator_->ProcessMessage(first_message,
                                            run_loop.QuitClosure());
  run_loop.Run();
}

TEST_F(ValidatingAuthenticatorTest, ValidConnection_SingleMessage) {
  EXPECT_CALL(*mock_authenticator_, ProcessMessage(_, _))
      .WillOnce(base::test::RunOnceCallback<1>());

  ON_CALL(*mock_authenticator_, state())
      .WillByDefault(Return(Authenticator::ACCEPTED));

  SendMessageAndWaitForCallback();
  ASSERT_TRUE(validate_complete_called_);
  ASSERT_EQ(validating_authenticator_->state(), Authenticator::ACCEPTED);
}

TEST_F(ValidatingAuthenticatorTest, ValidConnection_TwoMessages) {
  // Send the first message to the authenticator, set the mock up to act
  // like it is waiting for a second message.
  EXPECT_CALL(*mock_authenticator_, ProcessMessage(_, _))
      .Times(2)
      .WillRepeatedly(base::test::RunOnceCallbackRepeatedly<1>());

  EXPECT_CALL(*mock_authenticator_, state())
      .WillRepeatedly(Return(Authenticator::MESSAGE_READY));

  SendMessageAndWaitForCallback();
  ASSERT_FALSE(validate_complete_called_);
  ASSERT_EQ(validating_authenticator_->state(), Authenticator::MESSAGE_READY);

  // Now 'retrieve' the message for the client which resets the state.
  EXPECT_CALL(*mock_authenticator_, state())
      .WillRepeatedly(Return(Authenticator::WAITING_MESSAGE));

  EXPECT_CALL(*mock_authenticator_, GetNextMessage())
      .WillOnce(Return(JingleAuthentication()));

  validating_authenticator_->GetNextMessage();
  ASSERT_EQ(validating_authenticator_->state(), Authenticator::WAITING_MESSAGE);

  // Now send the second message for processing.
  EXPECT_CALL(*mock_authenticator_, state())
      .WillRepeatedly(Return(Authenticator::ACCEPTED));

  SendMessageAndWaitForCallback();
  ASSERT_TRUE(validate_complete_called_);
  ASSERT_EQ(validating_authenticator_->state(), Authenticator::ACCEPTED);
}

TEST_F(ValidatingAuthenticatorTest, ValidConnection_SendBeforeAccept) {
  // This test simulates an authenticator which needs to send a message before
  // transitioning to the ACCEPTED state.
  EXPECT_CALL(*mock_authenticator_, ProcessMessage(_, _))
      .WillOnce(base::test::RunOnceCallback<1>());

  EXPECT_CALL(*mock_authenticator_, state())
      .WillOnce(Return(Authenticator::MESSAGE_READY))
      .WillRepeatedly(Return(Authenticator::ACCEPTED));

  JingleAuthentication message;
  message.spake_message = {1};
  EXPECT_CALL(*mock_authenticator_, GetNextMessage()).WillOnce(Return(message));

  SendMessageAndWaitForCallback();
  ASSERT_TRUE(validate_complete_called_);
  ASSERT_EQ(validating_authenticator_->state(), Authenticator::MESSAGE_READY);

  // Now 'retrieve' the message for the client which resets the state.
  validating_authenticator_->GetNextMessage();
  ASSERT_EQ(validating_authenticator_->state(), Authenticator::ACCEPTED);
}

TEST_F(ValidatingAuthenticatorTest, ValidConnection_ErrorInvalidCredentials) {
  EXPECT_CALL(*mock_authenticator_, ProcessMessage(_, _))
      .WillOnce(base::test::RunOnceCallback<1>());

  ON_CALL(*mock_authenticator_, state())
      .WillByDefault(Return(Authenticator::ACCEPTED));

  validation_result_ = ValidationResult::ERROR_INVALID_CREDENTIALS;

  SendMessageAndWaitForCallback();
  ASSERT_TRUE(validate_complete_called_);
  ASSERT_EQ(validating_authenticator_->state(), Authenticator::REJECTED);
  ASSERT_EQ(validating_authenticator_->rejection_reason(),
            Authenticator::RejectionReason::INVALID_CREDENTIALS);
}

TEST_F(ValidatingAuthenticatorTest, ValidConnection_ErrorRejectedByUser) {
  EXPECT_CALL(*mock_authenticator_, ProcessMessage(_, _))
      .WillOnce(base::test::RunOnceCallback<1>());

  ON_CALL(*mock_authenticator_, state())
      .WillByDefault(Return(Authenticator::ACCEPTED));

  validation_result_ = ValidationResult::ERROR_REJECTED_BY_USER;

  SendMessageAndWaitForCallback();
  ASSERT_TRUE(validate_complete_called_);
  ASSERT_EQ(validating_authenticator_->state(), Authenticator::REJECTED);
  ASSERT_EQ(validating_authenticator_->rejection_reason(),
            Authenticator::RejectionReason::REJECTED_BY_USER);
}

TEST_F(ValidatingAuthenticatorTest,
       ValidConnectionMessageWaiting_ErrorRejectedByUser) {
  EXPECT_CALL(*mock_authenticator_, ProcessMessage(_, _))
      .WillOnce(base::test::RunOnceCallback<1>());

  EXPECT_CALL(*mock_authenticator_, state())
      .WillOnce(Return(Authenticator::MESSAGE_READY))
      .WillRepeatedly(Return(Authenticator::ACCEPTED));

  JingleAuthentication message;
  message.spake_message = {1};
  EXPECT_CALL(*mock_authenticator_, GetNextMessage()).WillOnce(Return(message));

  validation_result_ = ValidationResult::ERROR_REJECTED_BY_USER;

  SendMessageAndWaitForCallback();
  ASSERT_TRUE(validate_complete_called_);
  ASSERT_EQ(validating_authenticator_->state(), Authenticator::REJECTED);
  ASSERT_EQ(validating_authenticator_->rejection_reason(),
            Authenticator::RejectionReason::REJECTED_BY_USER);
}

TEST_F(ValidatingAuthenticatorTest, ValidConnection_ErrorTooManyConnections) {
  EXPECT_CALL(*mock_authenticator_, ProcessMessage(_, _))
      .WillOnce(base::test::RunOnceCallback<1>());

  ON_CALL(*mock_authenticator_, state())
      .WillByDefault(Return(Authenticator::ACCEPTED));

  validation_result_ = ValidationResult::ERROR_TOO_MANY_CONNECTIONS;

  SendMessageAndWaitForCallback();
  ASSERT_TRUE(validate_complete_called_);
  ASSERT_EQ(validating_authenticator_->state(), Authenticator::REJECTED);
  ASSERT_EQ(validating_authenticator_->rejection_reason(),
            Authenticator::RejectionReason::TOO_MANY_CONNECTIONS);
}

TEST_F(ValidatingAuthenticatorTest, InvalidConnection_InvalidCredentials) {
  EXPECT_CALL(*mock_authenticator_, ProcessMessage(_, _))
      .WillOnce(base::test::RunOnceCallback<1>());

  ON_CALL(*mock_authenticator_, state())
      .WillByDefault(Return(Authenticator::REJECTED));

  ON_CALL(*mock_authenticator_, rejection_reason())
      .WillByDefault(
          Return(Authenticator::RejectionReason::INVALID_CREDENTIALS));

  // Verify validation callback is not called for invalid connections.
  SendMessageAndWaitForCallback();
  ASSERT_FALSE(validate_complete_called_);
  ASSERT_EQ(validating_authenticator_->state(), Authenticator::REJECTED);
  ASSERT_EQ(validating_authenticator_->rejection_reason(),
            Authenticator::RejectionReason::INVALID_CREDENTIALS);
}

TEST_F(ValidatingAuthenticatorTest, InvalidConnection_InvalidAccount) {
  EXPECT_CALL(*mock_authenticator_, ProcessMessage(_, _))
      .WillOnce(base::test::RunOnceCallback<1>());

  ON_CALL(*mock_authenticator_, state())
      .WillByDefault(Return(Authenticator::REJECTED));

  ON_CALL(*mock_authenticator_, rejection_reason())
      .WillByDefault(
          Return(Authenticator::RejectionReason::INVALID_ACCOUNT_ID));

  // Verify validation callback is not called for invalid connections.
  SendMessageAndWaitForCallback();
  ASSERT_FALSE(validate_complete_called_);
  ASSERT_EQ(validating_authenticator_->state(), Authenticator::REJECTED);
  ASSERT_EQ(validating_authenticator_->rejection_reason(),
            Authenticator::RejectionReason::INVALID_ACCOUNT_ID);
}

TEST_F(ValidatingAuthenticatorTest, InvalidConnection_InvalidState) {
  EXPECT_CALL(*mock_authenticator_, ProcessMessage(_, _))
      .WillOnce(base::test::RunOnceCallback<1>());

  ON_CALL(*mock_authenticator_, state())
      .WillByDefault(Return(Authenticator::REJECTED));

  ON_CALL(*mock_authenticator_, rejection_reason())
      .WillByDefault(Return(Authenticator::RejectionReason::INVALID_STATE));

  // Verify validation callback is not called for invalid connections.
  SendMessageAndWaitForCallback();
  ASSERT_FALSE(validate_complete_called_);
  ASSERT_EQ(validating_authenticator_->state(), Authenticator::REJECTED);
  ASSERT_EQ(validating_authenticator_->rejection_reason(),
            Authenticator::RejectionReason::INVALID_STATE);
}

TEST_F(ValidatingAuthenticatorTest, StateChangeAfterAccepted_Propagated) {
  base::MockRepeatingClosure state_changed_after_accepted;
  validating_authenticator_->set_state_change_after_accepted_callback(
      state_changed_after_accepted.Get());
  EXPECT_CALL(*mock_authenticator_, ProcessMessage(_, _))
      .WillOnce(base::test::RunOnceCallback<1>());

  ON_CALL(*mock_authenticator_, state())
      .WillByDefault(Return(Authenticator::ACCEPTED));

  SendMessageAndWaitForCallback();
  ASSERT_EQ(validating_authenticator_->state(), Authenticator::ACCEPTED);

  EXPECT_CALL(*mock_authenticator_, state())
      .WillOnce(Return(Authenticator::REJECTED));
  EXPECT_CALL(*mock_authenticator_, rejection_reason())
      .WillOnce(
          Return(Authenticator::RejectionReason::REAUTHZ_POLICY_CHECK_FAILED));
  EXPECT_CALL(state_changed_after_accepted, Run());

  mock_authenticator_->NotifyStateChangeAfterAccepted();

  ASSERT_EQ(validating_authenticator_->state(), Authenticator::REJECTED);
  ASSERT_EQ(validating_authenticator_->rejection_reason(),
            Authenticator::RejectionReason::REAUTHZ_POLICY_CHECK_FAILED);
}

}  // namespace remoting::protocol
