Skip to content

Commit 3d1e482

Browse files
athakorBenWhiteheadFrank Natividadfrankyn
authored
feat: add support of public access prevention (#636)
Co-authored-by: BenWhitehead <[email protected]> Co-authored-by: Frank Natividad <[email protected]> Co-authored-by: Frank Natividad <[email protected]>
1 parent 99138a4 commit 3d1e482

File tree

3 files changed

+261
-3
lines changed

3 files changed

+261
-3
lines changed

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

+74-3
Original file line numberDiff line numberDiff line change
@@ -105,17 +105,56 @@ public com.google.api.services.storage.model.Bucket apply(BucketInfo bucketInfo)
105105

106106
private static final Logger log = Logger.getLogger(BucketInfo.class.getName());
107107

108+
/**
109+
* Public Access Prevention enum with expected values.
110+
*
111+
* @see <a
112+
* href="https://cloud.google.com/storage/docs/public-access-prevention">public-access-prevention</a>
113+
*/
114+
public enum PublicAccessPrevention {
115+
ENFORCED("enforced"),
116+
/** Default value for Public Access Prevention */
117+
UNSPECIFIED("unspecified"),
118+
/**
119+
* If the api returns a value that isn't defined in {@link PublicAccessPrevention} this value
120+
* will be returned.
121+
*/
122+
UNKNOWN(null);
123+
124+
private final String value;
125+
126+
PublicAccessPrevention(String value) {
127+
this.value = value;
128+
}
129+
130+
public String getValue() {
131+
return value;
132+
}
133+
134+
public static PublicAccessPrevention parse(String value) {
135+
String upper = value.toUpperCase();
136+
try {
137+
return valueOf(upper);
138+
} catch (IllegalArgumentException ignore) {
139+
return UNKNOWN;
140+
}
141+
}
142+
}
143+
108144
/**
109145
* The Bucket's IAM Configuration.
110146
*
111147
* @see <a href="https://cloud.google.com/storage/docs/uniform-bucket-level-access">uniform
112148
* bucket-level access</a>
149+
* @see <a
150+
* href="https://cloud.google.com/storage/docs/public-access-prevention">public-access-prevention</a>
113151
*/
114152
public static class IamConfiguration implements Serializable {
115153
private static final long serialVersionUID = -8671736104909424616L;
116154

117-
private Boolean isUniformBucketLevelAccessEnabled;
118-
private Long uniformBucketLevelAccessLockedTime;
155+
private final Boolean isUniformBucketLevelAccessEnabled;
156+
private final Long uniformBucketLevelAccessLockedTime;
157+
private final PublicAccessPrevention publicAccessPrevention;
119158

120159
@Override
121160
public boolean equals(Object o) {
@@ -129,12 +168,16 @@ public boolean equals(Object o) {
129168

130169
@Override
131170
public int hashCode() {
132-
return Objects.hash(isUniformBucketLevelAccessEnabled, uniformBucketLevelAccessLockedTime);
171+
return Objects.hash(
172+
isUniformBucketLevelAccessEnabled,
173+
uniformBucketLevelAccessLockedTime,
174+
publicAccessPrevention);
133175
}
134176

135177
private IamConfiguration(Builder builder) {
136178
this.isUniformBucketLevelAccessEnabled = builder.isUniformBucketLevelAccessEnabled;
137179
this.uniformBucketLevelAccessLockedTime = builder.uniformBucketLevelAccessLockedTime;
180+
this.publicAccessPrevention = builder.publicAccessPrevention;
138181
}
139182

140183
public static Builder newBuilder() {
@@ -145,6 +188,7 @@ public Builder toBuilder() {
145188
Builder builder = new Builder();
146189
builder.isUniformBucketLevelAccessEnabled = isUniformBucketLevelAccessEnabled;
147190
builder.uniformBucketLevelAccessLockedTime = uniformBucketLevelAccessLockedTime;
191+
builder.publicAccessPrevention = publicAccessPrevention;
148192
return builder;
149193
}
150194

@@ -168,6 +212,11 @@ public Long getUniformBucketLevelAccessLockedTime() {
168212
return uniformBucketLevelAccessLockedTime;
169213
}
170214

215+
/** Returns the Public Access Prevention. * */
216+
public PublicAccessPrevention getPublicAccessPrevention() {
217+
return publicAccessPrevention;
218+
}
219+
171220
Bucket.IamConfiguration toPb() {
172221
Bucket.IamConfiguration iamConfiguration = new Bucket.IamConfiguration();
173222

@@ -180,6 +229,8 @@ Bucket.IamConfiguration toPb() {
180229
: new DateTime(uniformBucketLevelAccessLockedTime));
181230

182231
iamConfiguration.setUniformBucketLevelAccess(uniformBucketLevelAccess);
232+
iamConfiguration.setPublicAccessPrevention(
233+
publicAccessPrevention == null ? null : publicAccessPrevention.getValue());
183234

184235
return iamConfiguration;
185236
}
@@ -188,17 +239,25 @@ static IamConfiguration fromPb(Bucket.IamConfiguration iamConfiguration) {
188239
Bucket.IamConfiguration.UniformBucketLevelAccess uniformBucketLevelAccess =
189240
iamConfiguration.getUniformBucketLevelAccess();
190241
DateTime lockedTime = uniformBucketLevelAccess.getLockedTime();
242+
String publicAccessPrevention = iamConfiguration.getPublicAccessPrevention();
243+
244+
PublicAccessPrevention publicAccessPreventionValue = null;
245+
if (publicAccessPrevention != null) {
246+
publicAccessPreventionValue = PublicAccessPrevention.parse(publicAccessPrevention);
247+
}
191248

192249
return newBuilder()
193250
.setIsUniformBucketLevelAccessEnabled(uniformBucketLevelAccess.getEnabled())
194251
.setUniformBucketLevelAccessLockedTime(lockedTime == null ? null : lockedTime.getValue())
252+
.setPublicAccessPrevention(publicAccessPreventionValue)
195253
.build();
196254
}
197255

198256
/** Builder for {@code IamConfiguration} */
199257
public static class Builder {
200258
private Boolean isUniformBucketLevelAccessEnabled;
201259
private Long uniformBucketLevelAccessLockedTime;
260+
private PublicAccessPrevention publicAccessPrevention;
202261

203262
/** Deprecated in favor of setIsUniformBucketLevelAccessEnabled(). */
204263
@Deprecated
@@ -239,6 +298,18 @@ Builder setUniformBucketLevelAccessLockedTime(Long uniformBucketLevelAccessLocke
239298
return this;
240299
}
241300

301+
/**
302+
* Sets the bucket's Public Access Prevention configuration. Currently supported options are
303+
* {@link PublicAccessPrevention#UNSPECIFIED} or {@link PublicAccessPrevention#ENFORCED}
304+
*
305+
* @see <a
306+
* href="https://cloud.google.com/storage/docs/public-access-prevention">public-access-prevention</a>
307+
*/
308+
public Builder setPublicAccessPrevention(PublicAccessPrevention publicAccessPrevention) {
309+
this.publicAccessPrevention = publicAccessPrevention;
310+
return this;
311+
}
312+
242313
/** Builds an {@code IamConfiguration} object */
243314
public IamConfiguration build() {
244315
return new IamConfiguration(this);

google-cloud-storage/src/test/java/com/google/cloud/storage/BucketInfoTest.java

+42
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,13 @@
1818

1919
import static com.google.cloud.storage.Acl.Project.ProjectRole.VIEWERS;
2020
import static org.junit.Assert.assertEquals;
21+
import static org.junit.Assert.assertFalse;
2122
import static org.junit.Assert.assertNotNull;
2223
import static org.junit.Assert.assertNull;
2324
import static org.junit.Assert.assertTrue;
2425

26+
import com.google.api.client.json.JsonGenerator;
27+
import com.google.api.client.json.jackson2.JacksonFactory;
2528
import com.google.api.client.util.DateTime;
2629
import com.google.api.services.storage.model.Bucket;
2730
import com.google.api.services.storage.model.Bucket.Lifecycle;
@@ -33,14 +36,18 @@
3336
import com.google.cloud.storage.BucketInfo.CreatedBeforeDeleteRule;
3437
import com.google.cloud.storage.BucketInfo.DeleteRule;
3538
import com.google.cloud.storage.BucketInfo.DeleteRule.Type;
39+
import com.google.cloud.storage.BucketInfo.IamConfiguration;
3640
import com.google.cloud.storage.BucketInfo.IsLiveDeleteRule;
3741
import com.google.cloud.storage.BucketInfo.LifecycleRule;
3842
import com.google.cloud.storage.BucketInfo.LifecycleRule.LifecycleAction;
3943
import com.google.cloud.storage.BucketInfo.LifecycleRule.LifecycleCondition;
4044
import com.google.cloud.storage.BucketInfo.NumNewerVersionsDeleteRule;
45+
import com.google.cloud.storage.BucketInfo.PublicAccessPrevention;
4146
import com.google.cloud.storage.BucketInfo.RawDeleteRule;
4247
import com.google.common.collect.ImmutableList;
4348
import com.google.common.collect.ImmutableMap;
49+
import java.io.IOException;
50+
import java.io.StringWriter;
4451
import java.util.Arrays;
4552
import java.util.Collections;
4653
import java.util.HashMap;
@@ -79,6 +86,7 @@ public class BucketInfoTest {
7986
BucketInfo.IamConfiguration.newBuilder()
8087
.setIsUniformBucketLevelAccessEnabled(true)
8188
.setUniformBucketLevelAccessLockedTime(System.currentTimeMillis())
89+
.setPublicAccessPrevention(BucketInfo.PublicAccessPrevention.ENFORCED)
8290
.build();
8391
private static final BucketInfo.Logging LOGGING =
8492
BucketInfo.Logging.newBuilder()
@@ -363,11 +371,45 @@ public void testIamConfiguration() {
363371
BucketInfo.IamConfiguration.newBuilder()
364372
.setIsUniformBucketLevelAccessEnabled(true)
365373
.setUniformBucketLevelAccessLockedTime(System.currentTimeMillis())
374+
.setPublicAccessPrevention(BucketInfo.PublicAccessPrevention.ENFORCED)
366375
.build()
367376
.toPb();
368377

369378
assertEquals(Boolean.TRUE, iamConfiguration.getUniformBucketLevelAccess().getEnabled());
370379
assertNotNull(iamConfiguration.getUniformBucketLevelAccess().getLockedTime());
380+
assertEquals(
381+
BucketInfo.PublicAccessPrevention.ENFORCED.getValue(),
382+
iamConfiguration.getPublicAccessPrevention());
383+
}
384+
385+
@Test
386+
public void testPublicAccessPrevention_ensureAbsentWhenUnknown() throws IOException {
387+
StringWriter stringWriter = new StringWriter();
388+
JsonGenerator jsonGenerator =
389+
JacksonFactory.getDefaultInstance().createJsonGenerator(stringWriter);
390+
391+
jsonGenerator.serialize(
392+
BucketInfo.IamConfiguration.newBuilder()
393+
.setIsUniformBucketLevelAccessEnabled(true)
394+
.setUniformBucketLevelAccessLockedTime(System.currentTimeMillis())
395+
.setPublicAccessPrevention(PublicAccessPrevention.UNKNOWN)
396+
.build()
397+
.toPb());
398+
jsonGenerator.flush();
399+
400+
assertFalse(stringWriter.getBuffer().toString().contains("publicAccessPrevention"));
401+
}
402+
403+
@Test
404+
public void testPapValueOfIamConfiguration() {
405+
Bucket.IamConfiguration iamConfiguration = new Bucket.IamConfiguration();
406+
Bucket.IamConfiguration.UniformBucketLevelAccess uniformBucketLevelAccess =
407+
new Bucket.IamConfiguration.UniformBucketLevelAccess();
408+
iamConfiguration.setUniformBucketLevelAccess(uniformBucketLevelAccess);
409+
iamConfiguration.setPublicAccessPrevention("random-string");
410+
IamConfiguration fromPb = IamConfiguration.fromPb(iamConfiguration);
411+
412+
assertEquals(PublicAccessPrevention.UNKNOWN, fromPb.getPublicAccessPrevention());
371413
}
372414

373415
@Test

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

+145
Original file line numberDiff line numberDiff line change
@@ -3271,6 +3271,151 @@ public void testEnableAndDisableUniformBucketLevelAccessOnExistingBucket() throw
32713271
}
32723272
}
32733273

3274+
private Bucket generatePublicAccessPreventionBucket(String bucketName, boolean enforced) {
3275+
return storage.create(
3276+
Bucket.newBuilder(bucketName)
3277+
.setIamConfiguration(
3278+
BucketInfo.IamConfiguration.newBuilder()
3279+
.setPublicAccessPrevention(
3280+
enforced
3281+
? BucketInfo.PublicAccessPrevention.ENFORCED
3282+
: BucketInfo.PublicAccessPrevention.UNSPECIFIED)
3283+
.build())
3284+
.build());
3285+
}
3286+
3287+
@Test
3288+
public void testEnforcedPublicAccessPreventionOnBucket() throws Exception {
3289+
String papBucket = RemoteStorageHelper.generateBucketName();
3290+
try {
3291+
Bucket bucket = generatePublicAccessPreventionBucket(papBucket, true);
3292+
// Making bucket public should fail.
3293+
try {
3294+
storage.setIamPolicy(
3295+
papBucket,
3296+
Policy.newBuilder()
3297+
.setVersion(3)
3298+
.setBindings(
3299+
ImmutableList.<com.google.cloud.Binding>of(
3300+
com.google.cloud.Binding.newBuilder()
3301+
.setRole("roles/storage.objectViewer")
3302+
.addMembers("allUsers")
3303+
.build()))
3304+
.build());
3305+
fail("pap: expected adding allUsers policy to bucket should fail");
3306+
} catch (StorageException storageException) {
3307+
// Creating a bucket with roles/storage.objectViewer is not
3308+
// allowed when publicAccessPrevention is enabled.
3309+
assertEquals(storageException.getCode(), 412);
3310+
}
3311+
3312+
// Making object public via ACL should fail.
3313+
try {
3314+
// Create a public object
3315+
bucket.create(
3316+
"pap-test-object",
3317+
"".getBytes(),
3318+
Bucket.BlobTargetOption.predefinedAcl(Storage.PredefinedAcl.PUBLIC_READ));
3319+
fail("pap: expected adding allUsers ACL to object should fail");
3320+
} catch (StorageException storageException) {
3321+
// Creating an object with allUsers roles/storage.viewer permission
3322+
// is not allowed. When Public Access Prevention is enabled.
3323+
assertEquals(storageException.getCode(), 412);
3324+
}
3325+
} finally {
3326+
RemoteStorageHelper.forceDelete(storage, papBucket, 1, TimeUnit.MINUTES);
3327+
}
3328+
}
3329+
3330+
@Test
3331+
public void testUnspecifiedPublicAccessPreventionOnBucket() throws Exception {
3332+
String papBucket = RemoteStorageHelper.generateBucketName();
3333+
try {
3334+
Bucket bucket = generatePublicAccessPreventionBucket(papBucket, false);
3335+
3336+
// Now, making object public or making bucket public should succeed.
3337+
try {
3338+
// Create a public object
3339+
bucket.create(
3340+
"pap-test-object",
3341+
"".getBytes(),
3342+
Bucket.BlobTargetOption.predefinedAcl(Storage.PredefinedAcl.PUBLIC_READ));
3343+
} catch (StorageException storageException) {
3344+
fail("pap: expected adding allUsers ACL to object to succeed");
3345+
}
3346+
3347+
// Now, making bucket public should succeed.
3348+
try {
3349+
storage.setIamPolicy(
3350+
papBucket,
3351+
Policy.newBuilder()
3352+
.setVersion(3)
3353+
.setBindings(
3354+
ImmutableList.<com.google.cloud.Binding>of(
3355+
com.google.cloud.Binding.newBuilder()
3356+
.setRole("roles/storage.objectViewer")
3357+
.addMembers("allUsers")
3358+
.build()))
3359+
.build());
3360+
} catch (StorageException storageException) {
3361+
fail("pap: expected adding allUsers policy to bucket to succeed");
3362+
}
3363+
} finally {
3364+
RemoteStorageHelper.forceDelete(storage, papBucket, 1, TimeUnit.MINUTES);
3365+
}
3366+
}
3367+
3368+
@Test
3369+
public void testUBLAWithPublicAccessPreventionOnBucket() throws Exception {
3370+
String papBucket = RemoteStorageHelper.generateBucketName();
3371+
try {
3372+
Bucket bucket = generatePublicAccessPreventionBucket(papBucket, false);
3373+
assertEquals(
3374+
bucket.getIamConfiguration().getPublicAccessPrevention(),
3375+
BucketInfo.PublicAccessPrevention.UNSPECIFIED);
3376+
assertFalse(bucket.getIamConfiguration().isUniformBucketLevelAccessEnabled());
3377+
assertFalse(bucket.getIamConfiguration().isBucketPolicyOnlyEnabled());
3378+
3379+
// Update PAP setting to ENFORCED and should not affect UBLA setting.
3380+
bucket
3381+
.toBuilder()
3382+
.setIamConfiguration(
3383+
bucket
3384+
.getIamConfiguration()
3385+
.toBuilder()
3386+
.setPublicAccessPrevention(BucketInfo.PublicAccessPrevention.ENFORCED)
3387+
.build())
3388+
.build()
3389+
.update();
3390+
bucket = storage.get(papBucket, Storage.BucketGetOption.fields(BucketField.IAMCONFIGURATION));
3391+
assertEquals(
3392+
bucket.getIamConfiguration().getPublicAccessPrevention(),
3393+
BucketInfo.PublicAccessPrevention.ENFORCED);
3394+
assertFalse(bucket.getIamConfiguration().isUniformBucketLevelAccessEnabled());
3395+
assertFalse(bucket.getIamConfiguration().isBucketPolicyOnlyEnabled());
3396+
3397+
// Updating UBLA should not affect PAP setting.
3398+
bucket =
3399+
bucket
3400+
.toBuilder()
3401+
.setIamConfiguration(
3402+
bucket
3403+
.getIamConfiguration()
3404+
.toBuilder()
3405+
.setIsUniformBucketLevelAccessEnabled(true)
3406+
.build())
3407+
.build()
3408+
.update();
3409+
assertTrue(bucket.getIamConfiguration().isUniformBucketLevelAccessEnabled());
3410+
assertTrue(bucket.getIamConfiguration().isBucketPolicyOnlyEnabled());
3411+
assertEquals(
3412+
bucket.getIamConfiguration().getPublicAccessPrevention(),
3413+
BucketInfo.PublicAccessPrevention.ENFORCED);
3414+
} finally {
3415+
RemoteStorageHelper.forceDelete(storage, papBucket, 1, TimeUnit.MINUTES);
3416+
}
3417+
}
3418+
32743419
@Test
32753420
public void testUploadUsingSignedURL() throws Exception {
32763421
String blobName = "test-signed-url-upload";

0 commit comments

Comments
 (0)