// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/internal_auth.h"

#include <algorithm>

#include "base/lazy_instance.h"
#include "base/message_loop/message_loop.h"
#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace chrome {

class InternalAuthTest : public ::testing::Test {
 public:
  InternalAuthTest() {
    long_string_ = "seed";
    for (int i = 20; i--;)
      long_string_ += long_string_;
  }
  virtual ~InternalAuthTest() {}

  virtual void SetUp() {
  }

  virtual void TearDown() {
  }

  base::MessageLoop message_loop_;
  std::string long_string_;
};

TEST_F(InternalAuthTest, BasicGeneration) {
  std::map<std::string, std::string> map;
  map["key"] = "value";
  std::string token = InternalAuthGeneration::GeneratePassport(
      "zapata", map);
  ASSERT_GT(token.size(), 10u);  // short token is insecure.

  map["key2"] = "value2";
  token = InternalAuthGeneration::GeneratePassport("zapata", map);
  ASSERT_GT(token.size(), 10u);
}

TEST_F(InternalAuthTest, DoubleGeneration) {
  std::map<std::string, std::string> map;
  map["key"] = "value";
  std::string token1 = InternalAuthGeneration::GeneratePassport(
      "zapata", map);
  ASSERT_GT(token1.size(), 10u);

  std::string token2 = InternalAuthGeneration::GeneratePassport(
      "zapata", map);
  ASSERT_GT(token2.size(), 10u);
  // tokens are different even if credentials coincide.
  ASSERT_NE(token1, token2);
}

TEST_F(InternalAuthTest, BadGeneration) {
  std::map<std::string, std::string> map;
  map["key"] = "value";
  // Trying huge domain.
  std::string token = InternalAuthGeneration::GeneratePassport(
      long_string_, map);
  ASSERT_TRUE(token.empty());
  ASSERT_FALSE(InternalAuthVerification::VerifyPassport(
      token, long_string_, map));

  // Trying empty domain.
  token = InternalAuthGeneration::GeneratePassport(std::string(), map);
  ASSERT_TRUE(token.empty());
  ASSERT_FALSE(
      InternalAuthVerification::VerifyPassport(token, std::string(), map));

  std::string dummy("abcdefghij");
  for (size_t i = 1000; i--;) {
    std::string key = dummy;
    std::next_permutation(dummy.begin(), dummy.end());
    std::string value = dummy;
    std::next_permutation(dummy.begin(), dummy.end());
    map[key] = value;
  }
  // Trying huge var=value map.
  token = InternalAuthGeneration::GeneratePassport("zapata", map);
  ASSERT_TRUE(token.empty());
  ASSERT_FALSE(InternalAuthVerification::VerifyPassport(token, "zapata", map));

  map.clear();
  map[std::string()] = "value";
  // Trying empty key.
  token = InternalAuthGeneration::GeneratePassport("zapata", map);
  ASSERT_TRUE(token.empty());
  ASSERT_FALSE(InternalAuthVerification::VerifyPassport(token, "zapata", map));
}

TEST_F(InternalAuthTest, BasicVerification) {
  std::map<std::string, std::string> map;
  map["key"] = "value";
  std::string token = InternalAuthGeneration::GeneratePassport("zapata", map);
  ASSERT_GT(token.size(), 10u);
  ASSERT_TRUE(InternalAuthVerification::VerifyPassport(token, "zapata", map));
  // Passport can not be reused.
  for (int i = 1000; i--;) {
    ASSERT_FALSE(InternalAuthVerification::VerifyPassport(
        token, "zapata", map));
  }
}

TEST_F(InternalAuthTest, BruteForce) {
  std::map<std::string, std::string> map;
  map["key"] = "value";
  std::string token = InternalAuthGeneration::GeneratePassport("zapata", map);
  ASSERT_GT(token.size(), 10u);

  // Trying bruteforce.
  std::string dummy = token;
  for (size_t i = 100; i--;) {
    std::next_permutation(dummy.begin(), dummy.end());
    ASSERT_FALSE(InternalAuthVerification::VerifyPassport(
        dummy, "zapata", map));
  }
  dummy = token;
  for (size_t i = 100; i--;) {
    std::next_permutation(dummy.begin(), dummy.begin() + dummy.size() / 2);
    ASSERT_FALSE(InternalAuthVerification::VerifyPassport(
        dummy, "zapata", map));
  }
  // We brute forced just too little, so original token must not expire yet.
  ASSERT_TRUE(InternalAuthVerification::VerifyPassport(token, "zapata", map));
}

TEST_F(InternalAuthTest, ExpirationAndBruteForce) {
  int kCustomVerificationWindow = 2;
  InternalAuthVerification::set_verification_window_seconds(
      kCustomVerificationWindow);

  std::map<std::string, std::string> map;
  map["key"] = "value";
  std::string token = InternalAuthGeneration::GeneratePassport("zapata", map);
  ASSERT_GT(token.size(), 10u);

  // We want to test token expiration, so we need to wait some amount of time,
  // so we are brute-forcing during this time.
  base::Time timestamp = base::Time::Now();
  std::string dummy1 = token;
  std::string dummy2 = token;
  for (;;) {
    for (size_t i = 100; i--;) {
      std::next_permutation(dummy1.begin(), dummy1.end());
      ASSERT_FALSE(InternalAuthVerification::VerifyPassport(
          dummy1, "zapata", map));
    }
    for (size_t i = 100; i--;) {
      std::next_permutation(dummy2.begin(), dummy2.begin() + dummy2.size() / 2);
      ASSERT_FALSE(InternalAuthVerification::VerifyPassport(
          dummy2, "zapata", map));
    }
    if (base::Time::Now() - timestamp > base::TimeDelta::FromSeconds(
        kCustomVerificationWindow + 1)) {
      break;
    }
  }
  ASSERT_FALSE(InternalAuthVerification::VerifyPassport(token, "zapata", map));
  // Reset verification window to default.
  InternalAuthVerification::set_verification_window_seconds(0);
}

TEST_F(InternalAuthTest, ChangeKey) {
  std::map<std::string, std::string> map;
  map["key"] = "value";
  std::string token = InternalAuthGeneration::GeneratePassport("zapata", map);
  ASSERT_GT(token.size(), 10u);

  InternalAuthGeneration::GenerateNewKey();
  // Passport should survive key change.
  ASSERT_TRUE(InternalAuthVerification::VerifyPassport(token, "zapata", map));

  token = InternalAuthGeneration::GeneratePassport("zapata", map);
  ASSERT_GT(token.size(), 10u);
  for (int i = 20; i--;)
    InternalAuthGeneration::GenerateNewKey();
  // Passport should not survive series of key changes.
  ASSERT_FALSE(InternalAuthVerification::VerifyPassport(token, "zapata", map));
}

}  // namespace chrome