//
// Copyright 2020 gRPC authors.
//
// 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 <grpc/support/port_platform.h>

#include "src/core/lib/security/credentials/external/file_external_account_credentials.h"

#include <map>
#include <utility>

#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"

#include <grpc/slice.h>

#include "src/core/lib/iomgr/load_file.h"
#include "src/core/lib/json/json.h"
#include "src/core/lib/slice/slice.h"
#include "src/core/lib/slice/slice_internal.h"

namespace grpc_core {

RefCountedPtr<FileExternalAccountCredentials>
FileExternalAccountCredentials::Create(Options options,
                                       std::vector<std::string> scopes,
                                       grpc_error_handle* error) {
  auto creds = MakeRefCounted<FileExternalAccountCredentials>(
      std::move(options), std::move(scopes), error);
  if (error->ok()) {
    return creds;
  } else {
    return nullptr;
  }
}

FileExternalAccountCredentials::FileExternalAccountCredentials(
    Options options, std::vector<std::string> scopes, grpc_error_handle* error)
    : ExternalAccountCredentials(options, std::move(scopes)) {
  auto it = options.credential_source.object_value().find("file");
  if (it == options.credential_source.object_value().end()) {
    *error = GRPC_ERROR_CREATE("file field not present.");
    return;
  }
  if (it->second.type() != Json::Type::STRING) {
    *error = GRPC_ERROR_CREATE("file field must be a string.");
    return;
  }
  file_ = it->second.string_value();
  it = options.credential_source.object_value().find("format");
  if (it != options.credential_source.object_value().end()) {
    const Json& format_json = it->second;
    if (format_json.type() != Json::Type::OBJECT) {
      *error = GRPC_ERROR_CREATE(
          "The JSON value of credential source format is not an object.");
      return;
    }
    auto format_it = format_json.object_value().find("type");
    if (format_it == format_json.object_value().end()) {
      *error = GRPC_ERROR_CREATE("format.type field not present.");
      return;
    }
    if (format_it->second.type() != Json::Type::STRING) {
      *error = GRPC_ERROR_CREATE("format.type field must be a string.");
      return;
    }
    format_type_ = format_it->second.string_value();
    if (format_type_ == "json") {
      format_it = format_json.object_value().find("subject_token_field_name");
      if (format_it == format_json.object_value().end()) {
        *error = GRPC_ERROR_CREATE(
            "format.subject_token_field_name field must be present if the "
            "format is in Json.");
        return;
      }
      if (format_it->second.type() != Json::Type::STRING) {
        *error = GRPC_ERROR_CREATE(
            "format.subject_token_field_name field must be a string.");
        return;
      }
      format_subject_token_field_name_ = format_it->second.string_value();
    }
  }
}

void FileExternalAccountCredentials::RetrieveSubjectToken(
    HTTPRequestContext* /*ctx*/, const Options& /*options*/,
    std::function<void(std::string, grpc_error_handle)> cb) {
  struct SliceWrapper {
    ~SliceWrapper() { CSliceUnref(slice); }
    grpc_slice slice = grpc_empty_slice();
  };
  SliceWrapper content_slice;
  // To retrieve the subject token, we read the file every time we make a
  // request because it may have changed since the last request.
  grpc_error_handle error =
      grpc_load_file(file_.c_str(), 0, &content_slice.slice);
  if (!error.ok()) {
    cb("", error);
    return;
  }
  absl::string_view content = StringViewFromSlice(content_slice.slice);
  if (format_type_ == "json") {
    auto content_json = Json::Parse(content);
    if (!content_json.ok() || content_json->type() != Json::Type::OBJECT) {
      cb("", GRPC_ERROR_CREATE(
                 "The content of the file is not a valid json object."));
      return;
    }
    auto content_it =
        content_json->object_value().find(format_subject_token_field_name_);
    if (content_it == content_json->object_value().end()) {
      cb("", GRPC_ERROR_CREATE("Subject token field not present."));
      return;
    }
    if (content_it->second.type() != Json::Type::STRING) {
      cb("", GRPC_ERROR_CREATE("Subject token field must be a string."));
      return;
    }
    cb(content_it->second.string_value(), absl::OkStatus());
    return;
  }
  cb(std::string(content), absl::OkStatus());
}

}  // namespace grpc_core
