/*
 * Copyright 2022-present MongoDB, Inc.
 *
 * 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 "test-mongocrypt.h"

/* Test authentication with the "gcp" KMS provider. */

static void
_test_createdatakey_with_credentials (_mongocrypt_tester_t *tester)
{
   mongocrypt_t *crypt;
   mongocrypt_ctx_t *ctx;
   mongocrypt_kms_ctx_t *kms;
   const char *kek = "{"
                     "'provider': 'gcp',"
                     "'projectId': 'test-projectId',"
                     "'location': 'test-location',"
                     "'keyRing': 'test-keyRing',"
                     "'keyName': 'test-keyName'"
                     "}";

   crypt = _mongocrypt_tester_mongocrypt (TESTER_MONGOCRYPT_DEFAULT);
   ctx = mongocrypt_ctx_new (crypt);
   ASSERT_OK (mongocrypt_ctx_setopt_key_encryption_key (ctx, TEST_BSON (kek)),
              ctx);
   ASSERT_OK (mongocrypt_ctx_datakey_init (ctx), ctx);

   ASSERT_STATE_EQUAL (mongocrypt_ctx_state (ctx), MONGOCRYPT_CTX_NEED_KMS);
   /* Assert first CTX_NEED_KMS state requests access token. */
   {
      kms = mongocrypt_ctx_next_kms_ctx (ctx);
      ASSERT_OR_PRINT_MSG (kms, "expected KMS context, got NULL");

      const char *endpoint;
      mongocrypt_kms_ctx_endpoint (kms, &endpoint);
      ASSERT_STREQUAL ("oauth2.googleapis.com:443", endpoint);

      /* Satisfy request. */
      ASSERT_OK (mongocrypt_kms_ctx_feed (
                    kms, TEST_FILE ("./test/data/gcp-auth/oauth-response.txt")),
                 kms);
      ASSERT_CMPINT ((int) mongocrypt_kms_ctx_bytes_needed (kms), ==, 0);

      kms = mongocrypt_ctx_next_kms_ctx (ctx);
      ASSERT_OR_PRINT_MSG (NULL == kms,
                           "expected NULL KMS context, got non-NULL");
      ASSERT_OK (mongocrypt_ctx_kms_done (ctx), ctx);
   }

   /* Assert second CTX_NEED_KMS state requests encryption. */
   {
      kms = mongocrypt_ctx_next_kms_ctx (ctx);
      ASSERT_OR_PRINT_MSG (kms, "expected KMS context, got NULL");

      const char *endpoint;
      mongocrypt_kms_ctx_endpoint (kms, &endpoint);
      ASSERT_STREQUAL ("cloudkms.googleapis.com:443", endpoint);

      /* Satisfy request. */
      ASSERT_OK (
         mongocrypt_kms_ctx_feed (
            kms, TEST_FILE ("./test/data/gcp-auth/encrypt-response.txt")),
         kms);
      ASSERT_CMPINT ((int) mongocrypt_kms_ctx_bytes_needed (kms), ==, 0);

      kms = mongocrypt_ctx_next_kms_ctx (ctx);
      ASSERT_OR_PRINT_MSG (NULL == kms,
                           "expected NULL KMS context, got non-NULL");
      ASSERT_OK (mongocrypt_ctx_kms_done (ctx), ctx);
   }

   ASSERT_STATE_EQUAL (mongocrypt_ctx_state (ctx), MONGOCRYPT_CTX_READY);

   mongocrypt_ctx_destroy (ctx);
   mongocrypt_destroy (crypt);
}

static void
_test_encrypt_with_credentials (_mongocrypt_tester_t *tester)
{
   mongocrypt_t *crypt;
   mongocrypt_ctx_t *ctx;
   mongocrypt_kms_ctx_t *kms;
   mongocrypt_binary_t *uuid;
   const char *uuid_data =
      "\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61";

   crypt = _mongocrypt_tester_mongocrypt (TESTER_MONGOCRYPT_DEFAULT);
   ctx = mongocrypt_ctx_new (crypt);
   uuid = mongocrypt_binary_new_from_data ((uint8_t *) uuid_data, UUID_LEN);
   ASSERT_OK (mongocrypt_ctx_setopt_key_id (ctx, uuid), ctx);
   ASSERT_OK (mongocrypt_ctx_setopt_algorithm (
                 ctx, MONGOCRYPT_ALGORITHM_DETERMINISTIC_STR, -1),
              ctx);
   ASSERT_OK (
      mongocrypt_ctx_explicit_encrypt_init (ctx, TEST_BSON ("{'v': 1}")), ctx);

   ASSERT_STATE_EQUAL (mongocrypt_ctx_state (ctx),
                       MONGOCRYPT_CTX_NEED_MONGO_KEYS);
   {
      ASSERT_OK (mongocrypt_ctx_mongo_feed (
                    ctx, TEST_FILE ("./test/data/key-document-gcp.json")),
                 ctx);
      ASSERT_OK (mongocrypt_ctx_mongo_done (ctx), ctx);
   }

   ASSERT_STATE_EQUAL (mongocrypt_ctx_state (ctx), MONGOCRYPT_CTX_NEED_KMS);
   /* Assert first CTX_NEED_KMS state requests access token. */
   {
      kms = mongocrypt_ctx_next_kms_ctx (ctx);
      ASSERT_OR_PRINT_MSG (kms, "expected KMS context, got NULL");

      const char *endpoint;
      mongocrypt_kms_ctx_endpoint (kms, &endpoint);
      ASSERT_STREQUAL ("oauth2.googleapis.com:443", endpoint);

      /* Satisfy request. */
      ASSERT_OK (mongocrypt_kms_ctx_feed (
                    kms, TEST_FILE ("./test/data/gcp-auth/oauth-response.txt")),
                 kms);
      ASSERT_CMPINT ((int) mongocrypt_kms_ctx_bytes_needed (kms), ==, 0);

      kms = mongocrypt_ctx_next_kms_ctx (ctx);
      ASSERT_OR_PRINT_MSG (NULL == kms,
                           "expected NULL KMS context, got non-NULL");
      ASSERT_OK (mongocrypt_ctx_kms_done (ctx), ctx);
   }

   /* Assert second CTX_NEED_KMS state requests decryption. */
   {
      kms = mongocrypt_ctx_next_kms_ctx (ctx);
      ASSERT_OR_PRINT_MSG (kms, "expected KMS context, got NULL");

      const char *endpoint;
      mongocrypt_kms_ctx_endpoint (kms, &endpoint);
      ASSERT_STREQUAL ("cloudkms.googleapis.com:443", endpoint);

      /* Satisfy request. */
      ASSERT_OK (
         mongocrypt_kms_ctx_feed (
            kms, TEST_FILE ("./test/data/gcp-auth/decrypt-response.txt")),
         kms);
      ASSERT_CMPINT ((int) mongocrypt_kms_ctx_bytes_needed (kms), ==, 0);

      kms = mongocrypt_ctx_next_kms_ctx (ctx);
      ASSERT_OR_PRINT_MSG (NULL == kms,
                           "expected NULL KMS context, got non-NULL");
      ASSERT_OK (mongocrypt_ctx_kms_done (ctx), ctx);
   }

   ASSERT_STATE_EQUAL (mongocrypt_ctx_state (ctx), MONGOCRYPT_CTX_READY);

   mongocrypt_binary_destroy (uuid);
   mongocrypt_ctx_destroy (ctx);
   mongocrypt_destroy (crypt);
}

static void
_test_createdatakey_with_accesstoken (_mongocrypt_tester_t *tester)
{
   mongocrypt_t *crypt;
   mongocrypt_ctx_t *ctx;
   mongocrypt_kms_ctx_t *kms;
   const char *kek = "{"
                     "'provider': 'gcp',"
                     "'projectId': 'test-projectId',"
                     "'location': 'test-location',"
                     "'keyRing': 'test-keyRing',"
                     "'keyName': 'test-keyName'"
                     "}";

   crypt = mongocrypt_new ();
   ASSERT_OK (
      mongocrypt_setopt_kms_providers (crypt, TEST_BSON ("{'gcp': {}}")),
      crypt);
   mongocrypt_setopt_use_need_kms_credentials_state (crypt);
   ASSERT_OK (mongocrypt_init (crypt), crypt);
   ctx = mongocrypt_ctx_new (crypt);
   ASSERT_OK (mongocrypt_ctx_setopt_key_encryption_key (ctx, TEST_BSON (kek)),
              ctx);
   ASSERT_OK (mongocrypt_ctx_datakey_init (ctx), ctx);

   ASSERT_STATE_EQUAL (mongocrypt_ctx_state (ctx),
                       MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS);
   {
      ASSERT_OK (mongocrypt_ctx_provide_kms_providers (
                    ctx, TEST_BSON ("{'gcp': { 'accessToken': 'foobar' } }")),
                 ctx);
   }

   /* Assert first CTX_NEED_KMS state requests encryption. */
   {
      kms = mongocrypt_ctx_next_kms_ctx (ctx);
      ASSERT_OR_PRINT_MSG (kms, "expected KMS context, got NULL");

      const char *endpoint;
      mongocrypt_kms_ctx_endpoint (kms, &endpoint);
      ASSERT_STREQUAL ("cloudkms.googleapis.com:443", endpoint);

      /* Satisfy request. */
      ASSERT_OK (
         mongocrypt_kms_ctx_feed (
            kms, TEST_FILE ("./test/data/gcp-auth/encrypt-response.txt")),
         kms);
      ASSERT_CMPINT ((int) mongocrypt_kms_ctx_bytes_needed (kms), ==, 0);

      kms = mongocrypt_ctx_next_kms_ctx (ctx);
      ASSERT_OR_PRINT_MSG (NULL == kms,
                           "expected NULL KMS context, got non-NULL");
      ASSERT_OK (mongocrypt_ctx_kms_done (ctx), ctx);
   }

   ASSERT_STATE_EQUAL (mongocrypt_ctx_state (ctx), MONGOCRYPT_CTX_READY);

   mongocrypt_ctx_destroy (ctx);
   mongocrypt_destroy (crypt);
}

static void
_test_encrypt_with_accesstoken (_mongocrypt_tester_t *tester)
{
   mongocrypt_t *crypt;
   mongocrypt_ctx_t *ctx;
   mongocrypt_kms_ctx_t *kms;
   mongocrypt_binary_t *uuid;
   const char *uuid_data =
      "\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61";

   crypt = mongocrypt_new ();
   ASSERT_OK (
      mongocrypt_setopt_kms_providers (crypt, TEST_BSON ("{'gcp': {}}")),
      crypt);
   mongocrypt_setopt_use_need_kms_credentials_state (crypt);
   ASSERT_OK (mongocrypt_init (crypt), crypt);
   ctx = mongocrypt_ctx_new (crypt);
   uuid = mongocrypt_binary_new_from_data ((uint8_t *) uuid_data, UUID_LEN);
   ASSERT_OK (mongocrypt_ctx_setopt_key_id (ctx, uuid), ctx);
   ASSERT_OK (mongocrypt_ctx_setopt_algorithm (
                 ctx, MONGOCRYPT_ALGORITHM_DETERMINISTIC_STR, -1),
              ctx);
   ASSERT_OK (
      mongocrypt_ctx_explicit_encrypt_init (ctx, TEST_BSON ("{'v': 1}")), ctx);

   ASSERT_STATE_EQUAL (mongocrypt_ctx_state (ctx),
                       MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS);
   {
      ASSERT_OK (mongocrypt_ctx_provide_kms_providers (
                    ctx, TEST_BSON ("{'gcp': { 'accessToken': 'foobar' } }")),
                 ctx);
   }

   ASSERT_STATE_EQUAL (mongocrypt_ctx_state (ctx),
                       MONGOCRYPT_CTX_NEED_MONGO_KEYS);
   {
      ASSERT_OK (mongocrypt_ctx_mongo_feed (
                    ctx, TEST_FILE ("./test/data/key-document-gcp.json")),
                 ctx);
      ASSERT_OK (mongocrypt_ctx_mongo_done (ctx), ctx);
   }

   ASSERT_STATE_EQUAL (mongocrypt_ctx_state (ctx), MONGOCRYPT_CTX_NEED_KMS);

   /* Assert first CTX_NEED_KMS state requests decryption. */
   {
      kms = mongocrypt_ctx_next_kms_ctx (ctx);
      ASSERT_OR_PRINT_MSG (kms, "expected KMS context, got NULL");

      const char *endpoint;
      mongocrypt_kms_ctx_endpoint (kms, &endpoint);
      ASSERT_STREQUAL ("cloudkms.googleapis.com:443", endpoint);

      /* Satisfy request. */
      ASSERT_OK (
         mongocrypt_kms_ctx_feed (
            kms, TEST_FILE ("./test/data/gcp-auth/decrypt-response.txt")),
         kms);
      ASSERT_CMPINT ((int) mongocrypt_kms_ctx_bytes_needed (kms), ==, 0);

      kms = mongocrypt_ctx_next_kms_ctx (ctx);
      ASSERT_OR_PRINT_MSG (NULL == kms,
                           "expected NULL KMS context, got non-NULL");
      ASSERT_OK (mongocrypt_ctx_kms_done (ctx), ctx);
   }

   ASSERT_STATE_EQUAL (mongocrypt_ctx_state (ctx), MONGOCRYPT_CTX_READY);

   mongocrypt_binary_destroy (uuid);
   mongocrypt_ctx_destroy (ctx);
   mongocrypt_destroy (crypt);
}

void
_mongocrypt_tester_install_gcp_auth (_mongocrypt_tester_t *tester)
{
   INSTALL_TEST (_test_createdatakey_with_credentials);
   INSTALL_TEST (_test_encrypt_with_credentials);
   INSTALL_TEST (_test_createdatakey_with_accesstoken);
   INSTALL_TEST (_test_encrypt_with_accesstoken);
}
