Skip to content

Commit f5f29bb

Browse files
JesseLovelacekolea2
authored andcommitted
Implement BucketPolicyOnly (#4404)
* implement BucketPolicyOnly * [Storage] Bucket Policy Only samples (#4408) * Humble beginnings for BPO samples with tests * Update samples per client changes * Fix format issue * Lint fix
1 parent 33202fb commit f5f29bb

File tree

10 files changed

+364
-3
lines changed

10 files changed

+364
-3
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,12 @@ public Builder setRetentionPeriod(Long retentionPeriod) {
655655
return this;
656656
}
657657

658+
@Override
659+
public Builder setIamConfiguration(IamConfiguration iamConfiguration) {
660+
infoBuilder.setIamConfiguration(iamConfiguration);
661+
return this;
662+
}
663+
658664
@Override
659665
public Bucket build() {
660666
return new Bucket(storage, infoBuilder);

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

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,118 @@ public com.google.api.services.storage.model.Bucket apply(BucketInfo bucketInfo)
9595
private final Long retentionEffectiveTime;
9696
private final Boolean retentionPolicyIsLocked;
9797
private final Long retentionPeriod;
98+
private final IamConfiguration iamConfiguration;
99+
100+
/**
101+
* The Bucket's IAM Configuration.
102+
*
103+
* @see <a href="https://cloud.google.com/storage/docs/bucket-policy-only">Bucket Policy Only</a>
104+
*/
105+
public static class IamConfiguration implements Serializable {
106+
private static final long serialVersionUID = -8671736104909424616L;
107+
108+
private Boolean isBucketPolicyOnlyEnabled;
109+
private Long bucketPolicyOnlyLockedTime;
110+
111+
@Override
112+
public boolean equals(Object o) {
113+
if (this == o) return true;
114+
if (o == null || getClass() != o.getClass()) {
115+
return false;
116+
}
117+
IamConfiguration other = (IamConfiguration) o;
118+
return Objects.equals(toPb(), other.toPb());
119+
}
120+
121+
@Override
122+
public int hashCode() {
123+
return Objects.hash(isBucketPolicyOnlyEnabled, bucketPolicyOnlyLockedTime);
124+
}
125+
126+
private IamConfiguration(Builder builder) {
127+
this.isBucketPolicyOnlyEnabled = builder.isBucketPolicyOnlyEnabled;
128+
this.bucketPolicyOnlyLockedTime = builder.bucketPolicyOnlyLockedTime;
129+
}
130+
131+
public static Builder newBuilder() {
132+
return new Builder();
133+
}
134+
135+
public Builder toBuilder() {
136+
Builder builder = new Builder();
137+
builder.isBucketPolicyOnlyEnabled = isBucketPolicyOnlyEnabled;
138+
builder.bucketPolicyOnlyLockedTime = bucketPolicyOnlyLockedTime;
139+
return builder;
140+
}
141+
142+
public Boolean isBucketPolicyOnlyEnabled() {
143+
return isBucketPolicyOnlyEnabled;
144+
}
145+
146+
public Long getBucketPolicyOnlyLockedTime() {
147+
return bucketPolicyOnlyLockedTime;
148+
}
149+
150+
Bucket.IamConfiguration toPb() {
151+
Bucket.IamConfiguration iamConfiguration = new Bucket.IamConfiguration();
152+
153+
Bucket.IamConfiguration.BucketPolicyOnly bucketPolicyOnly =
154+
new Bucket.IamConfiguration.BucketPolicyOnly();
155+
bucketPolicyOnly.setEnabled(isBucketPolicyOnlyEnabled);
156+
bucketPolicyOnly.setLockedTime(
157+
bucketPolicyOnlyLockedTime == null ? null : new DateTime(bucketPolicyOnlyLockedTime));
158+
159+
iamConfiguration.setBucketPolicyOnly(bucketPolicyOnly);
160+
161+
return iamConfiguration;
162+
}
163+
164+
static IamConfiguration fromPb(Bucket.IamConfiguration iamConfiguration) {
165+
Bucket.IamConfiguration.BucketPolicyOnly bucketPolicyOnly =
166+
iamConfiguration.getBucketPolicyOnly();
167+
DateTime lockedTime = bucketPolicyOnly.getLockedTime();
168+
169+
return newBuilder()
170+
.setIsBucketPolicyOnlyEnabled(bucketPolicyOnly.getEnabled())
171+
.setBucketPolicyOnlyLockedTime(lockedTime == null ? null : lockedTime.getValue())
172+
.build();
173+
}
174+
175+
/** Builder for {@code IamConfiguration} */
176+
public static class Builder {
177+
private Boolean isBucketPolicyOnlyEnabled;
178+
private Long bucketPolicyOnlyLockedTime;
179+
180+
/**
181+
* Sets whether BucketPolicyOnly is enabled for this bucket. When this is enabled, access to
182+
* the bucket will be configured through IAM, and legacy ACL policies will not work. When this
183+
* is first enabled, {@code bucketPolicyOnly.lockedTime} will be set by the API automatically.
184+
* This field can then be disabled until the time specified, after which it will become
185+
* immutable and calls to change it will fail. If this is enabled, calls to access legacy ACL
186+
* information will fail.
187+
*/
188+
public Builder setIsBucketPolicyOnlyEnabled(Boolean isBucketPolicyOnlyEnabled) {
189+
this.isBucketPolicyOnlyEnabled = isBucketPolicyOnlyEnabled;
190+
return this;
191+
}
192+
193+
/**
194+
* Sets the deadline for switching {@code bucketPolicyOnly.enabled} back to false. After this
195+
* time passes, calls to do so will fail. This is package-private, since in general this field
196+
* should never be set by a user--it's automatically set by the backend when {@code enabled}
197+
* is set to true.
198+
*/
199+
Builder setBucketPolicyOnlyLockedTime(Long bucketPolicyOnlyLockedTime) {
200+
this.bucketPolicyOnlyLockedTime = bucketPolicyOnlyLockedTime;
201+
return this;
202+
}
203+
204+
/** Builds an {@code IamConfiguration} object */
205+
public IamConfiguration build() {
206+
return new IamConfiguration(this);
207+
}
208+
}
209+
}
98210

99211
/**
100212
* Lifecycle rule for a bucket. Allows supported Actions, such as deleting and changing storage
@@ -786,6 +898,15 @@ public abstract static class Builder {
786898
@BetaApi
787899
public abstract Builder setRetentionPeriod(Long retentionPeriod);
788900

901+
/**
902+
* Sets the IamConfiguration to specify whether IAM access should be enabled.
903+
*
904+
* @see <a href="https://cloud.google.com/storage/docs/bucket-policy-only">Bucket Policy
905+
* Only</a>
906+
*/
907+
@BetaApi
908+
public abstract Builder setIamConfiguration(IamConfiguration iamConfiguration);
909+
789910
/** Creates a {@code BucketInfo} object. */
790911
public abstract BucketInfo build();
791912
}
@@ -816,6 +937,7 @@ static final class BuilderImpl extends Builder {
816937
private Long retentionEffectiveTime;
817938
private Boolean retentionPolicyIsLocked;
818939
private Long retentionPeriod;
940+
private IamConfiguration iamConfiguration;
819941

820942
BuilderImpl(String name) {
821943
this.name = name;
@@ -846,6 +968,7 @@ static final class BuilderImpl extends Builder {
846968
retentionEffectiveTime = bucketInfo.retentionEffectiveTime;
847969
retentionPolicyIsLocked = bucketInfo.retentionPolicyIsLocked;
848970
retentionPeriod = bucketInfo.retentionPeriod;
971+
iamConfiguration = bucketInfo.iamConfiguration;
849972
}
850973

851974
@Override
@@ -998,6 +1121,12 @@ public Builder setRetentionPeriod(Long retentionPeriod) {
9981121
return this;
9991122
}
10001123

1124+
@Override
1125+
public Builder setIamConfiguration(IamConfiguration iamConfiguration) {
1126+
this.iamConfiguration = iamConfiguration;
1127+
return this;
1128+
}
1129+
10011130
@Override
10021131
public BucketInfo build() {
10031132
checkNotNull(name);
@@ -1030,6 +1159,7 @@ public BucketInfo build() {
10301159
retentionEffectiveTime = builder.retentionEffectiveTime;
10311160
retentionPolicyIsLocked = builder.retentionPolicyIsLocked;
10321161
retentionPeriod = builder.retentionPeriod;
1162+
iamConfiguration = builder.iamConfiguration;
10331163
}
10341164

10351165
/** Returns the service-generated id for the bucket. */
@@ -1268,6 +1398,12 @@ public Long getRetentionPeriod() {
12681398
return retentionPeriod;
12691399
}
12701400

1401+
/** Returns the IAM configuration */
1402+
@BetaApi
1403+
public IamConfiguration getIamConfiguration() {
1404+
return iamConfiguration;
1405+
}
1406+
12711407
/** Returns a builder for the current bucket. */
12721408
public Builder toBuilder() {
12731409
return new BuilderImpl(this);
@@ -1405,6 +1541,9 @@ public Rule apply(LifecycleRule lifecycleRule) {
14051541
bucketPb.setRetentionPolicy(retentionPolicy);
14061542
}
14071543
}
1544+
if (iamConfiguration != null) {
1545+
bucketPb.setIamConfiguration(iamConfiguration.toPb());
1546+
}
14081547

14091548
return bucketPb;
14101549
}
@@ -1526,6 +1665,10 @@ public DeleteRule apply(Rule rule) {
15261665
builder.setRetentionPeriod(retentionPolicy.getRetentionPeriod());
15271666
}
15281667
}
1668+
Bucket.IamConfiguration iamConfiguration = bucketPb.getIamConfiguration();
1669+
if (iamConfiguration != null) {
1670+
builder.setIamConfiguration(IamConfiguration.fromPb(iamConfiguration));
1671+
}
15291672
return builder.build();
15301673
}
15311674
}

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ enum BucketField implements FieldSelector {
9696
ENCRYPTION("encryption"),
9797
BILLING("billing"),
9898
DEFAULT_EVENT_BASED_HOLD("defaultEventBasedHold"),
99-
RETENTION_POLICY("retentionPolicy");
99+
RETENTION_POLICY("retentionPolicy"),
100+
IAMCONFIGURATION("iamConfiguration");
100101

101102
static final List<? extends FieldSelector> REQUIRED_FIELDS = ImmutableList.of(NAME);
102103

@@ -204,6 +205,18 @@ public static BucketTargetOption metagenerationNotMatch() {
204205
public static BucketTargetOption userProject(String userProject) {
205206
return new BucketTargetOption(StorageRpc.Option.USER_PROJECT, userProject);
206207
}
208+
209+
/**
210+
* Returns an option to define the projection in the API request. In some cases this option may
211+
* be needed to be set to `noAcl` to omit ACL data from the response. The default value is
212+
* `full`
213+
*
214+
* @see <a href="https://cloud.google.com/storage/docs/json_api/v1/buckets/patch">Buckets:
215+
* patch</a>
216+
*/
217+
public static BucketTargetOption projection(String projection) {
218+
return new BucketTargetOption(StorageRpc.Option.PROJECTION, projection);
219+
}
207220
}
208221

209222
/** Class for specifying bucket source options. */

google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585

8686
public class HttpStorageRpc implements StorageRpc {
8787
public static final String DEFAULT_PROJECTION = "full";
88+
public static final String NO_ACL_PROJECTION = "noAcl";
8889
private static final String ENCRYPTION_KEY_PREFIX = "x-goog-encryption-";
8990
private static final String SOURCE_ENCRYPTION_KEY_PREFIX = "x-goog-copy-source-encryption-";
9091

@@ -450,10 +451,24 @@ public Bucket patch(Bucket bucket, Map<Option, ?> options) {
450451
Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_PATCH_BUCKET);
451452
Scope scope = tracer.withSpan(span);
452453
try {
454+
String projection = Option.PROJECTION.getString(options);
455+
456+
if (bucket.getIamConfiguration() != null
457+
&& bucket.getIamConfiguration().getBucketPolicyOnly() != null
458+
&& bucket.getIamConfiguration().getBucketPolicyOnly().getEnabled()) {
459+
// If BucketPolicyOnly is enabled, patch calls will fail if ACL information is included in
460+
// the request
461+
bucket.setDefaultObjectAcl(null);
462+
bucket.setAcl(null);
463+
464+
if (projection == null) {
465+
projection = NO_ACL_PROJECTION;
466+
}
467+
}
453468
return storage
454469
.buckets()
455470
.patch(bucket.getName(), bucket)
456-
.setProjection(DEFAULT_PROJECTION)
471+
.setProjection(projection == null ? DEFAULT_PROJECTION : projection)
457472
.setPredefinedAcl(Option.PREDEFINED_ACL.getString(options))
458473
.setPredefinedDefaultObjectAcl(Option.PREDEFINED_DEFAULT_OBJECT_ACL.getString(options))
459474
.setIfMetagenerationMatch(Option.IF_METAGENERATION_MATCH.getLong(options))

google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ enum Option {
5050
IF_SOURCE_GENERATION_NOT_MATCH("ifSourceGenerationNotMatch"),
5151
IF_DISABLE_GZIP_CONTENT("disableGzipContent"),
5252
PREFIX("prefix"),
53+
PROJECTION("projection"),
5354
MAX_RESULTS("maxResults"),
5455
PAGE_TOKEN("pageToken"),
5556
DELIMITER("delimiter"),

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
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.assertNotNull;
2122
import static org.junit.Assert.assertTrue;
2223

24+
import com.google.api.services.storage.model.Bucket;
2325
import com.google.api.services.storage.model.Bucket.Lifecycle.Rule;
2426
import com.google.cloud.storage.Acl.Project;
2527
import com.google.cloud.storage.Acl.Role;
@@ -64,6 +66,11 @@ public class BucketInfoTest {
6466
LifecycleAction.newDeleteAction(),
6567
LifecycleCondition.newBuilder().setAge(5).build()));
6668
private static final String INDEX_PAGE = "index.html";
69+
private static final BucketInfo.IamConfiguration IAM_CONFIGURATION =
70+
BucketInfo.IamConfiguration.newBuilder()
71+
.setIsBucketPolicyOnlyEnabled(true)
72+
.setBucketPolicyOnlyLockedTime(System.currentTimeMillis())
73+
.build();
6774
private static final String NOT_FOUND_PAGE = "error.html";
6875
private static final String LOCATION = "ASIA";
6976
private static final StorageClass STORAGE_CLASS = StorageClass.STANDARD;
@@ -90,6 +97,7 @@ public class BucketInfoTest {
9097
.setDeleteRules(DELETE_RULES)
9198
.setLifecycleRules(LIFECYCLE_RULES)
9299
.setIndexPage(INDEX_PAGE)
100+
.setIamConfiguration(IAM_CONFIGURATION)
93101
.setNotFoundPage(NOT_FOUND_PAGE)
94102
.setLocation(LOCATION)
95103
.setStorageClass(STORAGE_CLASS)
@@ -139,6 +147,7 @@ public void testBuilder() {
139147
assertEquals(DEFAULT_ACL, BUCKET_INFO.getDefaultAcl());
140148
assertEquals(DELETE_RULES, BUCKET_INFO.getDeleteRules());
141149
assertEquals(INDEX_PAGE, BUCKET_INFO.getIndexPage());
150+
assertEquals(IAM_CONFIGURATION, BUCKET_INFO.getIamConfiguration());
142151
assertEquals(NOT_FOUND_PAGE, BUCKET_INFO.getNotFoundPage());
143152
assertEquals(LOCATION, BUCKET_INFO.getLocation());
144153
assertEquals(STORAGE_CLASS, BUCKET_INFO.getStorageClass());
@@ -174,6 +183,7 @@ private void compareBuckets(BucketInfo expected, BucketInfo value) {
174183
assertEquals(expected.getDeleteRules(), value.getDeleteRules());
175184
assertEquals(expected.getLifecycleRules(), value.getLifecycleRules());
176185
assertEquals(expected.getIndexPage(), value.getIndexPage());
186+
assertEquals(expected.getIamConfiguration(), value.getIamConfiguration());
177187
assertEquals(expected.getNotFoundPage(), value.getNotFoundPage());
178188
assertEquals(expected.getLocation(), value.getLocation());
179189
assertEquals(expected.getStorageClass(), value.getStorageClass());
@@ -244,4 +254,17 @@ public void testLifecycleRules() {
244254
assertTrue(setStorageClassLifecycleRule.getCondition().getIsLive());
245255
assertEquals(10, setStorageClassLifecycleRule.getCondition().getNumNewerVersions().intValue());
246256
}
257+
258+
@Test
259+
public void testIamConfiguration() {
260+
Bucket.IamConfiguration iamConfiguration =
261+
BucketInfo.IamConfiguration.newBuilder()
262+
.setIsBucketPolicyOnlyEnabled(true)
263+
.setBucketPolicyOnlyLockedTime(System.currentTimeMillis())
264+
.build()
265+
.toPb();
266+
267+
assertEquals(Boolean.TRUE, iamConfiguration.getBucketPolicyOnly().getEnabled());
268+
assertNotNull(iamConfiguration.getBucketPolicyOnly().getLockedTime());
269+
}
247270
}

0 commit comments

Comments
 (0)