Skip to content

Commit ba44860

Browse files
authored
feat(storage): add UBLA attrs to IAMConfiguration (#9475)
Deprecate passing / setting BPO. Closes #8552.
1 parent cd91dc8 commit ba44860

File tree

3 files changed

+188
-46
lines changed

3 files changed

+188
-46
lines changed

google/cloud/storage/bucket.py

Lines changed: 100 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,22 @@
5454
from google.cloud.storage.notification import NONE_PAYLOAD_FORMAT
5555

5656

57+
_UBLA_BPO_ENABLED_MESSAGE = (
58+
"Pass only one of 'uniform_bucket_level_access_enabled' / "
59+
"'bucket_policy_only_enabled' to 'IAMConfiguration'."
60+
)
61+
_BPO_ENABLED_MESSAGE = (
62+
"'IAMConfiguration.bucket_policy_only_enabled' is deprecated. "
63+
"Instead, use 'IAMConfiguration.uniform_bucket_level_access_enabled'."
64+
)
65+
_UBLA_BPO_LOCK_TIME_MESSAGE = (
66+
"Pass only one of 'uniform_bucket_level_access_lock_time' / "
67+
"'bucket_policy_only_lock_time' to 'IAMConfiguration'."
68+
)
69+
_BPO_LOCK_TIME_MESSAGE = (
70+
"'IAMConfiguration.bucket_policy_only_lock_time' is deprecated. "
71+
"Instead, use 'IAMConfiguration.uniform_bucket_level_access_lock_time'."
72+
)
5773
_LOCATION_SETTER_MESSAGE = (
5874
"Assignment to 'Bucket.location' is deprecated, as it is only "
5975
"valid before the bucket is created. Instead, pass the location "
@@ -286,29 +302,66 @@ def from_api_repr(cls, resource):
286302
return instance
287303

288304

305+
_default = object()
306+
307+
289308
class IAMConfiguration(dict):
290309
"""Map a bucket's IAM configuration.
291310
292311
:type bucket: :class:`Bucket`
293312
:params bucket: Bucket for which this instance is the policy.
294313
314+
:type uniform_bucket_level_access_enabled: bool
315+
:params bucket_policy_only_enabled:
316+
(optional) whether the IAM-only policy is enabled for the bucket.
317+
318+
:type uniform_bucket_level_locked_time: :class:`datetime.datetime`
319+
:params uniform_bucket_level_locked_time:
320+
(optional) When the bucket's IAM-only policy was enabled.
321+
This value should normally only be set by the back-end API.
322+
295323
:type bucket_policy_only_enabled: bool
296-
:params bucket_policy_only_enabled: (optional) whether the IAM-only policy is enabled for the bucket.
324+
:params bucket_policy_only_enabled:
325+
Deprecated alias for :data:`uniform_bucket_level_access_enabled`.
297326
298327
:type bucket_policy_only_locked_time: :class:`datetime.datetime`
299-
:params bucket_policy_only_locked_time: (optional) When the bucket's IAM-only policy was ehabled. This value should normally only be set by the back-end API.
328+
:params bucket_policy_only_locked_time:
329+
Deprecated alias for :data:`uniform_bucket_level_access_locked_time`.
300330
"""
301331

302332
def __init__(
303333
self,
304334
bucket,
305-
bucket_policy_only_enabled=False,
306-
bucket_policy_only_locked_time=None,
335+
uniform_bucket_level_access_enabled=_default,
336+
uniform_bucket_level_access_locked_time=_default,
337+
bucket_policy_only_enabled=_default,
338+
bucket_policy_only_locked_time=_default,
307339
):
308-
data = {"bucketPolicyOnly": {"enabled": bucket_policy_only_enabled}}
309-
if bucket_policy_only_locked_time is not None:
310-
data["bucketPolicyOnly"]["lockedTime"] = _datetime_to_rfc3339(
311-
bucket_policy_only_locked_time
340+
if bucket_policy_only_enabled is not _default:
341+
342+
if uniform_bucket_level_access_enabled is not _default:
343+
raise ValueError(_UBLA_BPO_ENABLED_MESSAGE)
344+
345+
warnings.warn(_BPO_ENABLED_MESSAGE, DeprecationWarning, stacklevel=2)
346+
uniform_bucket_level_access_enabled = bucket_policy_only_enabled
347+
348+
if bucket_policy_only_locked_time is not _default:
349+
350+
if uniform_bucket_level_access_locked_time is not _default:
351+
raise ValueError(_UBLA_BPO_LOCK_TIME_MESSAGE)
352+
353+
warnings.warn(_BPO_LOCK_TIME_MESSAGE, DeprecationWarning, stacklevel=2)
354+
uniform_bucket_level_access_locked_time = bucket_policy_only_locked_time
355+
356+
if uniform_bucket_level_access_enabled is _default:
357+
uniform_bucket_level_access_enabled = False
358+
359+
data = {
360+
"uniformBucketLevelAccess": {"enabled": uniform_bucket_level_access_enabled}
361+
}
362+
if uniform_bucket_level_access_locked_time is not _default:
363+
data["uniformBucketLevelAccess"]["lockedTime"] = _datetime_to_rfc3339(
364+
uniform_bucket_level_access_locked_time
312365
)
313366
super(IAMConfiguration, self).__init__(data)
314367
self._bucket = bucket
@@ -340,41 +393,66 @@ def bucket(self):
340393
return self._bucket
341394

342395
@property
343-
def bucket_policy_only_enabled(self):
396+
def uniform_bucket_level_access_enabled(self):
344397
"""If set, access checks only use bucket-level IAM policies or above.
345398
346399
:rtype: bool
347400
:returns: whether the bucket is configured to allow only IAM.
348401
"""
349-
bpo = self.get("bucketPolicyOnly", {})
350-
return bpo.get("enabled", False)
402+
ubla = self.get("uniformBucketLevelAccess", {})
403+
return ubla.get("enabled", False)
351404

352-
@bucket_policy_only_enabled.setter
353-
def bucket_policy_only_enabled(self, value):
354-
bpo = self.setdefault("bucketPolicyOnly", {})
355-
bpo["enabled"] = bool(value)
405+
@uniform_bucket_level_access_enabled.setter
406+
def uniform_bucket_level_access_enabled(self, value):
407+
ubla = self.setdefault("uniformBucketLevelAccess", {})
408+
ubla["enabled"] = bool(value)
356409
self.bucket._patch_property("iamConfiguration", self)
357410

358411
@property
359-
def bucket_policy_only_locked_time(self):
360-
"""Deadline for changing :attr:`bucket_policy_only_enabled` from true to false.
412+
def uniform_bucket_level_access_locked_time(self):
413+
"""Deadline for changing :attr:`uniform_bucket_level_access_enabled` from true to false.
361414
362-
If the bucket's :attr:`bucket_policy_only_enabled` is true, this property
415+
If the bucket's :attr:`uniform_bucket_level_access_enabled` is true, this property
363416
is time time after which that setting becomes immutable.
364417
365-
If the bucket's :attr:`bucket_policy_only_enabled` is false, this property
418+
If the bucket's :attr:`uniform_bucket_level_access_enabled` is false, this property
366419
is ``None``.
367420
368421
:rtype: Union[:class:`datetime.datetime`, None]
369-
:returns: (readonly) Time after which :attr:`bucket_policy_only_enabled` will
422+
:returns: (readonly) Time after which :attr:`uniform_bucket_level_access_enabled` will
370423
be frozen as true.
371424
"""
372-
bpo = self.get("bucketPolicyOnly", {})
373-
stamp = bpo.get("lockedTime")
425+
ubla = self.get("uniformBucketLevelAccess", {})
426+
stamp = ubla.get("lockedTime")
374427
if stamp is not None:
375428
stamp = _rfc3339_to_datetime(stamp)
376429
return stamp
377430

431+
@property
432+
def bucket_policy_only_enabled(self):
433+
"""Deprecated alias for :attr:`uniform_bucket_level_access_enabled`.
434+
435+
:rtype: bool
436+
:returns: whether the bucket is configured to allow only IAM.
437+
"""
438+
return self.uniform_bucket_level_access_enabled
439+
440+
@bucket_policy_only_enabled.setter
441+
def bucket_policy_only_enabled(self, value):
442+
warnings.warn(_BPO_ENABLED_MESSAGE, DeprecationWarning, stacklevel=2)
443+
self.uniform_bucket_level_access_enabled = value
444+
445+
@property
446+
def bucket_policy_only_locked_time(self):
447+
"""Deprecated alias for :attr:`uniform_bucket_level_access_locked_time`.
448+
449+
:rtype: Union[:class:`datetime.datetime`, None]
450+
:returns:
451+
(readonly) Time after which :attr:`bucket_policy_only_enabled` will
452+
be frozen as true.
453+
"""
454+
return self.uniform_bucket_level_access_locked_time
455+
378456

379457
class Bucket(_PropertyMixin):
380458
"""A class representing a Bucket on Cloud Storage.

tests/system.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1726,13 +1726,13 @@ def tearDown(self):
17261726
bucket = Config.CLIENT.bucket(bucket_name)
17271727
retry_429_harder(bucket.delete)(force=True)
17281728

1729-
def test_new_bucket_w_bpo(self):
1730-
new_bucket_name = "new-w-bpo" + unique_resource_id("-")
1729+
def test_new_bucket_w_ubla(self):
1730+
new_bucket_name = "new-w-ubla" + unique_resource_id("-")
17311731
self.assertRaises(
17321732
exceptions.NotFound, Config.CLIENT.get_bucket, new_bucket_name
17331733
)
17341734
bucket = Config.CLIENT.bucket(new_bucket_name)
1735-
bucket.iam_configuration.bucket_policy_only_enabled = True
1735+
bucket.iam_configuration.uniform_bucket_level_access_enabled = True
17361736
retry_429_503(bucket.create)()
17371737
self.case_buckets_to_delete.append(new_bucket_name)
17381738

@@ -1762,9 +1762,8 @@ def test_new_bucket_w_bpo(self):
17621762
with self.assertRaises(exceptions.BadRequest):
17631763
blob_acl.save()
17641764

1765-
@unittest.skipUnless(False, "Back-end fix for BPO/UBLA expected 2019-07-12")
1766-
def test_bpo_set_unset_preserves_acls(self):
1767-
new_bucket_name = "bpo-acls" + unique_resource_id("-")
1765+
def test_ubla_set_unset_preserves_acls(self):
1766+
new_bucket_name = "ubla-acls" + unique_resource_id("-")
17681767
self.assertRaises(
17691768
exceptions.NotFound, Config.CLIENT.get_bucket, new_bucket_name
17701769
)
@@ -1776,25 +1775,25 @@ def test_bpo_set_unset_preserves_acls(self):
17761775
payload = b"DEADBEEF"
17771776
blob.upload_from_string(payload)
17781777

1779-
# Preserve ACLs before setting BPO
1778+
# Preserve ACLs before setting UBLA
17801779
bucket_acl_before = list(bucket.acl)
17811780
blob_acl_before = list(bucket.acl)
17821781

1783-
# Set BPO
1784-
bucket.iam_configuration.bucket_policy_only_enabled = True
1782+
# Set UBLA
1783+
bucket.iam_configuration.uniform_bucket_level_access_enabled = True
17851784
bucket.patch()
17861785

1787-
self.assertTrue(bucket.iam_configuration.bucket_policy_only_enabled)
1786+
self.assertTrue(bucket.iam_configuration.uniform_bucket_level_access_enabled)
17881787

1789-
# While BPO is set, cannot get / set ACLs
1788+
# While UBLA is set, cannot get / set ACLs
17901789
with self.assertRaises(exceptions.BadRequest):
17911790
bucket.acl.reload()
17921791

1793-
# Clear BPO
1794-
bucket.iam_configuration.bucket_policy_only_enabled = False
1792+
# Clear UBLA
1793+
bucket.iam_configuration.uniform_bucket_level_access_enabled = False
17951794
bucket.patch()
17961795

1797-
# Query ACLs after clearing BPO
1796+
# Query ACLs after clearing UBLA
17981797
bucket.acl.reload()
17991798
bucket_acl_after = list(bucket.acl)
18001799
blob.acl.reload()

tests/unit/test_bucket.py

Lines changed: 75 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -198,24 +198,75 @@ def test_ctor_defaults(self):
198198
config = self._make_one(bucket)
199199

200200
self.assertIs(config.bucket, bucket)
201+
self.assertFalse(config.uniform_bucket_level_access_enabled)
202+
self.assertIsNone(config.uniform_bucket_level_access_locked_time)
201203
self.assertFalse(config.bucket_policy_only_enabled)
202204
self.assertIsNone(config.bucket_policy_only_locked_time)
203205

204-
def test_ctor_explicit(self):
206+
def test_ctor_explicit_ubla(self):
205207
import datetime
206208
import pytz
207209

208210
bucket = self._make_bucket()
209211
now = datetime.datetime.utcnow().replace(tzinfo=pytz.UTC)
210212

211213
config = self._make_one(
212-
bucket, bucket_policy_only_enabled=True, bucket_policy_only_locked_time=now
214+
bucket,
215+
uniform_bucket_level_access_enabled=True,
216+
uniform_bucket_level_access_locked_time=now,
213217
)
214218

215219
self.assertIs(config.bucket, bucket)
220+
self.assertTrue(config.uniform_bucket_level_access_enabled)
221+
self.assertEqual(config.uniform_bucket_level_access_locked_time, now)
216222
self.assertTrue(config.bucket_policy_only_enabled)
217223
self.assertEqual(config.bucket_policy_only_locked_time, now)
218224

225+
def test_ctor_explicit_bpo(self):
226+
import datetime
227+
import pytz
228+
229+
bucket = self._make_bucket()
230+
now = datetime.datetime.utcnow().replace(tzinfo=pytz.UTC)
231+
232+
config = pytest.deprecated_call(
233+
self._make_one,
234+
bucket,
235+
bucket_policy_only_enabled=True,
236+
bucket_policy_only_locked_time=now,
237+
)
238+
239+
self.assertIs(config.bucket, bucket)
240+
self.assertTrue(config.uniform_bucket_level_access_enabled)
241+
self.assertEqual(config.uniform_bucket_level_access_locked_time, now)
242+
self.assertTrue(config.bucket_policy_only_enabled)
243+
self.assertEqual(config.bucket_policy_only_locked_time, now)
244+
245+
def test_ctor_ubla_and_bpo_enabled(self):
246+
bucket = self._make_bucket()
247+
248+
with self.assertRaises(ValueError):
249+
self._make_one(
250+
bucket,
251+
uniform_bucket_level_access_enabled=True,
252+
bucket_policy_only_enabled=True,
253+
)
254+
255+
def test_ctor_ubla_and_bpo_time(self):
256+
import datetime
257+
import pytz
258+
259+
bucket = self._make_bucket()
260+
now = datetime.datetime.utcnow().replace(tzinfo=pytz.UTC)
261+
262+
with self.assertRaises(ValueError):
263+
self._make_one(
264+
bucket,
265+
uniform_bucket_level_access_enabled=True,
266+
uniform_bucket_level_access_locked_time=now,
267+
bucket_policy_only_locked_time=now,
268+
)
269+
219270
def test_from_api_repr_w_empty_resource(self):
220271
klass = self._get_target_class()
221272
bucket = self._make_bucket()
@@ -230,7 +281,7 @@ def test_from_api_repr_w_empty_resource(self):
230281
def test_from_api_repr_w_empty_bpo(self):
231282
klass = self._get_target_class()
232283
bucket = self._make_bucket()
233-
resource = {"bucketPolicyOnly": {}}
284+
resource = {"uniformBucketLevelAccess": {}}
234285

235286
config = klass.from_api_repr(resource, bucket)
236287

@@ -241,7 +292,7 @@ def test_from_api_repr_w_empty_bpo(self):
241292
def test_from_api_repr_w_disabled(self):
242293
klass = self._get_target_class()
243294
bucket = self._make_bucket()
244-
resource = {"bucketPolicyOnly": {"enabled": False}}
295+
resource = {"uniformBucketLevelAccess": {"enabled": False}}
245296

246297
config = klass.from_api_repr(resource, bucket)
247298

@@ -258,7 +309,7 @@ def test_from_api_repr_w_enabled(self):
258309
bucket = self._make_bucket()
259310
now = datetime.datetime.utcnow().replace(tzinfo=pytz.UTC)
260311
resource = {
261-
"bucketPolicyOnly": {
312+
"uniformBucketLevelAccess": {
262313
"enabled": True,
263314
"lockedTime": _datetime_to_rfc3339(now),
264315
}
@@ -267,16 +318,30 @@ def test_from_api_repr_w_enabled(self):
267318
config = klass.from_api_repr(resource, bucket)
268319

269320
self.assertIs(config.bucket, bucket)
321+
self.assertTrue(config.uniform_bucket_level_access_enabled)
322+
self.assertEqual(config.uniform_bucket_level_access_locked_time, now)
270323
self.assertTrue(config.bucket_policy_only_enabled)
271324
self.assertEqual(config.bucket_policy_only_locked_time, now)
272325

326+
def test_uniform_bucket_level_access_enabled_setter(self):
327+
bucket = self._make_bucket()
328+
config = self._make_one(bucket)
329+
330+
config.uniform_bucket_level_access_enabled = True
331+
self.assertTrue(config.bucket_policy_only_enabled)
332+
333+
self.assertTrue(config["uniformBucketLevelAccess"]["enabled"])
334+
bucket._patch_property.assert_called_once_with("iamConfiguration", config)
335+
273336
def test_bucket_policy_only_enabled_setter(self):
274337
bucket = self._make_bucket()
275338
config = self._make_one(bucket)
276339

277-
config.bucket_policy_only_enabled = True
340+
with pytest.deprecated_call():
341+
config.bucket_policy_only_enabled = True
278342

279-
self.assertTrue(config["bucketPolicyOnly"]["enabled"])
343+
self.assertTrue(config.uniform_bucket_level_access_enabled)
344+
self.assertTrue(config["uniformBucketLevelAccess"]["enabled"])
280345
bucket._patch_property.assert_called_once_with("iamConfiguration", config)
281346

282347

@@ -1317,7 +1382,7 @@ def test_iam_configuration_policy_w_entry(self):
13171382
NAME = "name"
13181383
properties = {
13191384
"iamConfiguration": {
1320-
"bucketPolicyOnly": {
1385+
"uniformBucketLevelAccess": {
13211386
"enabled": True,
13221387
"lockedTime": _datetime_to_rfc3339(now),
13231388
}
@@ -1329,8 +1394,8 @@ def test_iam_configuration_policy_w_entry(self):
13291394

13301395
self.assertIsInstance(config, IAMConfiguration)
13311396
self.assertIs(config.bucket, bucket)
1332-
self.assertTrue(config.bucket_policy_only_enabled)
1333-
self.assertEqual(config.bucket_policy_only_locked_time, now)
1397+
self.assertTrue(config.uniform_bucket_level_access_enabled)
1398+
self.assertEqual(config.uniform_bucket_level_access_locked_time, now)
13341399

13351400
def test_lifecycle_rules_getter_unknown_action_type(self):
13361401
NAME = "name"

0 commit comments

Comments
 (0)