diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java index 95c1d78ed..a54948725 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java @@ -105,17 +105,56 @@ public com.google.api.services.storage.model.Bucket apply(BucketInfo bucketInfo) private static final Logger log = Logger.getLogger(BucketInfo.class.getName()); + /** + * Public Access Prevention enum with expected values. + * + * @see public-access-prevention + */ + public enum PublicAccessPrevention { + ENFORCED("enforced"), + /** Default value for Public Access Prevention */ + UNSPECIFIED("unspecified"), + /** + * If the api returns a value that isn't defined in {@link PublicAccessPrevention} this value + * will be returned. + */ + UNKNOWN(null); + + private final String value; + + PublicAccessPrevention(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public static PublicAccessPrevention parse(String value) { + String upper = value.toUpperCase(); + try { + return valueOf(upper); + } catch (IllegalArgumentException ignore) { + return UNKNOWN; + } + } + } + /** * The Bucket's IAM Configuration. * * @see uniform * bucket-level access + * @see public-access-prevention */ public static class IamConfiguration implements Serializable { private static final long serialVersionUID = -8671736104909424616L; - private Boolean isUniformBucketLevelAccessEnabled; - private Long uniformBucketLevelAccessLockedTime; + private final Boolean isUniformBucketLevelAccessEnabled; + private final Long uniformBucketLevelAccessLockedTime; + private final PublicAccessPrevention publicAccessPrevention; @Override public boolean equals(Object o) { @@ -129,12 +168,16 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(isUniformBucketLevelAccessEnabled, uniformBucketLevelAccessLockedTime); + return Objects.hash( + isUniformBucketLevelAccessEnabled, + uniformBucketLevelAccessLockedTime, + publicAccessPrevention); } private IamConfiguration(Builder builder) { this.isUniformBucketLevelAccessEnabled = builder.isUniformBucketLevelAccessEnabled; this.uniformBucketLevelAccessLockedTime = builder.uniformBucketLevelAccessLockedTime; + this.publicAccessPrevention = builder.publicAccessPrevention; } public static Builder newBuilder() { @@ -145,6 +188,7 @@ public Builder toBuilder() { Builder builder = new Builder(); builder.isUniformBucketLevelAccessEnabled = isUniformBucketLevelAccessEnabled; builder.uniformBucketLevelAccessLockedTime = uniformBucketLevelAccessLockedTime; + builder.publicAccessPrevention = publicAccessPrevention; return builder; } @@ -168,6 +212,11 @@ public Long getUniformBucketLevelAccessLockedTime() { return uniformBucketLevelAccessLockedTime; } + /** Returns the Public Access Prevention. * */ + public PublicAccessPrevention getPublicAccessPrevention() { + return publicAccessPrevention; + } + Bucket.IamConfiguration toPb() { Bucket.IamConfiguration iamConfiguration = new Bucket.IamConfiguration(); @@ -180,6 +229,8 @@ Bucket.IamConfiguration toPb() { : new DateTime(uniformBucketLevelAccessLockedTime)); iamConfiguration.setUniformBucketLevelAccess(uniformBucketLevelAccess); + iamConfiguration.setPublicAccessPrevention( + publicAccessPrevention == null ? null : publicAccessPrevention.getValue()); return iamConfiguration; } @@ -188,10 +239,17 @@ static IamConfiguration fromPb(Bucket.IamConfiguration iamConfiguration) { Bucket.IamConfiguration.UniformBucketLevelAccess uniformBucketLevelAccess = iamConfiguration.getUniformBucketLevelAccess(); DateTime lockedTime = uniformBucketLevelAccess.getLockedTime(); + String publicAccessPrevention = iamConfiguration.getPublicAccessPrevention(); + + PublicAccessPrevention publicAccessPreventionValue = null; + if (publicAccessPrevention != null) { + publicAccessPreventionValue = PublicAccessPrevention.parse(publicAccessPrevention); + } return newBuilder() .setIsUniformBucketLevelAccessEnabled(uniformBucketLevelAccess.getEnabled()) .setUniformBucketLevelAccessLockedTime(lockedTime == null ? null : lockedTime.getValue()) + .setPublicAccessPrevention(publicAccessPreventionValue) .build(); } @@ -199,6 +257,7 @@ static IamConfiguration fromPb(Bucket.IamConfiguration iamConfiguration) { public static class Builder { private Boolean isUniformBucketLevelAccessEnabled; private Long uniformBucketLevelAccessLockedTime; + private PublicAccessPrevention publicAccessPrevention; /** Deprecated in favor of setIsUniformBucketLevelAccessEnabled(). */ @Deprecated @@ -239,6 +298,18 @@ Builder setUniformBucketLevelAccessLockedTime(Long uniformBucketLevelAccessLocke return this; } + /** + * Sets the bucket's Public Access Prevention configuration. Currently supported options are + * {@link PublicAccessPrevention#UNSPECIFIED} or {@link PublicAccessPrevention#ENFORCED} + * + * @see public-access-prevention + */ + public Builder setPublicAccessPrevention(PublicAccessPrevention publicAccessPrevention) { + this.publicAccessPrevention = publicAccessPrevention; + return this; + } + /** Builds an {@code IamConfiguration} object */ public IamConfiguration build() { return new IamConfiguration(this); diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketInfoTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketInfoTest.java index d72901c17..c74625a15 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketInfoTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketInfoTest.java @@ -18,10 +18,13 @@ import static com.google.cloud.storage.Acl.Project.ProjectRole.VIEWERS; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import com.google.api.client.json.JsonGenerator; +import com.google.api.client.json.jackson2.JacksonFactory; import com.google.api.client.util.DateTime; import com.google.api.services.storage.model.Bucket; import com.google.api.services.storage.model.Bucket.Lifecycle; @@ -33,14 +36,18 @@ import com.google.cloud.storage.BucketInfo.CreatedBeforeDeleteRule; import com.google.cloud.storage.BucketInfo.DeleteRule; import com.google.cloud.storage.BucketInfo.DeleteRule.Type; +import com.google.cloud.storage.BucketInfo.IamConfiguration; import com.google.cloud.storage.BucketInfo.IsLiveDeleteRule; import com.google.cloud.storage.BucketInfo.LifecycleRule; import com.google.cloud.storage.BucketInfo.LifecycleRule.LifecycleAction; import com.google.cloud.storage.BucketInfo.LifecycleRule.LifecycleCondition; import com.google.cloud.storage.BucketInfo.NumNewerVersionsDeleteRule; +import com.google.cloud.storage.BucketInfo.PublicAccessPrevention; import com.google.cloud.storage.BucketInfo.RawDeleteRule; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import java.io.IOException; +import java.io.StringWriter; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -79,6 +86,7 @@ public class BucketInfoTest { BucketInfo.IamConfiguration.newBuilder() .setIsUniformBucketLevelAccessEnabled(true) .setUniformBucketLevelAccessLockedTime(System.currentTimeMillis()) + .setPublicAccessPrevention(BucketInfo.PublicAccessPrevention.ENFORCED) .build(); private static final BucketInfo.Logging LOGGING = BucketInfo.Logging.newBuilder() @@ -363,11 +371,45 @@ public void testIamConfiguration() { BucketInfo.IamConfiguration.newBuilder() .setIsUniformBucketLevelAccessEnabled(true) .setUniformBucketLevelAccessLockedTime(System.currentTimeMillis()) + .setPublicAccessPrevention(BucketInfo.PublicAccessPrevention.ENFORCED) .build() .toPb(); assertEquals(Boolean.TRUE, iamConfiguration.getUniformBucketLevelAccess().getEnabled()); assertNotNull(iamConfiguration.getUniformBucketLevelAccess().getLockedTime()); + assertEquals( + BucketInfo.PublicAccessPrevention.ENFORCED.getValue(), + iamConfiguration.getPublicAccessPrevention()); + } + + @Test + public void testPublicAccessPrevention_ensureAbsentWhenUnknown() throws IOException { + StringWriter stringWriter = new StringWriter(); + JsonGenerator jsonGenerator = + JacksonFactory.getDefaultInstance().createJsonGenerator(stringWriter); + + jsonGenerator.serialize( + BucketInfo.IamConfiguration.newBuilder() + .setIsUniformBucketLevelAccessEnabled(true) + .setUniformBucketLevelAccessLockedTime(System.currentTimeMillis()) + .setPublicAccessPrevention(PublicAccessPrevention.UNKNOWN) + .build() + .toPb()); + jsonGenerator.flush(); + + assertFalse(stringWriter.getBuffer().toString().contains("publicAccessPrevention")); + } + + @Test + public void testPapValueOfIamConfiguration() { + Bucket.IamConfiguration iamConfiguration = new Bucket.IamConfiguration(); + Bucket.IamConfiguration.UniformBucketLevelAccess uniformBucketLevelAccess = + new Bucket.IamConfiguration.UniformBucketLevelAccess(); + iamConfiguration.setUniformBucketLevelAccess(uniformBucketLevelAccess); + iamConfiguration.setPublicAccessPrevention("random-string"); + IamConfiguration fromPb = IamConfiguration.fromPb(iamConfiguration); + + assertEquals(PublicAccessPrevention.UNKNOWN, fromPb.getPublicAccessPrevention()); } @Test diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java index e2e282891..305580204 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java @@ -3271,6 +3271,151 @@ public void testEnableAndDisableUniformBucketLevelAccessOnExistingBucket() throw } } + private Bucket generatePublicAccessPreventionBucket(String bucketName, boolean enforced) { + return storage.create( + Bucket.newBuilder(bucketName) + .setIamConfiguration( + BucketInfo.IamConfiguration.newBuilder() + .setPublicAccessPrevention( + enforced + ? BucketInfo.PublicAccessPrevention.ENFORCED + : BucketInfo.PublicAccessPrevention.UNSPECIFIED) + .build()) + .build()); + } + + @Test + public void testEnforcedPublicAccessPreventionOnBucket() throws Exception { + String papBucket = RemoteStorageHelper.generateBucketName(); + try { + Bucket bucket = generatePublicAccessPreventionBucket(papBucket, true); + // Making bucket public should fail. + try { + storage.setIamPolicy( + papBucket, + Policy.newBuilder() + .setVersion(3) + .setBindings( + ImmutableList.of( + com.google.cloud.Binding.newBuilder() + .setRole("roles/storage.objectViewer") + .addMembers("allUsers") + .build())) + .build()); + fail("pap: expected adding allUsers policy to bucket should fail"); + } catch (StorageException storageException) { + // Creating a bucket with roles/storage.objectViewer is not + // allowed when publicAccessPrevention is enabled. + assertEquals(storageException.getCode(), 412); + } + + // Making object public via ACL should fail. + try { + // Create a public object + bucket.create( + "pap-test-object", + "".getBytes(), + Bucket.BlobTargetOption.predefinedAcl(Storage.PredefinedAcl.PUBLIC_READ)); + fail("pap: expected adding allUsers ACL to object should fail"); + } catch (StorageException storageException) { + // Creating an object with allUsers roles/storage.viewer permission + // is not allowed. When Public Access Prevention is enabled. + assertEquals(storageException.getCode(), 412); + } + } finally { + RemoteStorageHelper.forceDelete(storage, papBucket, 1, TimeUnit.MINUTES); + } + } + + @Test + public void testUnspecifiedPublicAccessPreventionOnBucket() throws Exception { + String papBucket = RemoteStorageHelper.generateBucketName(); + try { + Bucket bucket = generatePublicAccessPreventionBucket(papBucket, false); + + // Now, making object public or making bucket public should succeed. + try { + // Create a public object + bucket.create( + "pap-test-object", + "".getBytes(), + Bucket.BlobTargetOption.predefinedAcl(Storage.PredefinedAcl.PUBLIC_READ)); + } catch (StorageException storageException) { + fail("pap: expected adding allUsers ACL to object to succeed"); + } + + // Now, making bucket public should succeed. + try { + storage.setIamPolicy( + papBucket, + Policy.newBuilder() + .setVersion(3) + .setBindings( + ImmutableList.of( + com.google.cloud.Binding.newBuilder() + .setRole("roles/storage.objectViewer") + .addMembers("allUsers") + .build())) + .build()); + } catch (StorageException storageException) { + fail("pap: expected adding allUsers policy to bucket to succeed"); + } + } finally { + RemoteStorageHelper.forceDelete(storage, papBucket, 1, TimeUnit.MINUTES); + } + } + + @Test + public void testUBLAWithPublicAccessPreventionOnBucket() throws Exception { + String papBucket = RemoteStorageHelper.generateBucketName(); + try { + Bucket bucket = generatePublicAccessPreventionBucket(papBucket, false); + assertEquals( + bucket.getIamConfiguration().getPublicAccessPrevention(), + BucketInfo.PublicAccessPrevention.UNSPECIFIED); + assertFalse(bucket.getIamConfiguration().isUniformBucketLevelAccessEnabled()); + assertFalse(bucket.getIamConfiguration().isBucketPolicyOnlyEnabled()); + + // Update PAP setting to ENFORCED and should not affect UBLA setting. + bucket + .toBuilder() + .setIamConfiguration( + bucket + .getIamConfiguration() + .toBuilder() + .setPublicAccessPrevention(BucketInfo.PublicAccessPrevention.ENFORCED) + .build()) + .build() + .update(); + bucket = storage.get(papBucket, Storage.BucketGetOption.fields(BucketField.IAMCONFIGURATION)); + assertEquals( + bucket.getIamConfiguration().getPublicAccessPrevention(), + BucketInfo.PublicAccessPrevention.ENFORCED); + assertFalse(bucket.getIamConfiguration().isUniformBucketLevelAccessEnabled()); + assertFalse(bucket.getIamConfiguration().isBucketPolicyOnlyEnabled()); + + // Updating UBLA should not affect PAP setting. + bucket = + bucket + .toBuilder() + .setIamConfiguration( + bucket + .getIamConfiguration() + .toBuilder() + .setIsUniformBucketLevelAccessEnabled(true) + .build()) + .build() + .update(); + assertTrue(bucket.getIamConfiguration().isUniformBucketLevelAccessEnabled()); + assertTrue(bucket.getIamConfiguration().isBucketPolicyOnlyEnabled()); + assertEquals( + bucket.getIamConfiguration().getPublicAccessPrevention(), + BucketInfo.PublicAccessPrevention.ENFORCED); + } finally { + RemoteStorageHelper.forceDelete(storage, papBucket, 1, TimeUnit.MINUTES); + } + } + @Test public void testUploadUsingSignedURL() throws Exception { String blobName = "test-signed-url-upload";