// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////

#include "tink/jwt/raw_jwt.h"

#include <string>
#include <vector>

#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/status/statusor.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "tink/util/test_matchers.h"

using ::crypto::tink::test::IsOk;
using ::crypto::tink::test::IsOkAndHolds;
using ::testing::IsEmpty;
using ::testing::Not;
using ::testing::UnorderedElementsAreArray;

namespace crypto {
namespace tink {

TEST(RawJwt, GetTypeHeaderIssuerSubjectJwtIdOK) {
  absl::StatusOr<RawJwt> jwt = RawJwtBuilder()
                                   .SetTypeHeader("typeHeader")
                                   .SetIssuer("issuer")
                                   .SetSubject("subject")
                                   .SetJwtId("jwt_id")
                                   .WithoutExpiration()
                                   .Build();
  ASSERT_THAT(jwt, IsOk());

  EXPECT_TRUE(jwt->HasTypeHeader());
  EXPECT_THAT(jwt->GetTypeHeader(), IsOkAndHolds("typeHeader"));
  EXPECT_TRUE(jwt->HasIssuer());
  EXPECT_THAT(jwt->GetIssuer(), IsOkAndHolds("issuer"));
  EXPECT_TRUE(jwt->HasSubject());
  EXPECT_THAT(jwt->GetSubject(), IsOkAndHolds("subject"));
  EXPECT_TRUE(jwt->HasJwtId());
  EXPECT_THAT(jwt->GetJwtId(), IsOkAndHolds("jwt_id"));
}

TEST(RawJwt, TimestampsOK) {
  absl::Time nbf = absl::FromUnixSeconds(1234567890);
  absl::Time iat = absl::FromUnixSeconds(1234567891);
  absl::Time exp = absl::FromUnixSeconds(1234567892);
  absl::StatusOr<RawJwt> jwt = RawJwtBuilder()
                                   .SetNotBefore(nbf)
                                   .SetIssuedAt(iat)
                                   .SetExpiration(exp)
                                   .Build();
  ASSERT_THAT(jwt, IsOk());

  EXPECT_TRUE(jwt->HasNotBefore());
  EXPECT_THAT(jwt->GetNotBefore(), IsOkAndHolds(nbf));

  EXPECT_TRUE(jwt->HasIssuedAt());
  EXPECT_THAT(jwt->GetIssuedAt(), IsOkAndHolds(iat));

  EXPECT_TRUE(jwt->HasExpiration());
  EXPECT_THAT(jwt->GetExpiration(), IsOkAndHolds(exp));
}

TEST(RawJwt, ExpWithMillisAlwaysRoundDown) {
  absl::StatusOr<RawJwt> jwt =
      RawJwtBuilder().SetExpiration(absl::FromUnixMillis(123999)).Build();
  ASSERT_THAT(jwt, IsOk());

  EXPECT_TRUE(jwt->HasExpiration());
  absl::StatusOr<absl::Time> exp = jwt->GetExpiration();
  EXPECT_THAT(exp, IsOkAndHolds(absl::FromUnixSeconds(123)));
}

TEST(RawJwt, NbfWithMillisAlwaysRoundDown) {
  absl::StatusOr<RawJwt> jwt = RawJwtBuilder()
                                   .SetNotBefore(absl::FromUnixMillis(123999))
                                   .WithoutExpiration()
                                   .Build();
  ASSERT_THAT(jwt, IsOk());

  EXPECT_TRUE(jwt->HasNotBefore());
  absl::StatusOr<absl::Time> nbf = jwt->GetNotBefore();
  EXPECT_THAT(nbf, IsOkAndHolds(absl::FromUnixSeconds(123)));
}

TEST(RawJwt, IatWithMillisAlwaysRoundDown) {
  absl::StatusOr<RawJwt> jwt = RawJwtBuilder()
                                   .SetIssuedAt(absl::FromUnixMillis(123999))
                                   .WithoutExpiration()
                                   .Build();
  ASSERT_THAT(jwt, IsOk());

  EXPECT_TRUE(jwt->HasIssuedAt());
  absl::StatusOr<absl::Time> iat = jwt->GetIssuedAt();
  EXPECT_THAT(iat, IsOkAndHolds(absl::FromUnixSeconds(123)));
}

TEST(RawJwt, LargeExpirationWorks) {
  absl::Time large = absl::FromUnixSeconds(253402300799);  // year 9999
  absl::StatusOr<RawJwt> jwt = RawJwtBuilder()
                                   .SetNotBefore(large)
                                   .SetIssuedAt(large)
                                   .SetExpiration(large)
                                   .Build();
  ASSERT_THAT(jwt, IsOk());

  EXPECT_TRUE(jwt->HasExpiration());
  EXPECT_TRUE(jwt->HasIssuedAt());
  EXPECT_TRUE(jwt->HasNotBefore());
  absl::StatusOr<absl::Time> exp = jwt->GetExpiration();
  EXPECT_THAT(exp, IsOkAndHolds(large));
  absl::StatusOr<absl::Time> iat = jwt->GetIssuedAt();
  EXPECT_THAT(iat, IsOkAndHolds(large));
  absl::StatusOr<absl::Time> nbf = jwt->GetNotBefore();
  EXPECT_THAT(nbf, IsOkAndHolds(large));
}

TEST(RawJwt, TooLargeTimestampsFail) {
  absl::Time too_large = absl::FromUnixSeconds(253402300800);  // year 10000
  EXPECT_THAT(RawJwtBuilder().SetExpiration(too_large).Build(), Not(IsOk()));
  EXPECT_THAT(
      RawJwtBuilder().SetIssuedAt(too_large).WithoutExpiration().Build(),
      Not(IsOk()));
  EXPECT_THAT(
      RawJwtBuilder().SetNotBefore(too_large).WithoutExpiration().Build(),
      Not(IsOk()));
}

TEST(RawJwt, NegativeTimestampsFail) {
  absl::Time neg = absl::FromUnixMillis(-1);
  EXPECT_THAT(RawJwtBuilder().SetExpiration(neg).Build(), Not(IsOk()));
  EXPECT_THAT(RawJwtBuilder().SetIssuedAt(neg).WithoutExpiration().Build(),
              Not(IsOk()));
  EXPECT_THAT(RawJwtBuilder().SetNotBefore(neg).WithoutExpiration().Build(),
              Not(IsOk()));
}

TEST(RawJwt, SetExpirationAndWithoutExpirationFail) {
  absl::Time exp = absl::FromUnixMillis(12345);
  EXPECT_THAT(RawJwtBuilder().SetExpiration(exp).WithoutExpiration().Build(),
              Not(IsOk()));
}

TEST(RawJwt, NeitherSetExpirationNorWithoutExpirationFail) {
  EXPECT_THAT(RawJwtBuilder().Build(), Not(IsOk()));
}

TEST(RawJwt, SetAudienceAndGetAudiencesOK) {
  absl::StatusOr<RawJwt> jwt =
      RawJwtBuilder().SetAudience("audience").WithoutExpiration().Build();
  ASSERT_THAT(jwt, IsOk());

  std::vector<std::string> expected = {"audience"};
  EXPECT_TRUE(jwt->HasAudiences());
  EXPECT_THAT(jwt->GetAudiences(), IsOkAndHolds(expected));
}

TEST(RawJwt, SetAudiencesAndGetAudiencesOK) {
  absl::StatusOr<RawJwt> jwt = RawJwtBuilder()
                                   .SetAudiences({"audience1", "audience2"})
                                   .WithoutExpiration()
                                   .Build();
  ASSERT_THAT(jwt, IsOk());

  std::vector<std::string> expected = {"audience1", "audience2"};
  EXPECT_TRUE(jwt->HasAudiences());
  EXPECT_THAT(jwt->GetAudiences(), IsOkAndHolds(expected));
}

TEST(RawJwt, AddGetAudiencesOK) {
  absl::StatusOr<RawJwt> jwt = RawJwtBuilder()
                                   .AddAudience("audience1")
                                   .AddAudience("audience2")
                                   .WithoutExpiration()
                                   .Build();
  ASSERT_THAT(jwt, IsOk());

  std::vector<std::string> expected = {"audience1", "audience2"};
  EXPECT_TRUE(jwt->HasAudiences());
  EXPECT_THAT(jwt->GetAudiences(), IsOkAndHolds(expected));
}

TEST(RawJwt, SetAudienceStringAud) {
  absl::StatusOr<RawJwt> jwt =
      RawJwtBuilder().SetAudience("audience").WithoutExpiration().Build();
  ASSERT_THAT(jwt, IsOk());

  EXPECT_THAT(jwt->GetJsonPayload(), IsOkAndHolds(R"({"aud":"audience"})"));
}

TEST(RawJwt, AddAudienceListAud) {
  absl::StatusOr<RawJwt> jwt =
      RawJwtBuilder().AddAudience("audience").WithoutExpiration().Build();
  ASSERT_THAT(jwt, IsOk());

  EXPECT_THAT(jwt->GetJsonPayload(), IsOkAndHolds(R"({"aud":["audience"]})"));
}

TEST(RawJwt, SetAndAddAudienceFail) {
  EXPECT_THAT(RawJwtBuilder()
                  .SetAudience("audience1")
                  .AddAudience("audience2")
                  .Build()
                  .status(),
              Not(IsOk()));
  EXPECT_THAT(RawJwtBuilder()
                  .AddAudience("audience2")
                  .SetAudience("audience1")
                  .Build()
                  .status(),
              Not(IsOk()));
}

TEST(RawJwt, GetCustomClaimOK) {
  absl::StatusOr<RawJwt> jwt =
      RawJwtBuilder()
          .WithoutExpiration()
          .AddNullClaim("null_claim")
          .AddBooleanClaim("boolean_claim", true)
          .AddNumberClaim("number_claim", 123.456)
          .AddStringClaim("string_claim", "a string")
          .AddJsonObjectClaim("object_claim", R"({ "number": 123.456})")
          .AddJsonArrayClaim("array_claim", R"([1, "one", 1.2, true])")
          .Build();
  ASSERT_THAT(jwt, IsOk());

  EXPECT_TRUE(jwt->IsNullClaim("null_claim"));
  EXPECT_TRUE(jwt->HasBooleanClaim("boolean_claim"));
  EXPECT_THAT(jwt->GetBooleanClaim("boolean_claim"), IsOkAndHolds(true));
  EXPECT_TRUE(jwt->HasNumberClaim("number_claim"));
  EXPECT_THAT(jwt->GetNumberClaim("number_claim"), IsOkAndHolds(123.456));
  EXPECT_TRUE(jwt->HasStringClaim("string_claim"));
  EXPECT_THAT(jwt->GetStringClaim("string_claim"), IsOkAndHolds("a string"));
  EXPECT_TRUE(jwt->HasJsonObjectClaim("object_claim"));
  EXPECT_THAT(jwt->GetJsonObjectClaim("object_claim"),
              IsOkAndHolds(R"({"number":123.456})"));
  EXPECT_TRUE(jwt->HasJsonArrayClaim("array_claim"));
  EXPECT_THAT(jwt->GetJsonArrayClaim("array_claim"),
              IsOkAndHolds(R"([1,"one",1.2,true])"));

  std::vector<std::string> expected_claim_names = {
      "object_claim", "number_claim", "boolean_claim",
      "array_claim",  "null_claim",   "string_claim"};
  EXPECT_THAT(jwt->CustomClaimNames(),
              UnorderedElementsAreArray(expected_claim_names));
}

TEST(RawJwt, HasCustomClaimIsFalseForWrongType) {
  absl::StatusOr<RawJwt> jwt = RawJwtBuilder()
                                   .WithoutExpiration()
                                   .AddNullClaim("null_claim")
                                   .AddBooleanClaim("boolean_claim", true)
                                   .AddNumberClaim("number_claim", 123.456)
                                   .AddStringClaim("string_claim", "a string")
                                   .Build();
  ASSERT_THAT(jwt, IsOk());

  EXPECT_FALSE(jwt->IsNullClaim("boolean_claim"));
  EXPECT_FALSE(jwt->HasBooleanClaim("number_claim"));
  EXPECT_FALSE(jwt->HasNumberClaim("string_claim"));
  EXPECT_FALSE(jwt->HasStringClaim("null_claim"));
}

TEST(RawJwt, HasAlwaysReturnsFalseForRegisteredClaims) {
  absl::Time now = absl::Now();
  absl::StatusOr<RawJwt> jwt = RawJwtBuilder()
                                   .SetIssuer("issuer")
                                   .SetSubject("subject")
                                   .SetJwtId("jwt_id")
                                   .SetNotBefore(now - absl::Seconds(300))
                                   .SetIssuedAt(now)
                                   .SetExpiration(now + absl::Seconds(300))
                                   .Build();
  ASSERT_THAT(jwt, IsOk());

  EXPECT_FALSE(jwt->HasStringClaim("iss"));
  EXPECT_FALSE(jwt->HasStringClaim("sub"));
  EXPECT_FALSE(jwt->HasStringClaim("jti"));
  EXPECT_FALSE(jwt->HasNumberClaim("nbf"));
  EXPECT_FALSE(jwt->HasNumberClaim("iat"));
  EXPECT_FALSE(jwt->HasNumberClaim("exp"));

  EXPECT_THAT(jwt->CustomClaimNames(), IsEmpty());
}

TEST(RawJwt, GetRegisteredCustomClaimNotOK) {
  absl::Time now = absl::Now();
  absl::StatusOr<RawJwt> jwt = RawJwtBuilder()
                                   .SetIssuer("issuer")
                                   .SetSubject("subject")
                                   .SetJwtId("jwt_id")
                                   .SetNotBefore(now - absl::Seconds(300))
                                   .SetIssuedAt(now)
                                   .SetExpiration(now + absl::Seconds(300))
                                   .Build();
  ASSERT_THAT(jwt, IsOk());

  EXPECT_THAT(jwt->GetStringClaim("iss"), Not(IsOk()));
  EXPECT_THAT(jwt->GetStringClaim("sub"), Not(IsOk()));
  EXPECT_THAT(jwt->GetStringClaim("jti"), Not(IsOk()));
  EXPECT_THAT(jwt->GetNumberClaim("nbf"), Not(IsOk()));
  EXPECT_THAT(jwt->GetNumberClaim("iat"), Not(IsOk()));
  EXPECT_THAT(jwt->GetNumberClaim("exp"), Not(IsOk()));
}

TEST(RawJwt, SetRegisteredCustomClaimNotOK) {
  EXPECT_THAT(RawJwtBuilder()
                  .WithoutExpiration()
                  .AddStringClaim("iss", "issuer")
                  .Build(),
              Not(IsOk()));
  EXPECT_THAT(RawJwtBuilder()
                  .WithoutExpiration()
                  .AddStringClaim("sub", "issuer")
                  .Build(),
              Not(IsOk()));
  EXPECT_THAT(RawJwtBuilder()
                  .WithoutExpiration()
                  .AddStringClaim("jti", "issuer")
                  .Build(),
              Not(IsOk()));
  EXPECT_THAT(
      RawJwtBuilder().WithoutExpiration().AddNumberClaim("nbf", 123).Build(),
      Not(IsOk()));
  EXPECT_THAT(
      RawJwtBuilder().WithoutExpiration().AddNumberClaim("iat", 123).Build(),
      Not(IsOk()));
  EXPECT_THAT(
      RawJwtBuilder().WithoutExpiration().AddNumberClaim("exp", 123).Build(),
      Not(IsOk()));
  EXPECT_THAT(
      RawJwtBuilder().WithoutExpiration().AddBooleanClaim("iss", true).Build(),
      Not(IsOk()));
  EXPECT_THAT(RawJwtBuilder().WithoutExpiration().AddNullClaim("iss").Build(),
              Not(IsOk()));
  EXPECT_THAT(RawJwtBuilder()
                  .WithoutExpiration()
                  .AddJsonObjectClaim("iss", "{\"1\": 2}")
                  .Build(),
              Not(IsOk()));
  EXPECT_THAT(RawJwtBuilder()
                  .WithoutExpiration()
                  .AddJsonArrayClaim("iss", "[1,2]")
                  .Build(),
              Not(IsOk()));
}

TEST(RawJwt, SetInvalidJsonObjectClaimNotOK) {
  EXPECT_THAT(RawJwtBuilder()
                  .WithoutExpiration()
                  .AddJsonObjectClaim("obj", "invalid")
                  .Build(),
              Not(IsOk()));
  EXPECT_THAT(RawJwtBuilder()
                  .WithoutExpiration()
                  .AddJsonObjectClaim("obj", R"("string")")
                  .Build(),
              Not(IsOk()));
  EXPECT_THAT(RawJwtBuilder()
                  .WithoutExpiration()
                  .AddJsonObjectClaim("obj", "42")
                  .Build(),
              Not(IsOk()));
  EXPECT_THAT(RawJwtBuilder()
                  .WithoutExpiration()
                  .AddJsonObjectClaim("obj", "[1,2]")
                  .Build(),
              Not(IsOk()));
}

TEST(RawJwt, SetInvalidJsonArrayClaimNotOK) {
  EXPECT_THAT(RawJwtBuilder()
                  .WithoutExpiration()
                  .AddJsonArrayClaim("arr", "invalid")
                  .Build(),
              Not(IsOk()));
  EXPECT_THAT(RawJwtBuilder()
                  .WithoutExpiration()
                  .AddJsonArrayClaim("arr", R"("string")")
                  .Build(),
              Not(IsOk()));
  EXPECT_THAT(RawJwtBuilder()
                  .WithoutExpiration()
                  .AddJsonArrayClaim("arr", "42")
                  .Build(),
              Not(IsOk()));
  EXPECT_THAT(RawJwtBuilder()
                  .WithoutExpiration()
                  .AddJsonArrayClaim("arr", R"({"1": 2})")
                  .Build(),
              Not(IsOk()));
}

TEST(RawJwt, EmptyTokenHasAndIsReturnsFalse) {
  absl::StatusOr<RawJwt> jwt = RawJwtBuilder().WithoutExpiration().Build();
  ASSERT_THAT(jwt, IsOk());

  EXPECT_FALSE(jwt->HasTypeHeader());
  EXPECT_FALSE(jwt->HasIssuer());
  EXPECT_FALSE(jwt->HasSubject());
  EXPECT_FALSE(jwt->HasAudiences());
  EXPECT_FALSE(jwt->HasJwtId());
  EXPECT_FALSE(jwt->HasExpiration());
  EXPECT_FALSE(jwt->HasNotBefore());
  EXPECT_FALSE(jwt->HasIssuedAt());
  EXPECT_FALSE(jwt->IsNullClaim("null_claim"));
  EXPECT_FALSE(jwt->HasBooleanClaim("boolean_claim"));
  EXPECT_FALSE(jwt->HasNumberClaim("number_claim"));
  EXPECT_FALSE(jwt->HasStringClaim("string_claim"));
  EXPECT_FALSE(jwt->HasJsonObjectClaim("object_claim"));
  EXPECT_FALSE(jwt->HasJsonArrayClaim("array_claim"));
}

TEST(RawJwt, EmptyTokenGetReturnsNotOK) {
  absl::StatusOr<RawJwt> jwt = RawJwtBuilder().WithoutExpiration().Build();
  ASSERT_THAT(jwt, IsOk());

  EXPECT_THAT(jwt->GetTypeHeader(), Not(IsOk()));
  EXPECT_THAT(jwt->GetIssuer(), Not(IsOk()));
  EXPECT_THAT(jwt->GetSubject(), Not(IsOk()));
  EXPECT_THAT(jwt->GetAudiences(), Not(IsOk()));
  EXPECT_THAT(jwt->GetJwtId(), Not(IsOk()));
  EXPECT_THAT(jwt->GetExpiration(), Not(IsOk()));
  EXPECT_THAT(jwt->GetNotBefore(), Not(IsOk()));
  EXPECT_THAT(jwt->GetIssuedAt(), Not(IsOk()));
  EXPECT_FALSE(jwt->IsNullClaim("null_claim"));
  EXPECT_THAT(jwt->GetBooleanClaim("boolean_claim"), Not(IsOk()));
  EXPECT_THAT(jwt->GetNumberClaim("number_claim"), Not(IsOk()));
  EXPECT_THAT(jwt->GetStringClaim("string_claim"), Not(IsOk()));
  EXPECT_THAT(jwt->GetJsonObjectClaim("object_claim"), Not(IsOk()));
  EXPECT_THAT(jwt->GetJsonArrayClaim("array_claim"), Not(IsOk()));
}

TEST(RawJwt, BuildCanBeCalledTwice) {
  auto builder = RawJwtBuilder()
                     .SetIssuer("issuer")
                     .SetSubject("subject")
                     .WithoutExpiration();
  absl::StatusOr<RawJwt> jwt = builder.Build();
  ASSERT_THAT(jwt, IsOk());

  builder.SetSubject("subject2");
  absl::StatusOr<RawJwt> jwt2 = builder.Build();
  ASSERT_THAT(jwt2, IsOk());

  EXPECT_THAT(jwt->GetIssuer(), IsOkAndHolds("issuer"));
  EXPECT_THAT(jwt->GetSubject(), IsOkAndHolds("subject"));
  EXPECT_THAT(jwt2->GetIssuer(), IsOkAndHolds("issuer"));
  EXPECT_THAT(jwt2->GetSubject(), IsOkAndHolds("subject2"));
}

TEST(RawJwt, GetJsonPayload) {
  absl::StatusOr<RawJwt> jwt =
      RawJwtBuilder().SetIssuer("issuer").WithoutExpiration().Build();
  ASSERT_THAT(jwt, IsOk());

  EXPECT_THAT(jwt->GetJsonPayload(), IsOkAndHolds(R"({"iss":"issuer"})"));
}

TEST(RawJwt, IntegerIsEncodedAsInteger) {
  absl::StatusOr<RawJwt> jwt =
      RawJwtBuilder().AddNumberClaim("num", 1).WithoutExpiration().Build();
  ASSERT_THAT(jwt, IsOk());

  EXPECT_THAT(jwt->GetJsonPayload(), IsOkAndHolds(R"({"num":1})"));
}

TEST(RawJwt, GetExpirationJsonPayload) {
  absl::StatusOr<RawJwt> jwt =
      RawJwtBuilder().SetExpiration(absl::FromUnixSeconds(2218027244)).Build();
  ASSERT_THAT(jwt, IsOk());

  EXPECT_THAT(jwt->GetJsonPayload(), IsOkAndHolds(R"({"exp":2218027244})"));
}

TEST(RawJwt, GetNanoExpirationJsonPayload) {
  absl::StatusOr<RawJwt> jwt =
      RawJwtBuilder().SetExpiration(absl::FromUnixNanos(123456789012)).Build();
  ASSERT_THAT(jwt, IsOk());

  EXPECT_THAT(jwt->GetJsonPayload(), IsOkAndHolds(R"({"exp":123})"));
}

}  // namespace tink
}  // namespace crypto
