Skip to content

Commit 69b1aa7

Browse files
authored
feat(GCS+gRPC): implement GetNativeBucketIamPolicy() (googleapis#8420)
1 parent d4beefe commit 69b1aa7

19 files changed

+274
-5
lines changed

generator/generator_config.textproto

-1
Original file line numberDiff line numberDiff line change
@@ -860,7 +860,6 @@ service {
860860
omit_stub_factory: true
861861
omitted_rpcs: [
862862
"LockBucketRetentionPolicy",
863-
"GetIamPolicy",
864863
"SetIamPolicy",
865864
"TestIamPermissions",
866865
"DeleteNotification",

google/cloud/storage/internal/grpc_bucket_request_parser.cc

+35
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "google/cloud/storage/internal/object_access_control_parser.h"
2424
#include "google/cloud/storage/internal/patch_builder_details.h"
2525
#include "google/cloud/log.h"
26+
#include <algorithm>
2627

2728
namespace google {
2829
namespace cloud {
@@ -125,6 +126,40 @@ ListBucketsResponse GrpcBucketRequestParser::FromProto(
125126
return result;
126127
}
127128

129+
google::iam::v1::GetIamPolicyRequest GrpcBucketRequestParser::ToProto(
130+
GetBucketIamPolicyRequest const& request) {
131+
google::iam::v1::GetIamPolicyRequest result;
132+
result.set_resource("projects/_/buckets/" + request.bucket_name());
133+
if (request.HasOption<RequestedPolicyVersion>()) {
134+
result.mutable_options()->set_requested_policy_version(
135+
static_cast<std::int32_t>(
136+
request.GetOption<RequestedPolicyVersion>().value()));
137+
}
138+
return result;
139+
}
140+
141+
NativeIamBinding GrpcBucketRequestParser::FromProto(
142+
google::iam::v1::Binding const& b) {
143+
std::vector<std::string> members{b.members().begin(), b.members().end()};
144+
if (!b.has_condition()) return {b.role(), std::move(members)};
145+
NativeExpression expr(b.condition().expression(), b.condition().title(),
146+
b.condition().description(), b.condition().location());
147+
return {b.role(), std::move(members), std::move(expr)};
148+
}
149+
150+
NativeIamPolicy GrpcBucketRequestParser::FromProto(
151+
google::iam::v1::Policy const& response) {
152+
std::vector<NativeIamBinding> bindings;
153+
std::transform(
154+
response.bindings().begin(), response.bindings().end(),
155+
std::back_inserter(bindings),
156+
[](google::iam::v1::Binding const& b) { return FromProto(b); });
157+
158+
NativeIamPolicy result(std::move(bindings), response.etag(),
159+
response.version());
160+
return result;
161+
}
162+
128163
StatusOr<google::storage::v2::UpdateBucketRequest>
129164
GrpcBucketRequestParser::ToProto(PatchBucketRequest const& request) {
130165
google::storage::v2::UpdateBucketRequest result;

google/cloud/storage/internal/grpc_bucket_request_parser.h

+5
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ struct GrpcBucketRequestParser {
4141
static ListBucketsResponse FromProto(
4242
google::storage::v2::ListBucketsResponse const& response);
4343

44+
static google::iam::v1::GetIamPolicyRequest ToProto(
45+
GetBucketIamPolicyRequest const& request);
46+
static NativeIamBinding FromProto(google::iam::v1::Binding const& b);
47+
static NativeIamPolicy FromProto(google::iam::v1::Policy const& response);
48+
4449
static StatusOr<google::storage::v2::UpdateBucketRequest> ToProto(
4550
PatchBucketRequest const& request);
4651
static google::storage::v2::UpdateBucketRequest ToProto(

google/cloud/storage/internal/grpc_bucket_request_parser_test.cc

+67
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ namespace {
2828

2929
namespace v2 = ::google::storage::v2;
3030
using ::google::cloud::testing_util::IsProtoEqual;
31+
using ::testing::AllOf;
3132
using ::testing::ElementsAre;
33+
using ::testing::ElementsAreArray;
34+
using ::testing::Property;
3235
using ::testing::UnorderedElementsAre;
3336

3437
TEST(GrpcBucketRequestParser, DeleteBucketMetadataAllOptions) {
@@ -212,6 +215,70 @@ TEST(GrpcBucketRequestParser, ListBucketsResponse) {
212215
EXPECT_THAT(names, ElementsAre("test-bucket-1", "test-bucket-2"));
213216
}
214217

218+
TEST(GrpcBucketRequestParser, GetIamPolicyRequest) {
219+
google::iam::v1::GetIamPolicyRequest expected;
220+
ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(
221+
R"pb(
222+
resource: "projects/_/buckets/test-bucket"
223+
options { requested_policy_version: 3 }
224+
)pb",
225+
&expected));
226+
227+
GetBucketIamPolicyRequest req("test-bucket");
228+
req.set_multiple_options(
229+
RequestedPolicyVersion(3), UserProject("test-user-project"),
230+
QuotaUser("test-quota-user"), UserIp("test-user-ip"));
231+
auto const actual = GrpcBucketRequestParser::ToProto(req);
232+
EXPECT_THAT(actual, IsProtoEqual(expected));
233+
}
234+
235+
TEST(GrpcBucketRequestParser, NativeIamPolicy) {
236+
google::iam::v1::Policy input;
237+
ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(
238+
R"pb(
239+
version: 7
240+
bindings {
241+
role: "role/test.only"
242+
members: "test-1"
243+
members: "test-2"
244+
condition {
245+
expression: "test-expression"
246+
title: "test-title"
247+
description: "test-description"
248+
location: "test-location"
249+
}
250+
}
251+
bindings { role: "role/another.test.only" members: "test-3" }
252+
etag: "test-etag"
253+
)pb",
254+
&input));
255+
256+
auto const actual = GrpcBucketRequestParser::FromProto(input);
257+
EXPECT_EQ(7, actual.version());
258+
EXPECT_EQ("test-etag", actual.etag());
259+
NativeIamBinding b0(
260+
"role/test.only", {"test-1", "test-2"},
261+
{"test-expression", "test-title", "test-description", "test-location"});
262+
NativeIamBinding b1("role/another.test.only", {"test-3"});
263+
264+
auto match_expr = [](NativeExpression const& e) {
265+
return AllOf(Property(&NativeExpression::expression, e.expression()),
266+
Property(&NativeExpression::title, e.title()),
267+
Property(&NativeExpression::description, e.description()),
268+
Property(&NativeExpression::location, e.location()));
269+
};
270+
auto match_binding = [&](NativeIamBinding const& b) {
271+
return AllOf(
272+
Property(&NativeIamBinding::role, b.role()),
273+
Property(&NativeIamBinding::members, ElementsAreArray(b.members())),
274+
Property(&NativeIamBinding::has_condition, b.has_condition()));
275+
};
276+
ASSERT_THAT(actual.bindings(),
277+
ElementsAre(match_binding(b0), match_binding(b1)));
278+
ASSERT_TRUE(actual.bindings()[0].has_condition());
279+
ASSERT_THAT(actual.bindings()[0].condition(), match_expr(b0.condition()));
280+
}
281+
215282
TEST(GrpcBucketRequestParser, PatchBucketRequestAllOptions) {
216283
google::storage::v2::UpdateBucketRequest expected;
217284
ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(

google/cloud/storage/internal/grpc_client.cc

+24-4
Original file line numberDiff line numberDiff line change
@@ -212,14 +212,34 @@ StatusOr<BucketMetadata> GrpcClient::PatchBucket(
212212
return GrpcBucketMetadataParser::FromProto(*response);
213213
}
214214

215+
// TODO(#5929) - remove after decommission is completed
216+
#include "google/cloud/internal/disable_deprecation_warnings.inc"
217+
215218
StatusOr<IamPolicy> GrpcClient::GetBucketIamPolicy(
216-
GetBucketIamPolicyRequest const&) {
217-
return Status(StatusCode::kUnimplemented, __func__);
219+
GetBucketIamPolicyRequest const& request) {
220+
auto proto = GrpcBucketRequestParser::ToProto(request);
221+
grpc::ClientContext context;
222+
ApplyQueryParameters(context, request);
223+
auto response = stub_->GetIamPolicy(context, proto);
224+
if (!response) return std::move(response).status();
225+
IamBindings bindings;
226+
for (auto const& b : response->bindings()) {
227+
bindings.AddMembers(b.role(), std::set<std::string>(b.members().begin(),
228+
b.members().end()));
229+
}
230+
return IamPolicy{response->version(), std::move(bindings), response->etag()};
218231
}
219232

233+
#include "google/cloud/internal/diagnostics_pop.inc"
234+
220235
StatusOr<NativeIamPolicy> GrpcClient::GetNativeBucketIamPolicy(
221-
GetBucketIamPolicyRequest const&) {
222-
return Status(StatusCode::kUnimplemented, __func__);
236+
GetBucketIamPolicyRequest const& request) {
237+
auto proto = GrpcBucketRequestParser::ToProto(request);
238+
grpc::ClientContext context;
239+
ApplyQueryParameters(context, request);
240+
auto response = stub_->GetIamPolicy(context, proto);
241+
if (!response) return std::move(response).status();
242+
return GrpcBucketRequestParser::FromProto(*response);
223243
}
224244

225245
StatusOr<IamPolicy> GrpcClient::SetBucketIamPolicy(

google/cloud/storage/internal/grpc_client_test.cc

+40
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,46 @@ TEST_F(GrpcClientTest, PatchBucket) {
204204
EXPECT_EQ(response.status(), PermanentError());
205205
}
206206

207+
TEST_F(GrpcClientTest, GetBucketIamPolicy) {
208+
auto mock = std::make_shared<testing::MockStorageStub>();
209+
EXPECT_CALL(*mock, GetIamPolicy)
210+
.WillOnce([this](grpc::ClientContext& context,
211+
google::iam::v1::GetIamPolicyRequest const& request) {
212+
auto metadata = GetMetadata(context);
213+
EXPECT_THAT(metadata, UnorderedElementsAre(
214+
Pair("x-goog-quota-user", "test-quota-user"),
215+
Pair("x-goog-fieldmask", "field1,field2")));
216+
EXPECT_THAT(request.resource(), "projects/_/buckets/test-bucket");
217+
return PermanentError();
218+
});
219+
auto client = CreateTestClient(mock);
220+
auto response = client->GetBucketIamPolicy(
221+
GetBucketIamPolicyRequest("test-bucket")
222+
.set_multiple_options(Fields("field1,field2"),
223+
QuotaUser("test-quota-user")));
224+
EXPECT_EQ(response.status(), PermanentError());
225+
}
226+
227+
TEST_F(GrpcClientTest, GetNativeBucketIamPolicy) {
228+
auto mock = std::make_shared<testing::MockStorageStub>();
229+
EXPECT_CALL(*mock, GetIamPolicy)
230+
.WillOnce([this](grpc::ClientContext& context,
231+
google::iam::v1::GetIamPolicyRequest const& request) {
232+
auto metadata = GetMetadata(context);
233+
EXPECT_THAT(metadata, UnorderedElementsAre(
234+
Pair("x-goog-quota-user", "test-quota-user"),
235+
Pair("x-goog-fieldmask", "field1,field2")));
236+
EXPECT_THAT(request.resource(), "projects/_/buckets/test-bucket");
237+
return PermanentError();
238+
});
239+
auto client = CreateTestClient(mock);
240+
auto response = client->GetNativeBucketIamPolicy(
241+
GetBucketIamPolicyRequest("test-bucket")
242+
.set_multiple_options(Fields("field1,field2"),
243+
QuotaUser("test-quota-user")));
244+
EXPECT_EQ(response.status(), PermanentError());
245+
}
246+
207247
TEST_F(GrpcClientTest, InsertObjectMedia) {
208248
auto mock = std::make_shared<testing::MockStorageStub>();
209249
EXPECT_CALL(*mock, WriteObject)

google/cloud/storage/internal/storage_auth_decorator.cc

+8
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ StatusOr<google::storage::v2::ListBucketsResponse> StorageAuth::ListBuckets(
6262
return child_->ListBuckets(context, request);
6363
}
6464

65+
StatusOr<google::iam::v1::Policy> StorageAuth::GetIamPolicy(
66+
grpc::ClientContext& context,
67+
google::iam::v1::GetIamPolicyRequest const& request) {
68+
auto status = auth_->ConfigureContext(context);
69+
if (!status.ok()) return status;
70+
return child_->GetIamPolicy(context, request);
71+
}
72+
6573
StatusOr<google::storage::v2::Bucket> StorageAuth::UpdateBucket(
6674
grpc::ClientContext& context,
6775
google::storage::v2::UpdateBucketRequest const& request) {

google/cloud/storage/internal/storage_auth_decorator.h

+4
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ class StorageAuth : public StorageStub {
5454
grpc::ClientContext& context,
5555
google::storage::v2::ListBucketsRequest const& request) override;
5656

57+
StatusOr<google::iam::v1::Policy> GetIamPolicy(
58+
grpc::ClientContext& context,
59+
google::iam::v1::GetIamPolicyRequest const& request) override;
60+
5761
StatusOr<google::storage::v2::Bucket> UpdateBucket(
5862
grpc::ClientContext& context,
5963
google::storage::v2::UpdateBucketRequest const& request) override;

google/cloud/storage/internal/storage_logging_decorator.cc

+11
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,17 @@ StatusOr<google::storage::v2::ListBucketsResponse> StorageLogging::ListBuckets(
8080
context, request, __func__, tracing_options_);
8181
}
8282

83+
StatusOr<google::iam::v1::Policy> StorageLogging::GetIamPolicy(
84+
grpc::ClientContext& context,
85+
google::iam::v1::GetIamPolicyRequest const& request) {
86+
return google::cloud::internal::LogWrapper(
87+
[this](grpc::ClientContext& context,
88+
google::iam::v1::GetIamPolicyRequest const& request) {
89+
return child_->GetIamPolicy(context, request);
90+
},
91+
context, request, __func__, tracing_options_);
92+
}
93+
8394
StatusOr<google::storage::v2::Bucket> StorageLogging::UpdateBucket(
8495
grpc::ClientContext& context,
8596
google::storage::v2::UpdateBucketRequest const& request) {

google/cloud/storage/internal/storage_logging_decorator.h

+4
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ class StorageLogging : public StorageStub {
5454
grpc::ClientContext& context,
5555
google::storage::v2::ListBucketsRequest const& request) override;
5656

57+
StatusOr<google::iam::v1::Policy> GetIamPolicy(
58+
grpc::ClientContext& context,
59+
google::iam::v1::GetIamPolicyRequest const& request) override;
60+
5761
StatusOr<google::storage::v2::Bucket> UpdateBucket(
5862
grpc::ClientContext& context,
5963
google::storage::v2::UpdateBucketRequest const& request) override;

google/cloud/storage/internal/storage_metadata_decorator.cc

+7
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ StatusOr<google::storage::v2::ListBucketsResponse> StorageMetadata::ListBuckets(
6161
return child_->ListBuckets(context, request);
6262
}
6363

64+
StatusOr<google::iam::v1::Policy> StorageMetadata::GetIamPolicy(
65+
grpc::ClientContext& context,
66+
google::iam::v1::GetIamPolicyRequest const& request) {
67+
SetMetadata(context, {});
68+
return child_->GetIamPolicy(context, request);
69+
}
70+
6471
StatusOr<google::storage::v2::Bucket> StorageMetadata::UpdateBucket(
6572
grpc::ClientContext& context,
6673
google::storage::v2::UpdateBucketRequest const& request) {

google/cloud/storage/internal/storage_metadata_decorator.h

+4
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ class StorageMetadata : public StorageStub {
5050
grpc::ClientContext& context,
5151
google::storage::v2::ListBucketsRequest const& request) override;
5252

53+
StatusOr<google::iam::v1::Policy> GetIamPolicy(
54+
grpc::ClientContext& context,
55+
google::iam::v1::GetIamPolicyRequest const& request) override;
56+
5357
StatusOr<google::storage::v2::Bucket> UpdateBucket(
5458
grpc::ClientContext& context,
5559
google::storage::v2::UpdateBucketRequest const& request) override;

google/cloud/storage/internal/storage_round_robin.cc

+6
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ StorageRoundRobin::ListBuckets(
4444
return Child()->ListBuckets(context, request);
4545
}
4646

47+
StatusOr<google::iam::v1::Policy> StorageRoundRobin::GetIamPolicy(
48+
grpc::ClientContext& context,
49+
google::iam::v1::GetIamPolicyRequest const& request) {
50+
return Child()->GetIamPolicy(context, request);
51+
}
52+
4753
StatusOr<google::storage::v2::Bucket> StorageRoundRobin::UpdateBucket(
4854
grpc::ClientContext& context,
4955
google::storage::v2::UpdateBucketRequest const& request) {

google/cloud/storage/internal/storage_round_robin.h

+4
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ class StorageRoundRobin : public StorageStub {
4747
grpc::ClientContext& context,
4848
google::storage::v2::ListBucketsRequest const& request) override;
4949

50+
StatusOr<google::iam::v1::Policy> GetIamPolicy(
51+
grpc::ClientContext& context,
52+
google::iam::v1::GetIamPolicyRequest const& request) override;
53+
5054
StatusOr<google::storage::v2::Bucket> UpdateBucket(
5155
grpc::ClientContext& context,
5256
google::storage::v2::UpdateBucketRequest const& request) override;

google/cloud/storage/internal/storage_round_robin_test.cc

+19
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,25 @@ TEST(StorageRoundRobinTest, ListBuckets) {
147147
}
148148
}
149149

150+
TEST(StorageRoundRobinTest, GetIamPolicy) {
151+
auto mocks = MakeMocks();
152+
InSequence sequence;
153+
for (int i = 0; i != kRepeats; ++i) {
154+
for (auto& m : mocks) {
155+
EXPECT_CALL(*m, GetIamPolicy)
156+
.WillOnce(Return(Status(StatusCode::kPermissionDenied, "uh-oh")));
157+
}
158+
}
159+
160+
StorageRoundRobin under_test(AsPlainStubs(mocks));
161+
for (size_t i = 0; i != kRepeats * mocks.size(); ++i) {
162+
grpc::ClientContext context;
163+
google::iam::v1::GetIamPolicyRequest request;
164+
auto response = under_test.GetIamPolicy(context, request);
165+
EXPECT_THAT(response, StatusIs(StatusCode::kPermissionDenied));
166+
}
167+
}
168+
150169
TEST(StorageRoundRobinTest, UpdateBucket) {
151170
auto mocks = MakeMocks();
152171
InSequence sequence;

google/cloud/storage/internal/storage_stub.cc

+11
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,17 @@ DefaultStorageStub::ListBuckets(
7575
return response;
7676
}
7777

78+
StatusOr<google::iam::v1::Policy> DefaultStorageStub::GetIamPolicy(
79+
grpc::ClientContext& client_context,
80+
google::iam::v1::GetIamPolicyRequest const& request) {
81+
google::iam::v1::Policy response;
82+
auto status = grpc_stub_->GetIamPolicy(&client_context, request, &response);
83+
if (!status.ok()) {
84+
return google::cloud::MakeStatusFromRpcError(status);
85+
}
86+
return response;
87+
}
88+
7889
StatusOr<google::storage::v2::Bucket> DefaultStorageStub::UpdateBucket(
7990
grpc::ClientContext& client_context,
8091
google::storage::v2::UpdateBucketRequest const& request) {

0 commit comments

Comments
 (0)