|
| 1 | +// Copyright 2022 Google LLC |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// https://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +// See the License for the specific language governing permissions and |
| 13 | +// limitations under the License. |
| 14 | + |
| 15 | +#include "google/cloud/internal/external_account_token_source_file.h" |
| 16 | +#include "google/cloud/internal/absl_str_cat_quiet.h" |
| 17 | +#include "google/cloud/internal/external_account_parsing.h" |
| 18 | +#include "google/cloud/internal/make_status.h" |
| 19 | +#include <fstream> |
| 20 | + |
| 21 | +namespace google { |
| 22 | +namespace cloud { |
| 23 | +namespace oauth2_internal { |
| 24 | +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN |
| 25 | +namespace { |
| 26 | + |
| 27 | +Status BadFile(internal::ErrorContext const& ec) { |
| 28 | + return InvalidArgumentError("error reading subject token file", |
| 29 | + GCP_ERROR_INFO().WithContext(ec)); |
| 30 | +} |
| 31 | + |
| 32 | +StatusOr<internal::SubjectToken> TextFileReader( |
| 33 | + std::string const& filename, internal::ErrorContext const& ec) { |
| 34 | + std::ifstream is(filename); |
| 35 | + auto contents = std::string{std::istreambuf_iterator<char>(is.rdbuf()), {}}; |
| 36 | + if (!is.is_open() || is.bad()) return BadFile(ec); |
| 37 | + return internal::SubjectToken{std::move(contents)}; |
| 38 | +} |
| 39 | + |
| 40 | +StatusOr<internal::SubjectToken> JsonFileReader( |
| 41 | + std::string const& filename, std::string const& field_name, |
| 42 | + internal::ErrorContext const& ec) { |
| 43 | + std::ifstream is(filename); |
| 44 | + auto contents = std::string{std::istreambuf_iterator<char>(is.rdbuf()), {}}; |
| 45 | + if (!is.is_open() || is.bad()) return BadFile(ec); |
| 46 | + auto json = nlohmann::json::parse(contents, nullptr, false); |
| 47 | + auto error_details = [&](std::string const& msg) { |
| 48 | + return msg + " in JSON object loaded from `" + filename + |
| 49 | + "`, with subject_token_field `" + field_name + "`"; |
| 50 | + }; |
| 51 | + if (!json.is_object()) { |
| 52 | + return InvalidArgumentError(error_details("parse error"), |
| 53 | + GCP_ERROR_INFO().WithContext(ec)); |
| 54 | + } |
| 55 | + auto it = json.find(field_name); |
| 56 | + if (it == json.end()) { |
| 57 | + return InvalidArgumentError(error_details("subject token field not found"), |
| 58 | + GCP_ERROR_INFO().WithContext(ec)); |
| 59 | + } |
| 60 | + if (!it->is_string()) { |
| 61 | + return InvalidArgumentError(error_details("invalid type for token field"), |
| 62 | + GCP_ERROR_INFO().WithContext(ec)); |
| 63 | + } |
| 64 | + return internal::SubjectToken{it->get<std::string>()}; |
| 65 | +} |
| 66 | + |
| 67 | +struct Format { |
| 68 | + std::string type; |
| 69 | + std::string subject_token_field_name; |
| 70 | +}; |
| 71 | + |
| 72 | +StatusOr<Format> ParseFormat(nlohmann::json const& credentials_source, |
| 73 | + internal::ErrorContext const& ec) { |
| 74 | + auto it = credentials_source.find("format"); |
| 75 | + if (it == credentials_source.end()) return Format{"text", {}}; |
| 76 | + if (!it->is_object()) { |
| 77 | + return InvalidArgumentError( |
| 78 | + "invalid type for `format` field in `credentials_source`", |
| 79 | + GCP_ERROR_INFO().WithContext(ec)); |
| 80 | + } |
| 81 | + auto const& format = *it; |
| 82 | + auto type = ValidateStringField(format, "type", "credentials_source.format", |
| 83 | + "text", ec); |
| 84 | + if (!type) return std::move(type).status(); |
| 85 | + if (*type == "text") return Format{"text", {}}; |
| 86 | + if (*type != "json") { |
| 87 | + return InvalidArgumentError( |
| 88 | + absl::StrCat("invalid file type <", *type, "> in `credentials_source`"), |
| 89 | + GCP_ERROR_INFO().WithContext(ec)); |
| 90 | + } |
| 91 | + auto field = ValidateStringField(format, "subject_token_field_name", |
| 92 | + "credentials_source.format", ec); |
| 93 | + if (!field) return std::move(field).status(); |
| 94 | + return Format{*std::move(type), *std::move(field)}; |
| 95 | +} |
| 96 | + |
| 97 | +} // namespace |
| 98 | + |
| 99 | +StatusOr<ExternalAccountTokenSource> MakeExternalAccountTokenSourceFile( |
| 100 | + nlohmann::json const& credentials_source, |
| 101 | + internal::ErrorContext const& ec) { |
| 102 | + auto file = |
| 103 | + ValidateStringField(credentials_source, "file", "credentials_source", ec); |
| 104 | + if (!file) return std::move(file).status(); |
| 105 | + |
| 106 | + // Make a copy. Most of the time this function should succeed, and we need the |
| 107 | + // copy for the lambda captures. |
| 108 | + auto context = ec; |
| 109 | + context.emplace_back("credentials_source.type", "file"); |
| 110 | + context.emplace_back("credentials_source.file.filename", *file); |
| 111 | + auto format = ParseFormat(credentials_source, context); |
| 112 | + if (!format) return std::move(format).status(); |
| 113 | + if (format->type == "text") { |
| 114 | + context.emplace_back("credentials_source.file.type", "text"); |
| 115 | + return ExternalAccountTokenSource{ |
| 116 | + [f = *std::move(file), ec = std::move(context)](Options const&) { |
| 117 | + return TextFileReader(f, ec); |
| 118 | + }}; |
| 119 | + } |
| 120 | + context.emplace_back("credentials_source.file.type", "json"); |
| 121 | + context.emplace_back("credentials_source.file.source_token_field_name", |
| 122 | + format->subject_token_field_name); |
| 123 | + return ExternalAccountTokenSource{[f = *std::move(file), |
| 124 | + field = format->subject_token_field_name, |
| 125 | + ec = std::move(context)](Options const&) { |
| 126 | + return JsonFileReader(f, field, ec); |
| 127 | + }}; |
| 128 | +} |
| 129 | + |
| 130 | +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END |
| 131 | +} // namespace oauth2_internal |
| 132 | +} // namespace cloud |
| 133 | +} // namespace google |
0 commit comments