Skip to content

Commit 3e9ad21

Browse files
committed
ensure feature and behavior parity in GCS
1 parent d6ec063 commit 3e9ad21

27 files changed

Lines changed: 313 additions & 278 deletions

ci/cloudbuild/builds/cmake-install.sh

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,6 @@ expected_dirs+=(
133133
./include/google/cloud/storage/internal/grpc
134134
./include/google/cloud/storage/internal/rest
135135
./include/google/cloud/storage/mocks
136-
./include/google/cloud/storage/oauth2
137136
./include/google/cloud/storage/testing
138137
# no gRPC services in google/cloud/workflows/type.
139138
./include/google/cloud/workflows/type

google/cloud/credentials.cc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,13 @@ std::shared_ptr<Credentials> MakeImpersonateServiceAccountCredentials(
4848
std::shared_ptr<Credentials> MakeServiceAccountCredentials(
4949
std::string json_object, Options opts) {
5050
return std::make_shared<internal::ServiceAccountConfig>(
51-
std::move(json_object), std::move(opts));
51+
std::move(json_object), absl::nullopt, std::move(opts));
5252
}
5353

5454
std::shared_ptr<Credentials> MakeServiceAccountCredentialsFromFile(
5555
std::string const& file_path, Options opts) {
56-
return std::make_shared<internal::ServiceAccountConfig>("", file_path,
57-
std::move(opts));
56+
return std::make_shared<internal::ServiceAccountConfig>(
57+
absl::nullopt, file_path, std::move(opts));
5858
}
5959

6060
std::shared_ptr<Credentials> MakeExternalAccountCredentials(

google/cloud/credentials.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,9 @@ std::shared_ptr<Credentials> MakeServiceAccountCredentials(
335335
* [service account key]:
336336
* https://cloud.google.com/iam/docs/creating-managing-service-account-keys#iam-service-account-keys-create-cpp
337337
*
338+
* Use `ScopesOption` to restrict the authentication scope for the obtained
339+
* credentials.
340+
*
338341
* @ingroup guac
339342
*
340343
* @note While JSON file formats are supported for both REST and gRPC transport,

google/cloud/internal/credentials_impl.cc

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,9 @@ std::vector<std::string> const& ImpersonateServiceAccountConfig::delegates()
8787
return options_.get<DelegatesOption>();
8888
}
8989

90-
ServiceAccountConfig::ServiceAccountConfig(std::string json_object,
91-
Options opts)
92-
: json_object_(std::move(json_object)),
93-
options_(PopulateAuthOptions(std::move(opts))) {}
94-
95-
ServiceAccountConfig::ServiceAccountConfig(std::string json_object,
96-
std::string file_path, Options opts)
90+
ServiceAccountConfig::ServiceAccountConfig(
91+
absl::optional<std::string> json_object,
92+
absl::optional<std::string> file_path, Options opts)
9793
: json_object_(std::move(json_object)),
9894
file_path_(std::move(file_path)),
9995
options_(PopulateAuthOptions(std::move(opts))) {}

google/cloud/internal/credentials_impl.h

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -144,19 +144,23 @@ class ImpersonateServiceAccountConfig : public Credentials {
144144

145145
class ServiceAccountConfig : public Credentials {
146146
public:
147-
ServiceAccountConfig(std::string json_object, Options opts);
148-
ServiceAccountConfig(std::string json_object, std::string file_path,
149-
Options opts);
150-
151-
std::string const& json_object() const { return json_object_; }
147+
// Only one of json_object or file_path should have a value.
148+
// TODO(#15886): Use the C++ type system to make better constructors that
149+
// enforces this comment.
150+
ServiceAccountConfig(absl::optional<std::string> json_object,
151+
absl::optional<std::string> file_path, Options opts);
152+
153+
absl::optional<std::string> const& json_object() const {
154+
return json_object_;
155+
}
152156
absl::optional<std::string> const& file_path() const { return file_path_; }
153157
Options const& options() const { return options_; }
154158

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

158-
std::string json_object_;
159-
absl::optional<std::string> file_path_ = absl::nullopt;
162+
absl::optional<std::string> json_object_;
163+
absl::optional<std::string> file_path_;
160164
Options options_;
161165
};
162166

google/cloud/internal/oauth2_service_account_credentials.cc

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,18 @@
1313
// limitations under the License.
1414

1515
#include "google/cloud/internal/oauth2_service_account_credentials.h"
16+
#include "google/cloud/credentials.h"
1617
#include "google/cloud/internal/absl_str_join_quiet.h"
1718
#include "google/cloud/internal/getenv.h"
1819
#include "google/cloud/internal/make_jwt_assertion.h"
1920
#include "google/cloud/internal/make_status.h"
2021
#include "google/cloud/internal/oauth2_google_credentials.h"
2122
#include "google/cloud/internal/oauth2_universe_domain.h"
23+
#include "google/cloud/internal/parse_service_account_p12_file.h"
2224
#include "google/cloud/internal/rest_response.h"
2325
#include "google/cloud/internal/sign_using_sha256.h"
2426
#include <nlohmann/json.hpp>
27+
#include <fstream>
2528
#include <functional>
2629

2730
namespace google {
@@ -240,6 +243,81 @@ StatusOr<std::string> MakeSelfSignedJWT(
240243
info.private_key);
241244
}
242245

246+
StatusOr<std::shared_ptr<Credentials>>
247+
CreateServiceAccountCredentialsFromJsonContents(
248+
std::string const& contents, Options const& options,
249+
HttpClientFactory client_factory) {
250+
auto info = ParseServiceAccountCredentials(contents, "memory");
251+
if (!info) return info.status();
252+
253+
std::set<std::string> scopes;
254+
if (options.has<ScopesOption>()) {
255+
auto s = options.get<ScopesOption>();
256+
scopes.insert(s.begin(), s.end());
257+
info->scopes = std::move(scopes);
258+
}
259+
260+
// Verify this is usable before returning it.
261+
auto const tp = std::chrono::system_clock::time_point{};
262+
auto const components = AssertionComponentsFromInfo(*info, tp);
263+
auto jwt = MakeJWTAssertionNoThrow(components.first, components.second,
264+
info->private_key);
265+
if (!jwt) return jwt.status();
266+
return StatusOr<std::shared_ptr<Credentials>>(
267+
std::make_shared<ServiceAccountCredentials>(*info, options,
268+
std::move(client_factory)));
269+
}
270+
271+
StatusOr<std::shared_ptr<Credentials>>
272+
CreateServiceAccountCredentialsFromJsonFilePath(
273+
std::string const& path, Options const& options,
274+
HttpClientFactory client_factory) {
275+
std::ifstream is(path);
276+
if (!is.is_open()) {
277+
// We use kUnknown here because we don't know if the file does not exist, or
278+
// if we were unable to open it for some other reason.
279+
return google::cloud::internal::UnknownError(
280+
"Cannot open credentials file " + path, GCP_ERROR_INFO());
281+
}
282+
std::string contents(std::istreambuf_iterator<char>{is}, {});
283+
return CreateServiceAccountCredentialsFromJsonContents(
284+
std::move(contents), options, std::move(client_factory));
285+
}
286+
287+
StatusOr<std::shared_ptr<Credentials>>
288+
CreateServiceAccountCredentialsFromP12FilePath(
289+
std::string const& path, Options const& options,
290+
HttpClientFactory client_factory) {
291+
auto info = ParseServiceAccountP12File(path);
292+
if (!info) {
293+
return std::move(info).status();
294+
}
295+
std::set<std::string> scopes;
296+
if (options.has<ScopesOption>()) {
297+
auto s = options.get<ScopesOption>();
298+
scopes.insert(s.begin(), s.end());
299+
info->scopes = std::move(scopes);
300+
}
301+
302+
// These are supplied as extra parameters to this method, not in the P12
303+
// file.
304+
info->scopes = std::move(scopes);
305+
return StatusOr<std::shared_ptr<Credentials>>(
306+
std::make_shared<ServiceAccountCredentials>(*info, options,
307+
std::move(client_factory)));
308+
}
309+
310+
StatusOr<std::shared_ptr<Credentials>>
311+
CreateServiceAccountCredentialsFromFilePath(std::string const& path,
312+
Options const& options,
313+
HttpClientFactory client_factory) {
314+
auto credentials = CreateServiceAccountCredentialsFromJsonFilePath(
315+
path, options, client_factory);
316+
if (credentials) return *credentials;
317+
return CreateServiceAccountCredentialsFromP12FilePath(
318+
path, options, std::move(client_factory));
319+
}
320+
243321
ServiceAccountCredentials::ServiceAccountCredentials(
244322
ServiceAccountCredentialsInfo info, Options options,
245323
HttpClientFactory client_factory)
@@ -313,7 +391,8 @@ bool ServiceAccountUseOAuth(ServiceAccountCredentialsInfo const& info) {
313391
}
314392

315393
bool ServiceAccountCredentials::UseOAuth() {
316-
return ServiceAccountUseOAuth(info_);
394+
return options_.has<DisableSelfSignedJWTOption>() ||
395+
ServiceAccountUseOAuth(info_);
317396
}
318397

319398
StatusOr<AccessToken> ServiceAccountCredentials::GetTokenOAuth(

google/cloud/internal/oauth2_service_account_credentials.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "absl/types/optional.h"
2525
#include <chrono>
2626
#include <string>
27+
#include <variant>
2728
#include <vector>
2829

2930
namespace google {
@@ -127,6 +128,30 @@ StatusOr<std::string> MakeSelfSignedJWT(
127128
ServiceAccountCredentialsInfo const& info,
128129
std::chrono::system_clock::time_point tp);
129130

131+
StatusOr<std::shared_ptr<Credentials>>
132+
CreateServiceAccountCredentialsFromJsonContents(
133+
std::string const& contents, Options const& options,
134+
HttpClientFactory client_factory);
135+
136+
StatusOr<std::shared_ptr<Credentials>>
137+
CreateServiceAccountCredentialsFromJsonFilePath(
138+
std::string const& path, Options const& options,
139+
HttpClientFactory client_factory);
140+
141+
StatusOr<std::shared_ptr<Credentials>>
142+
CreateServiceAccountCredentialsFromP12FilePath(
143+
std::string const& path, Options const& options,
144+
HttpClientFactory client_factory);
145+
146+
StatusOr<std::shared_ptr<Credentials>>
147+
CreateServiceAccountCredentialsFromFilePath(std::string const& path,
148+
Options const& options,
149+
HttpClientFactory client_factory);
150+
151+
struct DisableSelfSignedJWTOption {
152+
using Type = std::monostate;
153+
};
154+
130155
/**
131156
* Implements service account credentials for REST clients.
132157
*

google/cloud/internal/unified_grpc_credentials.cc

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,14 @@ std::shared_ptr<GrpcAuthenticationStrategy> CreateAuthenticationStrategy(
120120
std::string contents(std::istreambuf_iterator<char>{is}, {});
121121
result = std::make_unique<GrpcServiceAccountAuthentication>(
122122
std::move(contents), std::move(options));
123-
} else {
123+
} else if (cfg.json_object().has_value()) {
124124
result = std::make_unique<GrpcServiceAccountAuthentication>(
125-
cfg.json_object(), std::move(options));
125+
*cfg.json_object(), std::move(options));
126+
} else {
127+
result = std::make_unique<GrpcErrorCredentialsAuthentication>(
128+
ErrorCredentialsConfig{InternalError(
129+
"ServiceAccountConfig has neither json_object nor file_path",
130+
GCP_ERROR_INFO())});
126131
}
127132
}
128133
void visit(ExternalAccountConfig const& cfg) override {

google/cloud/internal/unified_rest_credentials.cc

Lines changed: 17 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "google/cloud/internal/unified_rest_credentials.h"
1616
#include "google/cloud/common_options.h"
1717
#include "google/cloud/internal/make_jwt_assertion.h"
18+
#include "google/cloud/internal/make_status.h"
1819
#include "google/cloud/internal/oauth2_access_token_credentials.h"
1920
#include "google/cloud/internal/oauth2_anonymous_credentials.h"
2021
#include "google/cloud/internal/oauth2_api_key_credentials.h"
@@ -25,8 +26,6 @@
2526
#include "google/cloud/internal/oauth2_google_credentials.h"
2627
#include "google/cloud/internal/oauth2_impersonate_service_account_credentials.h"
2728
#include "google/cloud/internal/oauth2_service_account_credentials.h"
28-
#include "google/cloud/internal/parse_service_account_p12_file.h"
29-
#include <fstream>
3029

3130
namespace google {
3231
namespace cloud {
@@ -51,67 +50,6 @@ std::shared_ptr<oauth2_internal::Credentials> MakeErrorCredentials(
5150
return std::make_shared<oauth2_internal::ErrorCredentials>(std::move(status));
5251
}
5352

54-
StatusOr<std::shared_ptr<oauth2_internal::Credentials>>
55-
CreateServiceAccountCredentialsFromJsonContents(
56-
std::string const& contents, Options const& options,
57-
oauth2_internal::HttpClientFactory client_factory) {
58-
auto info =
59-
oauth2_internal::ParseServiceAccountCredentials(contents, "memory");
60-
if (!info) return info.status();
61-
// Verify this is usable before returning it.
62-
auto const tp = std::chrono::system_clock::time_point{};
63-
auto const components = AssertionComponentsFromInfo(*info, tp);
64-
auto jwt = internal::MakeJWTAssertionNoThrow(
65-
components.first, components.second, info->private_key);
66-
if (!jwt) return jwt.status();
67-
return StatusOr<std::shared_ptr<oauth2_internal::Credentials>>(
68-
std::make_shared<oauth2_internal::ServiceAccountCredentials>(
69-
*info, options, std::move(client_factory)));
70-
}
71-
72-
StatusOr<std::shared_ptr<oauth2_internal::Credentials>>
73-
CreateServiceAccountCredentialsFromJsonFilePath(
74-
std::string const& path, absl::optional<std::set<std::string>>,
75-
absl::optional<std::string>, Options const& options,
76-
oauth2_internal::HttpClientFactory client_factory) {
77-
std::ifstream is(path);
78-
std::string contents(std::istreambuf_iterator<char>{is}, {});
79-
return CreateServiceAccountCredentialsFromJsonContents(
80-
std::move(contents), options, std::move(client_factory));
81-
}
82-
83-
std::shared_ptr<oauth2_internal::Credentials>
84-
CreateServiceAccountCredentialsFromP12FilePath(
85-
std::string const& path, absl::optional<std::set<std::string>> scopes,
86-
absl::optional<std::string> subject, Options const& options,
87-
oauth2_internal::HttpClientFactory client_factory) {
88-
auto info = oauth2_internal::ParseServiceAccountP12File(path);
89-
if (!info) {
90-
return MakeErrorCredentials(std::move(info).status());
91-
}
92-
// These are supplied as extra parameters to this method, not in the P12
93-
// file.
94-
info->subject = std::move(subject);
95-
info->scopes = std::move(scopes);
96-
return std::make_shared<oauth2_internal::ServiceAccountCredentials>(
97-
*info, options, std::move(client_factory));
98-
}
99-
100-
std::shared_ptr<oauth2_internal::Credentials>
101-
CreateServiceAccountCredentialsFromFilePath(
102-
std::string const& path, absl::optional<std::set<std::string>> scopes,
103-
absl::optional<std::string> subject, Options const& options,
104-
oauth2_internal::HttpClientFactory client_factory) {
105-
auto credentials = CreateServiceAccountCredentialsFromJsonFilePath(
106-
path, scopes, subject, options, client_factory);
107-
if (credentials) {
108-
return *credentials;
109-
}
110-
return CreateServiceAccountCredentialsFromP12FilePath(
111-
path, std::move(scopes), std::move(subject), options,
112-
std::move(client_factory));
113-
}
114-
11553
} // namespace
11654

11755
std::shared_ptr<oauth2_internal::Credentials> MapCredentials(
@@ -165,18 +103,27 @@ std::shared_ptr<oauth2_internal::Credentials> MapCredentials(
165103

166104
void visit(ServiceAccountConfig const& cfg) override {
167105
if (cfg.file_path().has_value()) {
168-
result = Decorate(CreateServiceAccountCredentialsFromFilePath(
169-
*cfg.file_path(), {}, {}, cfg.options(),
170-
std::move(client_factory_)),
171-
cfg.options());
172-
} else {
173-
auto creds = CreateServiceAccountCredentialsFromJsonContents(
174-
cfg.json_object(), cfg.options(), std::move(client_factory_));
106+
auto creds =
107+
oauth2_internal::CreateServiceAccountCredentialsFromFilePath(
108+
*cfg.file_path(), cfg.options(), std::move(client_factory_));
109+
if (creds) {
110+
result = Decorate(*creds, cfg.options());
111+
} else {
112+
result = MakeErrorCredentials(std::move(creds).status());
113+
}
114+
} else if (cfg.json_object().has_value()) {
115+
auto creds =
116+
oauth2_internal::CreateServiceAccountCredentialsFromJsonContents(
117+
*cfg.json_object(), cfg.options(), std::move(client_factory_));
175118
if (creds) {
176119
result = Decorate(std::move(*creds), cfg.options());
177120
return;
178121
}
179122
result = MakeErrorCredentials(std::move(creds).status());
123+
} else {
124+
result = MakeErrorCredentials(internal::InternalError(
125+
"ServiceAccountConfig has neither json_object nor file_path",
126+
GCP_ERROR_INFO()));
180127
}
181128
}
182129

google/cloud/internal/unified_rest_credentials_test.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,7 @@ TEST(UnifiedRestCredentialsTest, ServiceAccount) {
405405
EXPECT_CALL(client_factory, Call).Times(0);
406406

407407
auto const config =
408-
internal::ServiceAccountConfig(contents.dump(), Options{});
408+
internal::ServiceAccountConfig(contents.dump(), absl::nullopt, Options{});
409409
auto credentials = MapCredentials(config, client_factory.AsStdFunction());
410410
auto access_token = credentials->GetToken(now);
411411
ASSERT_STATUS_OK(access_token);

0 commit comments

Comments
 (0)