Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions google/cloud/credentials.cc
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@ std::shared_ptr<Credentials> MakeComputeEngineCredentials(Options opts) {
std::move(opts));
}

namespace experimental {

std::shared_ptr<Credentials> MakeUserAccountCredentials(std::string json_object,
Options opts) {
return std::make_shared<internal::AuthorizedUserConfig>(
std::move(json_object), std::move(opts));
}

} // namespace experimental

GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
} // namespace cloud
} // namespace google
13 changes: 13 additions & 0 deletions google/cloud/credentials.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
namespace google {
namespace cloud {
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
class Credentials;
namespace internal {
class CredentialsVisitor;
} // namespace internal
Expand Down Expand Up @@ -65,6 +66,18 @@ struct CAInMemoryOption {
using Type = std::vector<absl::string_view>;
};

/**
* Creates user account credentials from a user account JSON key.
*
* User account credentials contain a refresh token, client ID, and client
* secret, typically obtained via OAuth 2.0 authorization flow.
*
* @param json_object the user account configuration as a JSON string.
* @param opts optional configuration values.
*/
std::shared_ptr<Credentials> MakeUserAccountCredentials(std::string json_object,
Options opts = {});

} // namespace experimental

/**
Expand Down
5 changes: 5 additions & 0 deletions google/cloud/internal/credentials_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ ApiKeyConfig::ApiKeyConfig(std::string api_key, Options opts)
ComputeEngineCredentialsConfig::ComputeEngineCredentialsConfig(Options opts)
: options_(PopulateAuthOptions(std::move(opts))) {}

AuthorizedUserConfig::AuthorizedUserConfig(std::string json_object,
Options opts)
: json_object_(std::move(json_object)),
options_(PopulateAuthOptions(std::move(opts))) {}

} // namespace internal
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
} // namespace cloud
Expand Down
17 changes: 17 additions & 0 deletions google/cloud/internal/credentials_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class ServiceAccountConfig;
class ExternalAccountConfig;
class ApiKeyConfig;
class ComputeEngineCredentialsConfig;
class AuthorizedUserConfig;

std::shared_ptr<Credentials> MakeErrorCredentials(Status error_status);

Expand All @@ -56,6 +57,7 @@ class CredentialsVisitor {
virtual void visit(ExternalAccountConfig const&) = 0;
virtual void visit(ApiKeyConfig const&) = 0;
virtual void visit(ComputeEngineCredentialsConfig const&) = 0;
virtual void visit(AuthorizedUserConfig const&) = 0;

static void dispatch(Credentials const& credentials,
CredentialsVisitor& visitor);
Expand Down Expand Up @@ -206,6 +208,21 @@ class ComputeEngineCredentialsConfig : public Credentials {
Options options_;
};

class AuthorizedUserConfig : public Credentials {
public:
AuthorizedUserConfig(std::string json_object, Options opts);
~AuthorizedUserConfig() override = default;

std::string const& json_object() const { return json_object_; }
Options const& options() const { return options_; }

private:
void dispatch(CredentialsVisitor& v) const override { v.visit(*this); }

std::string json_object_;
Options options_;
};

/// A helper function to initialize Auth options.
Options PopulateAuthOptions(Options options);

Expand Down
11 changes: 11 additions & 0 deletions google/cloud/internal/credentials_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,17 @@ TEST(Credentials, ApiKeyCredentials) {
EXPECT_EQ("api-key", visitor.api_key);
}

TEST(Credentials, AuthorizedUserCredentials) {
auto credentials = experimental::MakeUserAccountCredentials(
"test-only-invalid", Options{}.set<ScopesOption>({"scope1", "scope2"}));
TestCredentialsVisitor visitor;
CredentialsVisitor::dispatch(*credentials, visitor);
ASSERT_EQ("AuthorizedUserConfig", visitor.name);
EXPECT_EQ("test-only-invalid", visitor.json_object);
EXPECT_THAT(visitor.options.get<ScopesOption>(),
ElementsAre("scope1", "scope2"));
}

TEST(PopulateAuthOptions, EmptyOptions) {
auto result_options = PopulateAuthOptions(Options{});

Expand Down
8 changes: 8 additions & 0 deletions google/cloud/internal/unified_grpc_credentials.cc
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,14 @@ std::shared_ptr<GrpcAuthenticationStrategy> CreateAuthenticationStrategy(
void visit(ComputeEngineCredentialsConfig const&) override {
result = std::make_unique<GrpcComputeEngineAuthentication>(options);
}
void visit(AuthorizedUserConfig const&) override {
result = std::make_unique<GrpcErrorCredentialsAuthentication>(
ErrorCredentialsConfig{UnimplementedError(
"User account credentials (MakeUserAccountCredentials) are not "
"supported with gRPC transport. Use Google Default Credentials "
"or Access Token Credentials instead.",
GCP_ERROR_INFO())});
}

} visitor(std::move(cq), std::move(options));

Expand Down
12 changes: 12 additions & 0 deletions google/cloud/internal/unified_grpc_credentials_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,18 @@ TEST(UnifiedGrpcCredentialsTest, WithApiKeyCredentials) {
EXPECT_THAT(headers, Contains(Pair("x-goog-api-key", "api-key")));
}

TEST(UnifiedGrpcCredentialsTest, WithAuthorizedUserCredentials) {
CompletionQueue cq;
auto creds = AuthorizedUserConfig("{}", Options{});
auto auth = CreateAuthenticationStrategy(creds, cq);
ASSERT_THAT(auth, NotNull());
EXPECT_TRUE(auth->RequiresConfigureContext());

grpc::ClientContext context;
auto configured_context = auth->ConfigureContext(context);
EXPECT_THAT(configured_context, StatusIs(StatusCode::kUnimplemented));
}

TEST(UnifiedGrpcCredentialsTest, LoadCAInfoNotSet) {
auto contents = LoadCAInfo(Options{});
EXPECT_FALSE(contents.has_value());
Expand Down
15 changes: 15 additions & 0 deletions google/cloud/internal/unified_rest_credentials.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#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_authorized_user_credentials.h"
#include "google/cloud/internal/oauth2_compute_engine_credentials.h"
#include "google/cloud/internal/oauth2_decorate_credentials.h"
#include "google/cloud/internal/oauth2_error_credentials.h"
Expand All @@ -35,6 +36,7 @@ namespace {

using ::google::cloud::internal::AccessTokenConfig;
using ::google::cloud::internal::ApiKeyConfig;
using ::google::cloud::internal::AuthorizedUserConfig;
using ::google::cloud::internal::ComputeEngineCredentialsConfig;
using ::google::cloud::internal::CredentialsVisitor;
using ::google::cloud::internal::ErrorCredentialsConfig;
Expand Down Expand Up @@ -153,6 +155,19 @@ std::shared_ptr<oauth2_internal::Credentials> MapCredentials(
Decorate(std::move(creds), std::move(client_factory_), cfg.options());
}

void visit(AuthorizedUserConfig const& cfg) override {
auto info = oauth2_internal::ParseAuthorizedUserCredentials(
cfg.json_object(), "MakeUserAccountCredentials");
if (!info) {
result = MakeErrorCredentials(std::move(info).status());
return;
}
auto creds = std::make_shared<oauth2_internal::AuthorizedUserCredentials>(
*info, cfg.options(), client_factory_);
result = Decorate(std::move(creds), std::move(client_factory_),
cfg.options());
}

private:
oauth2_internal::HttpClientFactory client_factory_;
};
Expand Down
38 changes: 38 additions & 0 deletions google/cloud/internal/unified_rest_credentials_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,44 @@ TEST(UnifiedRestCredentialsTest, ServiceAccount) {
EXPECT_EQ(access_token->token, *jwt);
}

TEST(UnifiedRestCredentialsTest, AuthorizedUser) {
auto const token_uri = std::string{"https://user-refresh.example.com"};
auto const contents = nlohmann::json{
{"client_id", "a-client-id.example.com"},
{"client_secret", "a-123456ABCDEF"},
{"refresh_token", "1/THETOKEN"},
{"type", "authorized_user"},
{"token_uri", token_uri},
};

auto const now = std::chrono::system_clock::now();

MockClientFactory client_factory;
EXPECT_CALL(client_factory, Call).WillOnce([token_uri]() {
auto client = std::make_unique<MockRestClient>();
using FormDataType = std::vector<std::pair<std::string, std::string>>;
auto expected_request = Property(&RestRequest::path, token_uri);
auto expected_form_data = MatcherCast<FormDataType const&>(IsSupersetOf({
Pair("grant_type", "refresh_token"),
Pair("client_id", "a-client-id.example.com"),
Pair("client_secret", "a-123456ABCDEF"),
Pair("refresh_token", "1/THETOKEN"),
}));
EXPECT_CALL(*client, Post(_, expected_request, expected_form_data))
.WillOnce(Return(
Status{StatusCode::kPermissionDenied, "uh-oh - user refresh"}));
return client;
});

auto const config =
internal::AuthorizedUserConfig(contents.dump(), Options{});
auto credentials = MapCredentials(config, client_factory.AsStdFunction());

auto access_token = credentials->GetToken(now);
EXPECT_THAT(access_token,
StatusIs(StatusCode::kPermissionDenied, "uh-oh - user refresh"));
}

TEST(UnifiedRestCredentialsTest, ExternalAccount) {
// This sets up a mocked request for the subject token.
auto const subject_url = std::string{"https://test-only-oidc.example.com/"};
Expand Down
5 changes: 5 additions & 0 deletions google/cloud/testing_util/credentials.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ struct TestCredentialsVisitor : public internal::CredentialsVisitor {
name = "ComputeEngineCredentialsConfig";
options = cfg.options();
}
void visit(internal::AuthorizedUserConfig const& cfg) override {
name = "AuthorizedUserConfig";
json_object = cfg.json_object();
options = cfg.options();
}
};

} // namespace testing_util
Expand Down
Loading