Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement GrpcStorageImpl#createDefaultAcl & GrpcStorageImpl#updateDefaultAcl #1806

Merged
merged 2 commits into from
Dec 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1285,7 +1285,7 @@ public boolean deleteDefaultAcl(Entity entity) {
*
* @throws StorageException upon failure
*/
@TransportCompatibility({Transport.HTTP})
@TransportCompatibility({Transport.HTTP, Transport.GRPC})
public Acl createDefaultAcl(Acl acl) {
return storage.createDefaultAcl(getName(), acl);
}
Expand All @@ -1304,7 +1304,7 @@ public Acl createDefaultAcl(Acl acl) {
*
* @throws StorageException upon failure
*/
@TransportCompatibility({Transport.HTTP})
@TransportCompatibility({Transport.HTTP, Transport.GRPC})
public Acl updateDefaultAcl(Acl acl) {
return storage.updateDefaultAcl(getName(), acl);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
import com.google.common.io.BaseEncoding;
import com.google.common.io.ByteStreams;
import com.google.iam.v1.GetIamPolicyRequest;
Expand Down Expand Up @@ -936,12 +937,59 @@ public boolean deleteDefaultAcl(String bucket, Entity entity) {

@Override
public Acl createDefaultAcl(String bucket, Acl acl) {
return throwNotYetImplemented(fmtMethodName("createDefaultAcl", String.class, Acl.class));
return updateDefaultAcl(bucket, acl);
sydney-munro marked this conversation as resolved.
Show resolved Hide resolved
}

@Override
public Acl updateDefaultAcl(String bucket, Acl acl) {
return throwNotYetImplemented(fmtMethodName("updateDefaultAcl", String.class, Acl.class));
try {
com.google.storage.v2.Bucket resp = getBucketDefaultAcls(bucket);
ObjectAccessControl encode = codecs.objectAcl().encode(acl);
String entity = encode.getEntity();

Predicate<ObjectAccessControl> entityPredicate = objectAclEntityOrAltEq(entity);

ImmutableList<ObjectAccessControl> collect =
Streams.concat(
resp.getDefaultObjectAclList().stream().filter(entityPredicate.negate()),
Stream.of(encode))
.collect(ImmutableList.toImmutableList());

com.google.storage.v2.Bucket update =
com.google.storage.v2.Bucket.newBuilder()
.setName(bucketNameCodec.encode(bucket))
.addAllDefaultObjectAcl(collect)
.build();
Opts<BucketTargetOpt> opts =
Opts.from(
UnifiedOpts.fields(ImmutableSet.of(BucketField.DEFAULT_OBJECT_ACL)),
UnifiedOpts.metagenerationMatch(resp.getMetageneration()));
UpdateBucketRequest req =
opts.updateBucketsRequest()
.apply(UpdateBucketRequest.newBuilder())
.setBucket(update)
.build();

GrpcCallContext grpcCallContext = GrpcCallContext.createDefault();
com.google.storage.v2.Bucket updateResult =
Retrying.run(
getOptions(),
retryAlgorithmManager.getFor(req),
() -> storageClient.updateBucketCallable().call(req, grpcCallContext),
Decoder.identity());

//noinspection DataFlowIssue
Optional<Acl> first =
updateResult.getDefaultObjectAclList().stream()
.filter(entityPredicate)
.findFirst()
.map(codecs.objectAcl()::decode);

return first.orElseThrow(
() -> new StorageException(0, "Acl update call success, but not in response"));
} catch (NotFoundException e) {
throw StorageException.coalesce(e);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3530,7 +3530,7 @@ PostPolicyV4 generateSignedPostPolicyV4(
*
* @throws StorageException upon failure
*/
@TransportCompatibility({Transport.HTTP})
@TransportCompatibility({Transport.HTTP, Transport.GRPC})
Acl createDefaultAcl(String bucket, Acl acl);

/**
Expand All @@ -3549,7 +3549,7 @@ PostPolicyV4 generateSignedPostPolicyV4(
*
* @throws StorageException upon failure
*/
@TransportCompatibility({Transport.HTTP})
@TransportCompatibility({Transport.HTTP, Transport.GRPC})
Acl updateDefaultAcl(String bucket, Acl acl);

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,7 @@ public Mapper<ListObjectsRequest.Builder> listObjects() {
}

static final class Fields extends RpcOptVal<ImmutableSet<NamedField>>
implements ObjectSourceOpt, ObjectListOpt, BucketSourceOpt, BucketListOpt {
implements ObjectSourceOpt, ObjectListOpt, BucketSourceOpt, BucketTargetOpt, BucketListOpt {

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

@Override
public Mapper<UpdateBucketRequest.Builder> updateBucket() {
return b -> b.setUpdateMask(FieldMask.newBuilder().addAllPaths(getPaths()).build());
}

@Override
public Mapper<GetObjectRequest.Builder> getObject() {
return b -> b.setReadMask(FieldMask.newBuilder().addAllPaths(getPaths()).build());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
import com.google.cloud.RetryHelper.RetryHelperException;
import com.google.cloud.http.BaseHttpServiceException;
import com.google.cloud.storage.Acl;
import com.google.cloud.storage.Acl.Entity;
import com.google.cloud.storage.Acl.Project.ProjectRole;
import com.google.cloud.storage.Acl.Role;
import com.google.cloud.storage.Acl.User;
import com.google.cloud.storage.Blob;
Expand Down Expand Up @@ -70,6 +72,7 @@
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.function.Predicate;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import org.junit.Ignore;
Expand Down Expand Up @@ -185,12 +188,108 @@ public void bucket_defaultAcl_list_bucket404() {
assertThat(storageException.getCode()).isEqualTo(404);
}

@Test
public void bucket_defaultAcl_create() throws Exception {
BucketInfo bucketInfo = BucketInfo.newBuilder(generator.randomBucketName()).build();
try (TemporaryBucket tempB =
TemporaryBucket.newBuilder().setBucketInfo(bucketInfo).setStorage(storage).build()) {
BucketInfo bucket = tempB.getBucket();

Acl readAll = Acl.of(User.ofAllAuthenticatedUsers(), Role.READER);
Acl actual = retry429s(() -> storage.createDefaultAcl(bucket.getName(), readAll), storage);

assertThat(actual.getEntity()).isEqualTo(readAll.getEntity());
assertThat(actual.getRole()).isEqualTo(readAll.getRole());
assertThat(actual.getEtag()).isNotEmpty();

Bucket bucketUpdated =
storage.get(bucket.getName(), BucketGetOption.fields(BucketField.values()));
assertThat(bucketUpdated.getMetageneration()).isNotEqualTo(bucket.getMetageneration());

// etags change when updates happen, drop before our comparison
List<Acl> expectedAcls = dropEtags(bucket.getDefaultAcl());
List<Acl> actualAcls = dropEtags(bucketUpdated.getDefaultAcl());
assertThat(actualAcls).containsAtLeastElementsIn(expectedAcls);
assertThat(actualAcls).contains(readAll);
}
}

@Test
public void bucket_defaultAcl_create_bucket404() {
Acl readAll = Acl.of(User.ofAllAuthenticatedUsers(), Role.READER);
StorageException storageException =
assertThrows(
StorageException.class,
() ->
retry429s(
() -> storage.createDefaultAcl(bucket.getName() + "x", readAll), storage));

assertThat(storageException.getCode()).isEqualTo(404);
}

@Test
public void bucket_defaultAcl_update() throws Exception {
BucketInfo bucketInfo = BucketInfo.newBuilder(generator.randomBucketName()).build();
try (TemporaryBucket tempB =
TemporaryBucket.newBuilder().setBucketInfo(bucketInfo).setStorage(storage).build()) {
BucketInfo bucket = tempB.getBucket();

List<Acl> defaultAcls = bucket.getDefaultAcl();
assertThat(defaultAcls).isNotEmpty();

Predicate<Acl> isProjectEditor = hasProjectRole(ProjectRole.EDITORS);

//noinspection OptionalGetWithoutIsPresent
Acl projectEditorAsOwner =
defaultAcls.stream().filter(hasRole(Role.OWNER).and(isProjectEditor)).findFirst().get();

// lower the privileges of project editors to writer from owner
Entity entity = projectEditorAsOwner.getEntity();
Acl projectEditorAsReader = Acl.of(entity, Role.READER);

Acl actual =
retry429s(
() -> storage.updateDefaultAcl(bucket.getName(), projectEditorAsReader), storage);

assertThat(actual.getEntity()).isEqualTo(projectEditorAsReader.getEntity());
assertThat(actual.getRole()).isEqualTo(projectEditorAsReader.getRole());
assertThat(actual.getEtag()).isNotEmpty();

Bucket bucketUpdated =
storage.get(bucket.getName(), BucketGetOption.fields(BucketField.values()));
assertThat(bucketUpdated.getMetageneration()).isNotEqualTo(bucket.getMetageneration());

// etags change when updates happen, drop before our comparison
List<Acl> expectedAcls =
dropEtags(
bucket.getDefaultAcl().stream()
.filter(isProjectEditor.negate())
.collect(Collectors.toList()));
List<Acl> actualAcls = dropEtags(bucketUpdated.getDefaultAcl());
assertThat(actualAcls).containsAtLeastElementsIn(expectedAcls);
assertThat(actualAcls).doesNotContain(projectEditorAsOwner);
assertThat(actualAcls).contains(projectEditorAsReader);
}
}

@Test
public void bucket_defaultAcl_update_bucket404() {
Acl readAll = Acl.of(User.ofAllAuthenticatedUsers(), Role.READER);
StorageException storageException =
assertThrows(
StorageException.class,
() ->
retry429s(
() -> storage.updateDefaultAcl(bucket.getName() + "x", readAll), storage));

assertThat(storageException.getCode()).isEqualTo(404);
}

@Test
@CrossRun.Ignore(transports = Transport.GRPC)
public void testBucketDefaultAcl() {
// TODO: break this test up into each of the respective scenarios
// 2. Delete a default ACL for a specific entity
// 3. Create a default ACL for specific entity
// 4. Update default ACL to change role of a specific entity
BenWhitehead marked this conversation as resolved.
Show resolved Hide resolved

// according to https://cloud.google.com/storage/docs/access-control/lists#default
Expand Down Expand Up @@ -1026,4 +1125,24 @@ public boolean shouldRetry(Throwable previousThrowable, Object previousResponse)
}
}
}

private static ImmutableList<Acl> dropEtags(List<Acl> defaultAcls) {
return defaultAcls.stream()
.map(acl -> Acl.of(acl.getEntity(), acl.getRole()))
.collect(ImmutableList.toImmutableList());
}

private static Predicate<Acl> hasRole(Acl.Role expected) {
return acl -> acl.getRole().equals(expected);
}

private static Predicate<Acl> hasProjectRole(Acl.Project.ProjectRole expected) {
return acl -> {
Entity entity = acl.getEntity();
if (entity.getType().equals(Entity.Type.PROJECT)) {
return ((Acl.Project) entity).getProjectRole().equals(expected);
}
return false;
};
}
}