Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit df9de90

Browse filesBrowse files
tseaverlukesneeringer
authored andcommitted
Add IAM methods for buckets. (#3309)
* Add role / permission constants for storage IAM. * Add IAM methods for buckets.
1 parent 1633962 commit df9de90
Copy full SHA for df9de90

File tree

Expand file treeCollapse file tree

4 files changed

+315
-0
lines changed
Filter options
Expand file treeCollapse file tree

4 files changed

+315
-0
lines changed

‎storage/google/cloud/storage/bucket.py

Copy file name to clipboardExpand all lines: storage/google/cloud/storage/bucket.py
+78Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from google.cloud._helpers import _NOW
2727
from google.cloud._helpers import _rfc3339_to_datetime
2828
from google.cloud.exceptions import NotFound
29+
from google.cloud.iam import Policy
2930
from google.cloud.iterator import HTTPIterator
3031
from google.cloud.storage._helpers import _PropertyMixin
3132
from google.cloud.storage._helpers import _scalar_property
@@ -803,6 +804,83 @@ def disable_website(self):
803804
"""
804805
return self.configure_website(None, None)
805806

807+
def get_iam_policy(self, client=None):
808+
"""Retrieve the IAM policy for the bucket.
809+
810+
See:
811+
https://cloud.google.com/storage/docs/json_api/v1/buckets/getIamPolicy
812+
813+
:type client: :class:`~google.cloud.storage.client.Client` or
814+
``NoneType``
815+
:param client: Optional. The client to use. If not passed, falls back
816+
to the ``client`` stored on the current bucket.
817+
818+
:rtype: :class:`google.cloud.iam.Policy`
819+
:returns: the policy instance, based on the resource returned from
820+
the ``getIamPolicy`` API request.
821+
"""
822+
client = self._require_client(client)
823+
info = client._connection.api_request(
824+
method='GET',
825+
path='%s/iam' % (self.path,),
826+
_target_object=None)
827+
return Policy.from_api_repr(info)
828+
829+
def set_iam_policy(self, policy, client=None):
830+
"""Update the IAM policy for the bucket.
831+
832+
See:
833+
https://cloud.google.com/storage/docs/json_api/v1/buckets/setIamPolicy
834+
835+
:type policy: :class:`google.cloud.iam.Policy`
836+
:param policy: policy instance used to update bucket's IAM policy.
837+
838+
:type client: :class:`~google.cloud.storage.client.Client` or
839+
``NoneType``
840+
:param client: Optional. The client to use. If not passed, falls back
841+
to the ``client`` stored on the current bucket.
842+
843+
:rtype: :class:`google.cloud.iam.Policy`
844+
:returns: the policy instance, based on the resource returned from
845+
the ``setIamPolicy`` API request.
846+
"""
847+
client = self._require_client(client)
848+
resource = policy.to_api_repr()
849+
resource['resourceId'] = self.path
850+
info = client._connection.api_request(
851+
method='PUT',
852+
path='%s/iam' % (self.path,),
853+
data=resource,
854+
_target_object=None)
855+
return Policy.from_api_repr(info)
856+
857+
def test_iam_permissions(self, permissions, client=None):
858+
"""API call: test permissions
859+
860+
See:
861+
https://cloud.google.com/storage/docs/json_api/v1/buckets/testIamPermissions
862+
863+
:type permissions: list of string
864+
:param permissions: the permissions to check
865+
866+
:type client: :class:`~google.cloud.storage.client.Client` or
867+
``NoneType``
868+
:param client: Optional. The client to use. If not passed, falls back
869+
to the ``client`` stored on the current bucket.
870+
871+
:rtype: list of string
872+
:returns: the permissions returned by the ``testIamPermissions`` API
873+
request.
874+
"""
875+
client = self._require_client(client)
876+
query = {'permissions': permissions}
877+
path = '%s/iam/testPermissions' % (self.path,)
878+
resp = client._connection.api_request(
879+
method='GET',
880+
path=path,
881+
query_params=query)
882+
return resp.get('permissions', [])
883+
806884
def make_public(self, recursive=False, future=False, client=None):
807885
"""Make a bucket public.
808886

‎storage/google/cloud/storage/iam.py

Copy file name to clipboard
+86Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Copyright 2017 Google Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
"""Storage API IAM policy definitions
15+
16+
For allowed roles / permissions, see:
17+
https://cloud.google.com/storage/docs/access-control/iam
18+
"""
19+
20+
# Storage-specific IAM roles
21+
22+
STORAGE_OBJECT_CREATOR_ROLE = 'roles/storage.objectCreator'
23+
"""Role implying rights to create objects, but not delete or overwrite them."""
24+
25+
STORAGE_OBJECT_VIEWER_ROLE = 'roles/storage.objectViewer'
26+
"""Role implying rights to view object properties, excluding ACLs."""
27+
28+
STORAGE_OBJECT_ADMIN_ROLE = 'roles/storage.objectViewer'
29+
"""Role implying full control of objects."""
30+
31+
STORAGE_ADMIN_ROLE = 'roles/storage.admin'
32+
"""Role implying full control of objects and buckets."""
33+
34+
STORAGE_VIEWER_ROLE = 'Viewer'
35+
"""Can list buckets."""
36+
37+
STORAGE_EDITOR_ROLE = 'Editor'
38+
"""Can create, list, and delete buckets."""
39+
40+
STORAGE_OWNER_ROLE = 'Owners'
41+
"""Can create, list, and delete buckets."""
42+
43+
44+
# Storage-specific permissions
45+
46+
STORAGE_BUCKETS_CREATE = 'storage.buckets.create'
47+
"""Permission: create buckets."""
48+
49+
STORAGE_BUCKETS_DELETE = 'storage.buckets.delete'
50+
"""Permission: delete buckets."""
51+
52+
STORAGE_BUCKETS_GET = 'storage.buckets.get'
53+
"""Permission: read bucket metadata, excluding ACLs."""
54+
55+
STORAGE_BUCKETS_GET_IAM_POLICY = 'storage.buckets.getIamPolicy'
56+
"""Permission: read bucket ACLs."""
57+
58+
STORAGE_BUCKETS_LIST = 'storage.buckets.list'
59+
"""Permission: list buckets."""
60+
61+
STORAGE_BUCKETS_SET_IAM_POLICY = 'storage.buckets.setIamPolicy'
62+
"""Permission: update bucket ACLs."""
63+
64+
STORAGE_BUCKETS_UPDATE = 'storage.buckets.list'
65+
"""Permission: update buckets, excluding ACLS."""
66+
67+
STORAGE_OBJECTS_CREATE = 'storage.objects.create'
68+
"""Permission: add new objects to a bucket."""
69+
70+
STORAGE_OBJECTS_DELETE = 'storage.objects.delete'
71+
"""Permission: delete objects."""
72+
73+
STORAGE_OBJECTS_GET = 'storage.objects.get'
74+
"""Permission: read object data / metadata, excluding ACLs."""
75+
76+
STORAGE_OBJECTS_GET_IAM_POLICY = 'storage.objects.getIamPolicy'
77+
"""Permission: read object ACLs."""
78+
79+
STORAGE_OBJECTS_LIST = 'storage.objects.list'
80+
"""Permission: list objects in a bucket."""
81+
82+
STORAGE_OBJECTS_SET_IAM_POLICY = 'storage.objects.setIamPolicy'
83+
"""Permission: update object ACLs."""
84+
85+
STORAGE_OBJECTS_UPDATE = 'storage.objects.update'
86+
"""Permission: update object metadat, excluding ACLs."""

‎storage/tests/unit/test_bucket.py

Copy file name to clipboardExpand all lines: storage/tests/unit/test_bucket.py
+129Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -866,6 +866,135 @@ def test_disable_website(self):
866866
bucket.disable_website()
867867
self.assertEqual(bucket._properties, UNSET)
868868

869+
def test_get_iam_policy(self):
870+
from google.cloud.storage.iam import STORAGE_OWNER_ROLE
871+
from google.cloud.storage.iam import STORAGE_EDITOR_ROLE
872+
from google.cloud.storage.iam import STORAGE_VIEWER_ROLE
873+
from google.cloud.iam import Policy
874+
875+
NAME = 'name'
876+
PATH = '/b/%s' % (NAME,)
877+
ETAG = 'DEADBEEF'
878+
VERSION = 17
879+
OWNER1 = 'user:phred@example.com'
880+
OWNER2 = 'group:cloud-logs@google.com'
881+
EDITOR1 = 'domain:google.com'
882+
EDITOR2 = 'user:phred@example.com'
883+
VIEWER1 = 'serviceAccount:1234-abcdef@service.example.com'
884+
VIEWER2 = 'user:phred@example.com'
885+
RETURNED = {
886+
'resourceId': PATH,
887+
'etag': ETAG,
888+
'version': VERSION,
889+
'bindings': [
890+
{'role': STORAGE_OWNER_ROLE, 'members': [OWNER1, OWNER2]},
891+
{'role': STORAGE_EDITOR_ROLE, 'members': [EDITOR1, EDITOR2]},
892+
{'role': STORAGE_VIEWER_ROLE, 'members': [VIEWER1, VIEWER2]},
893+
],
894+
}
895+
EXPECTED = {
896+
binding['role']: set(binding['members'])
897+
for binding in RETURNED['bindings']}
898+
connection = _Connection(RETURNED)
899+
client = _Client(connection, None)
900+
bucket = self._make_one(client=client, name=NAME)
901+
902+
policy = bucket.get_iam_policy()
903+
904+
self.assertIsInstance(policy, Policy)
905+
self.assertEqual(policy.etag, RETURNED['etag'])
906+
self.assertEqual(policy.version, RETURNED['version'])
907+
self.assertEqual(dict(policy), EXPECTED)
908+
909+
kw = connection._requested
910+
self.assertEqual(len(kw), 1)
911+
self.assertEqual(kw[0]['method'], 'GET')
912+
self.assertEqual(kw[0]['path'], '%s/iam' % (PATH,))
913+
914+
def test_set_iam_policy(self):
915+
import operator
916+
from google.cloud.storage.iam import STORAGE_OWNER_ROLE
917+
from google.cloud.storage.iam import STORAGE_EDITOR_ROLE
918+
from google.cloud.storage.iam import STORAGE_VIEWER_ROLE
919+
from google.cloud.iam import Policy
920+
921+
NAME = 'name'
922+
PATH = '/b/%s' % (NAME,)
923+
ETAG = 'DEADBEEF'
924+
VERSION = 17
925+
OWNER1 = 'user:phred@example.com'
926+
OWNER2 = 'group:cloud-logs@google.com'
927+
EDITOR1 = 'domain:google.com'
928+
EDITOR2 = 'user:phred@example.com'
929+
VIEWER1 = 'serviceAccount:1234-abcdef@service.example.com'
930+
VIEWER2 = 'user:phred@example.com'
931+
BINDINGS = [
932+
{'role': STORAGE_OWNER_ROLE, 'members': [OWNER1, OWNER2]},
933+
{'role': STORAGE_EDITOR_ROLE, 'members': [EDITOR1, EDITOR2]},
934+
{'role': STORAGE_VIEWER_ROLE, 'members': [VIEWER1, VIEWER2]},
935+
]
936+
RETURNED = {
937+
'etag': ETAG,
938+
'version': VERSION,
939+
'bindings': BINDINGS,
940+
}
941+
policy = Policy()
942+
for binding in BINDINGS:
943+
policy[binding['role']] = binding['members']
944+
945+
connection = _Connection(RETURNED)
946+
client = _Client(connection, None)
947+
bucket = self._make_one(client=client, name=NAME)
948+
949+
returned = bucket.set_iam_policy(policy)
950+
951+
self.assertEqual(returned.etag, ETAG)
952+
self.assertEqual(returned.version, VERSION)
953+
self.assertEqual(dict(returned), dict(policy))
954+
955+
kw = connection._requested
956+
self.assertEqual(len(kw), 1)
957+
self.assertEqual(kw[0]['method'], 'PUT')
958+
self.assertEqual(kw[0]['path'], '%s/iam' % (PATH,))
959+
sent = kw[0]['data']
960+
self.assertEqual(sent['resourceId'], PATH)
961+
self.assertEqual(len(sent['bindings']), len(BINDINGS))
962+
key = operator.itemgetter('role')
963+
for found, expected in zip(
964+
sorted(sent['bindings'], key=key),
965+
sorted(BINDINGS, key=key)):
966+
self.assertEqual(found['role'], expected['role'])
967+
self.assertEqual(
968+
sorted(found['members']), sorted(expected['members']))
969+
970+
def test_test_iam_permissions(self):
971+
from google.cloud.storage.iam import STORAGE_OBJECTS_LIST
972+
from google.cloud.storage.iam import STORAGE_BUCKETS_GET
973+
from google.cloud.storage.iam import STORAGE_BUCKETS_UPDATE
974+
975+
NAME = 'name'
976+
PATH = '/b/%s' % (NAME,)
977+
PERMISSIONS = [
978+
STORAGE_OBJECTS_LIST,
979+
STORAGE_BUCKETS_GET,
980+
STORAGE_BUCKETS_UPDATE,
981+
]
982+
ALLOWED = PERMISSIONS[1:]
983+
RETURNED = {'permissions': ALLOWED}
984+
connection = _Connection(RETURNED)
985+
client = _Client(connection, None)
986+
bucket = self._make_one(client=client, name=NAME)
987+
988+
allowed = bucket.test_iam_permissions(PERMISSIONS)
989+
990+
self.assertEqual(allowed, ALLOWED)
991+
992+
kw = connection._requested
993+
self.assertEqual(len(kw), 1)
994+
self.assertEqual(kw[0]['method'], 'GET')
995+
self.assertEqual(kw[0]['path'], '%s/iam/testPermissions' % (PATH,))
996+
self.assertEqual(kw[0]['query_params'], {'permissions': PERMISSIONS})
997+
869998
def test_make_public_defaults(self):
870999
from google.cloud.storage.acl import _ACLEntity
8711000

‎storage/tests/unit/test_iam.py

Copy file name to clipboard
+22Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Copyright 2017 Google Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import unittest
16+
17+
18+
class Test_constants(unittest.TestCase):
19+
20+
def test_ctor_defaults(self):
21+
from google.cloud.storage.iam import STORAGE_ADMIN_ROLE
22+
role = STORAGE_ADMIN_ROLE

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.