Skip to content

Commit 337ca2f

Browse files
Add Identity Access Management (IAM) to the Storage API (#1916)
Adds support for bucket-level IAM (currently in limited alpha). More information about IAM in Google Cloud Storage can be found at https://cloud.google.com/storage/docs/access-control/iam
2 parents 56d0a30 + 04a7df3 commit 337ca2f

12 files changed

Lines changed: 736 additions & 26 deletions

File tree

google-cloud-contrib/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/FakeStorageRpc.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
import com.google.api.services.storage.model.Bucket;
2020
import com.google.api.services.storage.model.BucketAccessControl;
2121
import com.google.api.services.storage.model.ObjectAccessControl;
22+
import com.google.api.services.storage.model.Policy;
2223
import com.google.api.services.storage.model.StorageObject;
24+
import com.google.api.services.storage.model.TestIamPermissionsResponse;
2325
import com.google.cloud.storage.Storage;
2426
import com.google.cloud.storage.StorageException;
2527
import com.google.cloud.storage.spi.v1.RpcBatch;
@@ -61,6 +63,7 @@
6163
* <li>continueRewrite
6264
* <li>createBatch
6365
* <li>checksums, etags
66+
* <li>IAM operations</li>
6467
* </ul>
6568
* </ul>
6669
*/
@@ -443,4 +446,19 @@ private static boolean processedAsFolder(StorageObject so, String delimiter, Str
443446
folders.put(folderName, fakeFolder);
444447
return true;
445448
}
449+
450+
@Override
451+
public Policy getIamPolicy(String bucket) {
452+
throw new UnsupportedOperationException();
453+
}
454+
455+
@Override
456+
public Policy setIamPolicy(String bucket, Policy policy) {
457+
throw new UnsupportedOperationException();
458+
}
459+
460+
@Override
461+
public TestIamPermissionsResponse testIamPermissions(String bucket, List<String> permissions) {
462+
throw new UnsupportedOperationException();
463+
}
446464
}

google-cloud-core/src/main/java/com/google/cloud/Policy.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,8 @@ public static class Builder {
124124
private String etag;
125125
private int version;
126126

127-
protected Builder() {}
127+
protected Builder() {
128+
}
128129

129130
protected Builder(Policy policy) {
130131
setBindings(policy.bindings);
@@ -202,7 +203,6 @@ public final Builder removeIdentity(Role role, Identity first, Identity... other
202203
return this;
203204
}
204205

205-
206206
/**
207207
* Sets the policy's etag.
208208
*
@@ -214,7 +214,7 @@ public final Builder removeIdentity(Role role, Identity first, Identity... other
214214
* applied to the same version of the policy. If no etag is provided in the call to
215215
* setIamPolicy, then the existing policy is overwritten blindly.
216216
*/
217-
protected final Builder setEtag(String etag) {
217+
public final Builder setEtag(String etag) {
218218
this.etag = etag;
219219
return this;
220220
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2017 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.storage;
18+
19+
import com.google.api.services.storage.model.Policy.Bindings;
20+
import com.google.cloud.Identity;
21+
import com.google.cloud.Policy;
22+
import com.google.cloud.Role;
23+
import java.util.ArrayList;
24+
import java.util.List;
25+
import java.util.Map;
26+
import java.util.Set;
27+
28+
/**
29+
* Helper for converting between the Policy model provided by the API and the Policy model provided
30+
* by this library.
31+
*/
32+
class PolicyHelper {
33+
34+
static Policy convertFromApiPolicy(com.google.api.services.storage.model.Policy apiPolicy) {
35+
Policy.Builder policyBuilder = Policy.newBuilder();
36+
for (Bindings binding : apiPolicy.getBindings()) {
37+
for (String member : binding.getMembers()) {
38+
policyBuilder.addIdentity(Role.of(binding.getRole()), Identity.valueOf(member));
39+
}
40+
}
41+
return policyBuilder.setEtag(apiPolicy.getEtag()).build();
42+
}
43+
44+
static com.google.api.services.storage.model.Policy convertToApiPolicy(Policy policy) {
45+
List<Bindings> bindings = new ArrayList<>(policy.getBindings().size());
46+
for (Map.Entry<Role, Set<Identity>> entry : policy.getBindings().entrySet()) {
47+
List<String> members = new ArrayList<>(entry.getValue().size());
48+
for (Identity identity : entry.getValue()) {
49+
members.add(identity.strValue());
50+
}
51+
bindings.add(new Bindings().setMembers(members).setRole(entry.getKey().getValue()));
52+
}
53+
return new com.google.api.services.storage.model.Policy()
54+
.setBindings(bindings)
55+
.setEtag(policy.getEtag());
56+
}
57+
58+
private PolicyHelper() {
59+
// Intentionally left blank.
60+
}
61+
}

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

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import com.google.auth.ServiceAccountSigner.SigningException;
2525
import com.google.cloud.FieldSelector;
2626
import com.google.cloud.FieldSelector.Helper;
27+
import com.google.cloud.GcpLaunchStage;
28+
import com.google.cloud.Policy;
2729
import com.google.cloud.ReadChannel;
2830
import com.google.cloud.Service;
2931
import com.google.cloud.WriteChannel;
@@ -34,7 +36,6 @@
3436
import com.google.common.collect.Iterables;
3537
import com.google.common.collect.Lists;
3638
import com.google.common.io.BaseEncoding;
37-
3839
import java.io.InputStream;
3940
import java.io.Serializable;
4041
import java.net.URL;
@@ -2369,4 +2370,60 @@ public static Builder newBuilder() {
23692370
* @throws StorageException upon failure
23702371
*/
23712372
List<Acl> listAcls(BlobId blob);
2373+
2374+
/**
2375+
* Gets the IAM policy for the provided bucket.
2376+
*
2377+
* <p>Example of getting the IAM policy for a bucket.
2378+
* <pre> {@code
2379+
* String bucketName = "my_unique_bucket";
2380+
* Policy policy = storage.getIamPolicy(bucketName);
2381+
* }</pre>
2382+
*
2383+
* @throws StorageException upon failure
2384+
*/
2385+
@GcpLaunchStage.Alpha
2386+
Policy getIamPolicy(String bucket);
2387+
2388+
/**
2389+
* Updates the IAM policy on the specified bucket.
2390+
*
2391+
* <p>Example of updating the IAM policy on a bucket.
2392+
* <pre>{@code
2393+
* // We want to make all objects in our bucket publicly readable.
2394+
* String bucketName = "my_unique_bucket";
2395+
* Policy currentPolicy = storage.getIamPolicy(bucketName);
2396+
* Policy updatedPolicy =
2397+
* storage.setIamPolicy(
2398+
* bucketName,
2399+
* currentPolicy.toBuilder()
2400+
* .addIdentity(StorageRoles.objectViewer(), Identity.allUsers())
2401+
* .build());
2402+
* }</pre>
2403+
*
2404+
* @throws StorageException upon failure
2405+
*/
2406+
@GcpLaunchStage.Alpha
2407+
Policy setIamPolicy(String bucket, Policy policy);
2408+
2409+
/**
2410+
* Tests whether the caller holds the permissions on the specified bucket. Returns a list of
2411+
* booleans in the same placement and order in which the permissions were specified.
2412+
*
2413+
* <p>Example of testing permissions on a bucket.
2414+
* <pre> {@code
2415+
* String bucketName = "my_unique_bucket";
2416+
* List<Boolean> response =
2417+
* storage.testIamPermissions(
2418+
* bucket,
2419+
* ImmutableList.of("storage.buckets.get", "storage.buckets.getIamPolicy"));
2420+
* for (boolean hasPermission : response) {
2421+
* // Do something with permission test response
2422+
* }
2423+
* }</pre>
2424+
*
2425+
* @throws StorageException upon failure
2426+
*/
2427+
@GcpLaunchStage.Alpha
2428+
List<Boolean> testIamPermissions(String bucket, List<String> permissions);
23722429
}

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

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package com.google.cloud.storage;
1818

1919
import static com.google.cloud.RetryHelper.runWithRetries;
20+
import static com.google.cloud.storage.PolicyHelper.convertFromApiPolicy;
21+
import static com.google.cloud.storage.PolicyHelper.convertToApiPolicy;
2022
import static com.google.cloud.storage.spi.v1.StorageRpc.Option.DELIMITER;
2123
import static com.google.cloud.storage.spi.v1.StorageRpc.Option.IF_GENERATION_MATCH;
2224
import static com.google.cloud.storage.spi.v1.StorageRpc.Option.IF_GENERATION_NOT_MATCH;
@@ -35,11 +37,13 @@
3537
import com.google.api.services.storage.model.BucketAccessControl;
3638
import com.google.api.services.storage.model.ObjectAccessControl;
3739
import com.google.api.services.storage.model.StorageObject;
40+
import com.google.api.services.storage.model.TestIamPermissionsResponse;
3841
import com.google.auth.ServiceAccountSigner;
3942
import com.google.cloud.BaseService;
4043
import com.google.cloud.BatchResult;
4144
import com.google.cloud.PageImpl;
4245
import com.google.cloud.PageImpl.NextPageFetcher;
46+
import com.google.cloud.Policy;
4347
import com.google.cloud.ReadChannel;
4448
import com.google.cloud.RetryHelper.RetryHelperException;
4549
import com.google.cloud.storage.Acl.Entity;
@@ -49,14 +53,14 @@
4953
import com.google.common.base.Function;
5054
import com.google.common.collect.ImmutableList;
5155
import com.google.common.collect.ImmutableMap;
56+
import com.google.common.collect.ImmutableSet;
5257
import com.google.common.collect.Iterables;
5358
import com.google.common.collect.Lists;
5459
import com.google.common.collect.Maps;
5560
import com.google.common.hash.Hashing;
5661
import com.google.common.io.BaseEncoding;
5762
import com.google.common.net.UrlEscapers;
5863
import com.google.common.primitives.Ints;
59-
6064
import java.io.ByteArrayInputStream;
6165
import java.io.InputStream;
6266
import java.io.UnsupportedEncodingException;
@@ -68,6 +72,7 @@
6872
import java.util.EnumMap;
6973
import java.util.List;
7074
import java.util.Map;
75+
import java.util.Set;
7176
import java.util.concurrent.Callable;
7277
import java.util.concurrent.TimeUnit;
7378

@@ -855,6 +860,61 @@ public List<ObjectAccessControl> call() {
855860
}
856861
}
857862

863+
@Override
864+
public Policy getIamPolicy(final String bucket) {
865+
try {
866+
return convertFromApiPolicy(
867+
runWithRetries(new Callable<com.google.api.services.storage.model.Policy>() {
868+
@Override
869+
public com.google.api.services.storage.model.Policy call() {
870+
return storageRpc.getIamPolicy(bucket);
871+
}
872+
}, getOptions().getRetrySettings(), EXCEPTION_HANDLER, getOptions().getClock()));
873+
} catch (RetryHelperException e) {
874+
throw StorageException.translateAndThrow(e);
875+
}
876+
}
877+
878+
@Override
879+
public Policy setIamPolicy(final String bucket, final Policy policy) {
880+
try {
881+
return convertFromApiPolicy(
882+
runWithRetries(new Callable<com.google.api.services.storage.model.Policy>() {
883+
@Override
884+
public com.google.api.services.storage.model.Policy call() {
885+
return storageRpc.setIamPolicy(bucket, convertToApiPolicy(policy));
886+
}
887+
}, getOptions().getRetrySettings(), EXCEPTION_HANDLER, getOptions().getClock()));
888+
} catch (RetryHelperException e) {
889+
throw StorageException.translateAndThrow(e);
890+
}
891+
}
892+
893+
@Override
894+
public List<Boolean> testIamPermissions(final String bucket, final List<String> permissions) {
895+
try {
896+
TestIamPermissionsResponse response = runWithRetries(
897+
new Callable<TestIamPermissionsResponse>() {
898+
@Override
899+
public TestIamPermissionsResponse call() {
900+
return storageRpc.testIamPermissions(bucket, permissions);
901+
}
902+
}, getOptions().getRetrySettings(), EXCEPTION_HANDLER, getOptions().getClock());
903+
final Set<String> heldPermissions =
904+
response.getPermissions() != null
905+
? ImmutableSet.copyOf(response.getPermissions())
906+
: ImmutableSet.<String>of();
907+
return Lists.transform(permissions, new Function<String, Boolean>() {
908+
@Override
909+
public Boolean apply(String permission) {
910+
return heldPermissions.contains(permission);
911+
}
912+
});
913+
} catch (RetryHelperException e) {
914+
throw StorageException.translateAndThrow(e);
915+
}
916+
}
917+
858918
private static <T> void addToOptionMap(StorageRpc.Option option, T defaultValue,
859919
Map<StorageRpc.Option, Object> map) {
860920
addToOptionMap(option, option, defaultValue, map);

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@
4848
import com.google.api.services.storage.model.ComposeRequest.SourceObjects.ObjectPreconditions;
4949
import com.google.api.services.storage.model.ObjectAccessControl;
5050
import com.google.api.services.storage.model.Objects;
51+
import com.google.api.services.storage.model.Policy;
5152
import com.google.api.services.storage.model.StorageObject;
53+
import com.google.api.services.storage.model.TestIamPermissionsResponse;
5254
import com.google.cloud.BaseServiceException;
5355
import com.google.cloud.HttpTransportOptions;
5456
import com.google.cloud.storage.StorageException;
@@ -834,4 +836,31 @@ public List<ObjectAccessControl> listAcls(String bucket, String object, Long gen
834836
throw translate(ex);
835837
}
836838
}
839+
840+
@Override
841+
public Policy getIamPolicy(String bucket) {
842+
try {
843+
return storage.buckets().getIamPolicy(bucket).execute();
844+
} catch (IOException ex) {
845+
throw translate(ex);
846+
}
847+
}
848+
849+
@Override
850+
public Policy setIamPolicy(String bucket, Policy policy) {
851+
try {
852+
return storage.buckets().setIamPolicy(bucket, policy).execute();
853+
} catch (IOException ex) {
854+
throw translate(ex);
855+
}
856+
}
857+
858+
@Override
859+
public TestIamPermissionsResponse testIamPermissions(String bucket, List<String> permissions) {
860+
try {
861+
return storage.buckets().testIamPermissions(bucket, permissions).execute();
862+
} catch (IOException ex) {
863+
throw translate(ex);
864+
}
865+
}
837866
}

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@
1919
import com.google.api.services.storage.model.Bucket;
2020
import com.google.api.services.storage.model.BucketAccessControl;
2121
import com.google.api.services.storage.model.ObjectAccessControl;
22+
import com.google.api.services.storage.model.Policy;
2223
import com.google.api.services.storage.model.StorageObject;
24+
import com.google.api.services.storage.model.TestIamPermissionsResponse;
2325
import com.google.cloud.ServiceRpc;
2426
import com.google.cloud.storage.StorageException;
25-
2627
import java.io.InputStream;
2728
import java.util.List;
2829
import java.util.Map;
@@ -427,4 +428,25 @@ void write(String uploadId, byte[] toWrite, int toWriteOffset, long destOffset,
427428
* @throws StorageException upon failure
428429
*/
429430
List<ObjectAccessControl> listAcls(String bucket, String object, Long generation);
431+
432+
/**
433+
* Returns the IAM policy for the specified bucket.
434+
*
435+
* @throws StorageException upon failure
436+
*/
437+
Policy getIamPolicy(String bucket);
438+
439+
/**
440+
* Updates the IAM policy for the specified bucket.
441+
*
442+
* @throws StorageException upon failure
443+
*/
444+
Policy setIamPolicy(String bucket, Policy policy);
445+
446+
/**
447+
* Tests whether the caller holds the specified permissions for the specified bucket.
448+
*
449+
* @throws StorageException upon failure
450+
*/
451+
TestIamPermissionsResponse testIamPermissions(String bucket, List<String> permissions);
430452
}

0 commit comments

Comments
 (0)