From 8abc17086c7eb3351a11be54b419a6e6c0232b2b Mon Sep 17 00:00:00 2001 From: Darren Bolduc Date: Mon, 14 Oct 2024 13:56:06 -0400 Subject: [PATCH] impl: API key auth over REST (#14785) --- .../cloud/google_cloud_cpp_rest_internal.bzl | 2 + .../google_cloud_cpp_rest_internal.cmake | 3 ++ ...gle_cloud_cpp_rest_internal_unit_tests.bzl | 1 + .../internal/oauth2_api_key_credentials.cc | 38 ++++++++++++++ .../internal/oauth2_api_key_credentials.h | 52 +++++++++++++++++++ .../oauth2_api_key_credentials_test.cc | 49 +++++++++++++++++ .../internal/unified_rest_credentials.cc | 7 +-- .../internal/unified_rest_credentials_test.cc | 15 ++++++ 8 files changed, 164 insertions(+), 3 deletions(-) create mode 100644 google/cloud/internal/oauth2_api_key_credentials.cc create mode 100644 google/cloud/internal/oauth2_api_key_credentials.h create mode 100644 google/cloud/internal/oauth2_api_key_credentials_test.cc diff --git a/google/cloud/google_cloud_cpp_rest_internal.bzl b/google/cloud/google_cloud_cpp_rest_internal.bzl index b0125de362b5b..da7986ed59df1 100644 --- a/google/cloud/google_cloud_cpp_rest_internal.bzl +++ b/google/cloud/google_cloud_cpp_rest_internal.bzl @@ -36,6 +36,7 @@ google_cloud_cpp_rest_internal_hdrs = [ "internal/make_jwt_assertion.h", "internal/oauth2_access_token_credentials.h", "internal/oauth2_anonymous_credentials.h", + "internal/oauth2_api_key_credentials.h", "internal/oauth2_authorized_user_credentials.h", "internal/oauth2_cached_credentials.h", "internal/oauth2_compute_engine_credentials.h", @@ -94,6 +95,7 @@ google_cloud_cpp_rest_internal_srcs = [ "internal/make_jwt_assertion.cc", "internal/oauth2_access_token_credentials.cc", "internal/oauth2_anonymous_credentials.cc", + "internal/oauth2_api_key_credentials.cc", "internal/oauth2_authorized_user_credentials.cc", "internal/oauth2_cached_credentials.cc", "internal/oauth2_compute_engine_credentials.cc", diff --git a/google/cloud/google_cloud_cpp_rest_internal.cmake b/google/cloud/google_cloud_cpp_rest_internal.cmake index 86646f6e5a53e..40c1ca61ebea2 100644 --- a/google/cloud/google_cloud_cpp_rest_internal.cmake +++ b/google/cloud/google_cloud_cpp_rest_internal.cmake @@ -59,6 +59,8 @@ add_library( internal/oauth2_access_token_credentials.h internal/oauth2_anonymous_credentials.cc internal/oauth2_anonymous_credentials.h + internal/oauth2_api_key_credentials.cc + internal/oauth2_api_key_credentials.h internal/oauth2_authorized_user_credentials.cc internal/oauth2_authorized_user_credentials.h internal/oauth2_cached_credentials.cc @@ -257,6 +259,7 @@ if (BUILD_TESTING) internal/make_jwt_assertion_test.cc internal/oauth2_access_token_credentials_test.cc internal/oauth2_anonymous_credentials_test.cc + internal/oauth2_api_key_credentials_test.cc internal/oauth2_authorized_user_credentials_test.cc internal/oauth2_cached_credentials_test.cc internal/oauth2_compute_engine_credentials_test.cc diff --git a/google/cloud/google_cloud_cpp_rest_internal_unit_tests.bzl b/google/cloud/google_cloud_cpp_rest_internal_unit_tests.bzl index 5a45c77bc93fe..13d6b0f9e5234 100644 --- a/google/cloud/google_cloud_cpp_rest_internal_unit_tests.bzl +++ b/google/cloud/google_cloud_cpp_rest_internal_unit_tests.bzl @@ -38,6 +38,7 @@ google_cloud_cpp_rest_internal_unit_tests = [ "internal/make_jwt_assertion_test.cc", "internal/oauth2_access_token_credentials_test.cc", "internal/oauth2_anonymous_credentials_test.cc", + "internal/oauth2_api_key_credentials_test.cc", "internal/oauth2_authorized_user_credentials_test.cc", "internal/oauth2_cached_credentials_test.cc", "internal/oauth2_compute_engine_credentials_test.cc", diff --git a/google/cloud/internal/oauth2_api_key_credentials.cc b/google/cloud/internal/oauth2_api_key_credentials.cc new file mode 100644 index 0000000000000..245a9a33ed08d --- /dev/null +++ b/google/cloud/internal/oauth2_api_key_credentials.cc @@ -0,0 +1,38 @@ +// Copyright 2024 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 +// +// https://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 "google/cloud/internal/oauth2_api_key_credentials.h" + +namespace google { +namespace cloud { +namespace oauth2_internal { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN + +ApiKeyCredentials::ApiKeyCredentials(std::string api_key) + : api_key_(std::move(api_key)) {} + +StatusOr ApiKeyCredentials::GetToken( + std::chrono::system_clock::time_point tp) { + return AccessToken{std::string{}, tp}; +} + +StatusOr> +ApiKeyCredentials::AuthenticationHeader(std::chrono::system_clock::time_point) { + return std::make_pair(std::string{"x-goog-api-key"}, api_key_); +} + +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace oauth2_internal +} // namespace cloud +} // namespace google diff --git a/google/cloud/internal/oauth2_api_key_credentials.h b/google/cloud/internal/oauth2_api_key_credentials.h new file mode 100644 index 0000000000000..aec219a29cba0 --- /dev/null +++ b/google/cloud/internal/oauth2_api_key_credentials.h @@ -0,0 +1,52 @@ +// Copyright 2024 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 +// +// https://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. + +#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_OAUTH2_API_KEY_CREDENTIALS_H +#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_OAUTH2_API_KEY_CREDENTIALS_H + +#include "google/cloud/internal/credentials_impl.h" +#include "google/cloud/internal/oauth2_credentials.h" +#include "google/cloud/status_or.h" +#include "google/cloud/version.h" +#include +#include + +namespace google { +namespace cloud { +namespace oauth2_internal { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN + +/** + * A `Credentials` type representing a Bearer Token OAuth 2.0 credentials. + */ +class ApiKeyCredentials : public oauth2_internal::Credentials { + public: + explicit ApiKeyCredentials(std::string api_key); + + StatusOr GetToken( + std::chrono::system_clock::time_point tp) override; + + StatusOr> AuthenticationHeader( + std::chrono::system_clock::time_point) override; + + private: + std::string api_key_; +}; + +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace oauth2_internal +} // namespace cloud +} // namespace google + +#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_OAUTH2_API_KEY_CREDENTIALS_H diff --git a/google/cloud/internal/oauth2_api_key_credentials_test.cc b/google/cloud/internal/oauth2_api_key_credentials_test.cc new file mode 100644 index 0000000000000..12f51a2257b50 --- /dev/null +++ b/google/cloud/internal/oauth2_api_key_credentials_test.cc @@ -0,0 +1,49 @@ +// Copyright 2024 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 +// +// https://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 "google/cloud/internal/oauth2_api_key_credentials.h" +#include "google/cloud/testing_util/status_matchers.h" +#include +#include + +namespace google { +namespace cloud { +namespace oauth2_internal { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN +namespace { + +using ::google::cloud::testing_util::IsOkAndHolds; +using ::testing::IsEmpty; +using ::testing::Pair; + +TEST(ApiKeyCredentials, EmptyToken) { + ApiKeyCredentials creds("api-key"); + auto const now = std::chrono::system_clock::now(); + auto const token = creds.GetToken(now); + ASSERT_STATUS_OK(token); + EXPECT_THAT(token->token, IsEmpty()); +} + +TEST(ApiKeyCredentials, SetsXGoogApiKeyHeader) { + ApiKeyCredentials creds("api-key"); + auto const now = std::chrono::system_clock::now(); + EXPECT_THAT(creds.AuthenticationHeader(now), + IsOkAndHolds(Pair("x-goog-api-key", "api-key"))); +} + +} // namespace +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace oauth2_internal +} // namespace cloud +} // namespace google diff --git a/google/cloud/internal/unified_rest_credentials.cc b/google/cloud/internal/unified_rest_credentials.cc index 02eab337d3c62..bcf2cd0aa29b4 100644 --- a/google/cloud/internal/unified_rest_credentials.cc +++ b/google/cloud/internal/unified_rest_credentials.cc @@ -17,6 +17,7 @@ #include "google/cloud/internal/make_jwt_assertion.h" #include "google/cloud/internal/oauth2_access_token_credentials.h" #include "google/cloud/internal/oauth2_anonymous_credentials.h" +#include "google/cloud/internal/oauth2_api_key_credentials.h" #include "google/cloud/internal/oauth2_decorate_credentials.h" #include "google/cloud/internal/oauth2_error_credentials.h" #include "google/cloud/internal/oauth2_external_account_credentials.h" @@ -137,9 +138,9 @@ std::shared_ptr MapCredentials( cfg.options()); } - void visit(ApiKeyConfig const&) override { - // TODO(#14759) - Support API key authentication over REST - result = std::make_shared(); + void visit(ApiKeyConfig const& cfg) override { + result = + std::make_shared(cfg.api_key()); } private: diff --git a/google/cloud/internal/unified_rest_credentials_test.cc b/google/cloud/internal/unified_rest_credentials_test.cc index ed78cd7a05287..77eaa7ab7c3a8 100644 --- a/google/cloud/internal/unified_rest_credentials_test.cc +++ b/google/cloud/internal/unified_rest_credentials_test.cc @@ -38,9 +38,11 @@ GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN namespace { using ::google::cloud::MakeAccessTokenCredentials; +using ::google::cloud::MakeApiKeyCredentials; using ::google::cloud::MakeGoogleDefaultCredentials; using ::google::cloud::MakeInsecureCredentials; using ::google::cloud::testing_util::IsOk; +using ::google::cloud::testing_util::IsOkAndHolds; using ::google::cloud::testing_util::MakeMockHttpPayloadSuccess; using ::google::cloud::testing_util::MockRestClient; using ::google::cloud::testing_util::MockRestResponse; @@ -56,6 +58,7 @@ using ::testing::HasSubstr; using ::testing::IsEmpty; using ::testing::IsSupersetOf; using ::testing::MatcherCast; +using ::testing::NotNull; using ::testing::Pair; using ::testing::Property; using ::testing::Return; @@ -459,6 +462,18 @@ TEST(UnifiedRestCredentialsTest, ExternalAccount) { StatusIs(StatusCode::kPermissionDenied, "uh-oh - STS exchange")); } +TEST(UnifiedRestCredentialsTest, ApiKey) { + auto creds = MakeApiKeyCredentials("api-key"); + ASSERT_THAT(creds, NotNull()); + + auto oauth2_creds = MapCredentials(*creds); + ASSERT_THAT(oauth2_creds, NotNull()); + + auto header = + oauth2_creds->AuthenticationHeader(std::chrono::system_clock::now()); + EXPECT_THAT(header, IsOkAndHolds(Pair("x-goog-api-key", "api-key"))); +} + TEST(UnifiedRestCredentialsTest, LoadError) { // Create a name for a non-existing file, try to load it, and verify it // returns errors.