Skip to content

Commit 0f24a11

Browse files
authored
feat: implement GrpcStorageImpl#createDefaultAcl & GrpcStorageImpl#updateDefaultAcl (#1806)
1 parent 2791199 commit 0f24a11

File tree

5 files changed

+180
-8
lines changed

5 files changed

+180
-8
lines changed

google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1285,7 +1285,7 @@ public boolean deleteDefaultAcl(Entity entity) {
12851285
*
12861286
* @throws StorageException upon failure
12871287
*/
1288-
@TransportCompatibility({Transport.HTTP})
1288+
@TransportCompatibility({Transport.HTTP, Transport.GRPC})
12891289
public Acl createDefaultAcl(Acl acl) {
12901290
return storage.createDefaultAcl(getName(), acl);
12911291
}
@@ -1304,7 +1304,7 @@ public Acl createDefaultAcl(Acl acl) {
13041304
*
13051305
* @throws StorageException upon failure
13061306
*/
1307-
@TransportCompatibility({Transport.HTTP})
1307+
@TransportCompatibility({Transport.HTTP, Transport.GRPC})
13081308
public Acl updateDefaultAcl(Acl acl) {
13091309
return storage.updateDefaultAcl(getName(), acl);
13101310
}

google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
import com.google.common.annotations.VisibleForTesting;
6969
import com.google.common.collect.ImmutableList;
7070
import com.google.common.collect.ImmutableSet;
71+
import com.google.common.collect.Streams;
7172
import com.google.common.io.BaseEncoding;
7273
import com.google.common.io.ByteStreams;
7374
import com.google.iam.v1.GetIamPolicyRequest;
@@ -936,12 +937,59 @@ public boolean deleteDefaultAcl(String bucket, Entity entity) {
936937

937938
@Override
938939
public Acl createDefaultAcl(String bucket, Acl acl) {
939-
return throwNotYetImplemented(fmtMethodName("createDefaultAcl", String.class, Acl.class));
940+
return updateDefaultAcl(bucket, acl);
940941
}
941942

942943
@Override
943944
public Acl updateDefaultAcl(String bucket, Acl acl) {
944-
return throwNotYetImplemented(fmtMethodName("updateDefaultAcl", String.class, Acl.class));
945+
try {
946+
com.google.storage.v2.Bucket resp = getBucketDefaultAcls(bucket);
947+
ObjectAccessControl encode = codecs.objectAcl().encode(acl);
948+
String entity = encode.getEntity();
949+
950+
Predicate<ObjectAccessControl> entityPredicate = objectAclEntityOrAltEq(entity);
951+
952+
ImmutableList<ObjectAccessControl> collect =
953+
Streams.concat(
954+
resp.getDefaultObjectAclList().stream().filter(entityPredicate.negate()),
955+
Stream.of(encode))
956+
.collect(ImmutableList.toImmutableList());
957+
958+
com.google.storage.v2.Bucket update =
959+
com.google.storage.v2.Bucket.newBuilder()
960+
.setName(bucketNameCodec.encode(bucket))
961+
.addAllDefaultObjectAcl(collect)
962+
.build();
963+
Opts<BucketTargetOpt> opts =
964+
Opts.from(
965+
UnifiedOpts.fields(ImmutableSet.of(BucketField.DEFAULT_OBJECT_ACL)),
966+
UnifiedOpts.metagenerationMatch(resp.getMetageneration()));
967+
UpdateBucketRequest req =
968+
opts.updateBucketsRequest()
969+
.apply(UpdateBucketRequest.newBuilder())
970+
.setBucket(update)
971+
.build();
972+
973+
GrpcCallContext grpcCallContext = GrpcCallContext.createDefault();
974+
com.google.storage.v2.Bucket updateResult =
975+
Retrying.run(
976+
getOptions(),
977+
retryAlgorithmManager.getFor(req),
978+
() -> storageClient.updateBucketCallable().call(req, grpcCallContext),
979+
Decoder.identity());
980+
981+
//noinspection DataFlowIssue
982+
Optional<Acl> first =
983+
updateResult.getDefaultObjectAclList().stream()
984+
.filter(entityPredicate)
985+
.findFirst()
986+
.map(codecs.objectAcl()::decode);
987+
988+
return first.orElseThrow(
989+
() -> new StorageException(0, "Acl update call success, but not in response"));
990+
} catch (NotFoundException e) {
991+
throw StorageException.coalesce(e);
992+
}
945993
}
946994

947995
@Override

google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3530,7 +3530,7 @@ PostPolicyV4 generateSignedPostPolicyV4(
35303530
*
35313531
* @throws StorageException upon failure
35323532
*/
3533-
@TransportCompatibility({Transport.HTTP})
3533+
@TransportCompatibility({Transport.HTTP, Transport.GRPC})
35343534
Acl createDefaultAcl(String bucket, Acl acl);
35353535

35363536
/**
@@ -3549,7 +3549,7 @@ PostPolicyV4 generateSignedPostPolicyV4(
35493549
*
35503550
* @throws StorageException upon failure
35513551
*/
3552-
@TransportCompatibility({Transport.HTTP})
3552+
@TransportCompatibility({Transport.HTTP, Transport.GRPC})
35533553
Acl updateDefaultAcl(String bucket, Acl acl);
35543554

35553555
/**

google-cloud-storage/src/main/java/com/google/cloud/storage/UnifiedOpts.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,7 @@ public Mapper<ListObjectsRequest.Builder> listObjects() {
713713
}
714714

715715
static final class Fields extends RpcOptVal<ImmutableSet<NamedField>>
716-
implements ObjectSourceOpt, ObjectListOpt, BucketSourceOpt, BucketListOpt {
716+
implements ObjectSourceOpt, ObjectListOpt, BucketSourceOpt, BucketTargetOpt, BucketListOpt {
717717

718718
/**
719719
* Apiary and gRPC have differing handling of where the field selector is evaluated relative to
@@ -752,6 +752,11 @@ public Mapper<ListBucketsRequest.Builder> listBuckets() {
752752
return b -> b.setReadMask(FieldMask.newBuilder().addAllPaths(getPaths()).build());
753753
}
754754

755+
@Override
756+
public Mapper<UpdateBucketRequest.Builder> updateBucket() {
757+
return b -> b.setUpdateMask(FieldMask.newBuilder().addAllPaths(getPaths()).build());
758+
}
759+
755760
@Override
756761
public Mapper<GetObjectRequest.Builder> getObject() {
757762
return b -> b.setReadMask(FieldMask.newBuilder().addAllPaths(getPaths()).build());

google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITAccessTest.java

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
import com.google.cloud.RetryHelper.RetryHelperException;
3434
import com.google.cloud.http.BaseHttpServiceException;
3535
import com.google.cloud.storage.Acl;
36+
import com.google.cloud.storage.Acl.Entity;
37+
import com.google.cloud.storage.Acl.Project.ProjectRole;
3638
import com.google.cloud.storage.Acl.Role;
3739
import com.google.cloud.storage.Acl.User;
3840
import com.google.cloud.storage.Blob;
@@ -70,6 +72,7 @@
7072
import java.util.Map;
7173
import java.util.Set;
7274
import java.util.concurrent.Callable;
75+
import java.util.function.Predicate;
7376
import java.util.stream.Collector;
7477
import java.util.stream.Collectors;
7578
import org.junit.Ignore;
@@ -185,12 +188,108 @@ public void bucket_defaultAcl_list_bucket404() {
185188
assertThat(storageException.getCode()).isEqualTo(404);
186189
}
187190

191+
@Test
192+
public void bucket_defaultAcl_create() throws Exception {
193+
BucketInfo bucketInfo = BucketInfo.newBuilder(generator.randomBucketName()).build();
194+
try (TemporaryBucket tempB =
195+
TemporaryBucket.newBuilder().setBucketInfo(bucketInfo).setStorage(storage).build()) {
196+
BucketInfo bucket = tempB.getBucket();
197+
198+
Acl readAll = Acl.of(User.ofAllAuthenticatedUsers(), Role.READER);
199+
Acl actual = retry429s(() -> storage.createDefaultAcl(bucket.getName(), readAll), storage);
200+
201+
assertThat(actual.getEntity()).isEqualTo(readAll.getEntity());
202+
assertThat(actual.getRole()).isEqualTo(readAll.getRole());
203+
assertThat(actual.getEtag()).isNotEmpty();
204+
205+
Bucket bucketUpdated =
206+
storage.get(bucket.getName(), BucketGetOption.fields(BucketField.values()));
207+
assertThat(bucketUpdated.getMetageneration()).isNotEqualTo(bucket.getMetageneration());
208+
209+
// etags change when updates happen, drop before our comparison
210+
List<Acl> expectedAcls = dropEtags(bucket.getDefaultAcl());
211+
List<Acl> actualAcls = dropEtags(bucketUpdated.getDefaultAcl());
212+
assertThat(actualAcls).containsAtLeastElementsIn(expectedAcls);
213+
assertThat(actualAcls).contains(readAll);
214+
}
215+
}
216+
217+
@Test
218+
public void bucket_defaultAcl_create_bucket404() {
219+
Acl readAll = Acl.of(User.ofAllAuthenticatedUsers(), Role.READER);
220+
StorageException storageException =
221+
assertThrows(
222+
StorageException.class,
223+
() ->
224+
retry429s(
225+
() -> storage.createDefaultAcl(bucket.getName() + "x", readAll), storage));
226+
227+
assertThat(storageException.getCode()).isEqualTo(404);
228+
}
229+
230+
@Test
231+
public void bucket_defaultAcl_update() throws Exception {
232+
BucketInfo bucketInfo = BucketInfo.newBuilder(generator.randomBucketName()).build();
233+
try (TemporaryBucket tempB =
234+
TemporaryBucket.newBuilder().setBucketInfo(bucketInfo).setStorage(storage).build()) {
235+
BucketInfo bucket = tempB.getBucket();
236+
237+
List<Acl> defaultAcls = bucket.getDefaultAcl();
238+
assertThat(defaultAcls).isNotEmpty();
239+
240+
Predicate<Acl> isProjectEditor = hasProjectRole(ProjectRole.EDITORS);
241+
242+
//noinspection OptionalGetWithoutIsPresent
243+
Acl projectEditorAsOwner =
244+
defaultAcls.stream().filter(hasRole(Role.OWNER).and(isProjectEditor)).findFirst().get();
245+
246+
// lower the privileges of project editors to writer from owner
247+
Entity entity = projectEditorAsOwner.getEntity();
248+
Acl projectEditorAsReader = Acl.of(entity, Role.READER);
249+
250+
Acl actual =
251+
retry429s(
252+
() -> storage.updateDefaultAcl(bucket.getName(), projectEditorAsReader), storage);
253+
254+
assertThat(actual.getEntity()).isEqualTo(projectEditorAsReader.getEntity());
255+
assertThat(actual.getRole()).isEqualTo(projectEditorAsReader.getRole());
256+
assertThat(actual.getEtag()).isNotEmpty();
257+
258+
Bucket bucketUpdated =
259+
storage.get(bucket.getName(), BucketGetOption.fields(BucketField.values()));
260+
assertThat(bucketUpdated.getMetageneration()).isNotEqualTo(bucket.getMetageneration());
261+
262+
// etags change when updates happen, drop before our comparison
263+
List<Acl> expectedAcls =
264+
dropEtags(
265+
bucket.getDefaultAcl().stream()
266+
.filter(isProjectEditor.negate())
267+
.collect(Collectors.toList()));
268+
List<Acl> actualAcls = dropEtags(bucketUpdated.getDefaultAcl());
269+
assertThat(actualAcls).containsAtLeastElementsIn(expectedAcls);
270+
assertThat(actualAcls).doesNotContain(projectEditorAsOwner);
271+
assertThat(actualAcls).contains(projectEditorAsReader);
272+
}
273+
}
274+
275+
@Test
276+
public void bucket_defaultAcl_update_bucket404() {
277+
Acl readAll = Acl.of(User.ofAllAuthenticatedUsers(), Role.READER);
278+
StorageException storageException =
279+
assertThrows(
280+
StorageException.class,
281+
() ->
282+
retry429s(
283+
() -> storage.updateDefaultAcl(bucket.getName() + "x", readAll), storage));
284+
285+
assertThat(storageException.getCode()).isEqualTo(404);
286+
}
287+
188288
@Test
189289
@CrossRun.Ignore(transports = Transport.GRPC)
190290
public void testBucketDefaultAcl() {
191291
// TODO: break this test up into each of the respective scenarios
192292
// 2. Delete a default ACL for a specific entity
193-
// 3. Create a default ACL for specific entity
194293
// 4. Update default ACL to change role of a specific entity
195294

196295
// according to https://cloud.google.com/storage/docs/access-control/lists#default
@@ -1026,4 +1125,24 @@ public boolean shouldRetry(Throwable previousThrowable, Object previousResponse)
10261125
}
10271126
}
10281127
}
1128+
1129+
private static ImmutableList<Acl> dropEtags(List<Acl> defaultAcls) {
1130+
return defaultAcls.stream()
1131+
.map(acl -> Acl.of(acl.getEntity(), acl.getRole()))
1132+
.collect(ImmutableList.toImmutableList());
1133+
}
1134+
1135+
private static Predicate<Acl> hasRole(Acl.Role expected) {
1136+
return acl -> acl.getRole().equals(expected);
1137+
}
1138+
1139+
private static Predicate<Acl> hasProjectRole(Acl.Project.ProjectRole expected) {
1140+
return acl -> {
1141+
Entity entity = acl.getEntity();
1142+
if (entity.getType().equals(Entity.Type.PROJECT)) {
1143+
return ((Acl.Project) entity).getProjectRole().equals(expected);
1144+
}
1145+
return false;
1146+
};
1147+
}
10291148
}

0 commit comments

Comments
 (0)