-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[Storage] Support Uniform Bucket Level Access #9021
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -280,6 +280,12 @@ class IAMConfiguration(dict): | |||||
| :type bucket: :class:`Bucket` | ||||||
| :params bucket: Bucket for which this instance is the policy. | ||||||
|
|
||||||
| :type uniform_bucket_level_access_enabled: bool | ||||||
| :params bucket_policy_only_enabled: (optional) whether the IAM-only policy is enabled for the bucket. | ||||||
|
|
||||||
| :type uniform_bucket_level_locked_time: :class:`datetime.datetime` | ||||||
| :params uniform_bucket_level_locked_time: (optional) When the bucket's IAM-only policy was ehabled. This value should normally only be set by the back-end API. | ||||||
|
|
||||||
| :type bucket_policy_only_enabled: bool | ||||||
| :params bucket_policy_only_enabled: (optional) whether the IAM-only policy is enabled for the bucket. | ||||||
|
|
||||||
|
|
@@ -292,12 +298,32 @@ def __init__( | |||||
| bucket, | ||||||
| bucket_policy_only_enabled=False, | ||||||
| bucket_policy_only_locked_time=None, | ||||||
| uniform_bucket_level_access_enabled=False, | ||||||
| uniform_bucket_level_access_locked_time=None, | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ordering should match the order documented above. The constructor probably needs to check for / resolve conflicts between |
||||||
| ): | ||||||
| data = {"bucketPolicyOnly": {"enabled": bucket_policy_only_enabled}} | ||||||
| data = {"bucketPolicyOnly": {}, "uniformBucketLevelAccess": {}} | ||||||
| if bucket_policy_only_enabled: | ||||||
| data["bucketPolicyOnly"]["enabled"] = bucket_policy_only_enabled | ||||||
| data["uniformBucketLevelAccess"]["enabled"] = bucket_policy_only_enabled | ||||||
| if bucket_policy_only_locked_time is not None: | ||||||
| data["bucketPolicyOnly"]["lockedTime"] = _datetime_to_rfc3339( | ||||||
| bucket_policy_only_locked_time | ||||||
| ) | ||||||
| data["bucketPolicyOnly"]["lockedTime"] = _datetime_to_rfc3339( | ||||||
| bucket_policy_only_locked_time | ||||||
| ) | ||||||
| data["uniformBucketLevelAccess"]["lockedTime"] = _datetime_to_rfc3339( | ||||||
| bucket_policy_only_locked_time | ||||||
| ) | ||||||
|
|
||||||
| if uniform_bucket_level_access_enabled: | ||||||
| data["bucketPolicyOnly"]["enabled"] = uniform_bucket_level_access_enabled | ||||||
| data["uniformBucketLevelAccess"]["enabled"] = uniform_bucket_level_access_enabled | ||||||
| if uniform_bucket_level_access_locked_time is not None: | ||||||
| data["bucketPolicyOnly"]["lockedTime"] = _datetime_to_rfc3339( | ||||||
| uniform_bucket_level_access_locked_time | ||||||
| ) | ||||||
| data["uniformBucketLevelAccess"]["lockedTime"] = _datetime_to_rfc3339( | ||||||
| uniform_bucket_level_access_locked_time | ||||||
| ) | ||||||
|
|
||||||
| super(IAMConfiguration, self).__init__(data) | ||||||
| self._bucket = bucket | ||||||
|
|
||||||
|
|
@@ -314,6 +340,7 @@ def from_api_repr(cls, resource, bucket): | |||||
| :rtype: :class:`IAMConfiguration` | ||||||
| :returns: Instance created from resource. | ||||||
| """ | ||||||
|
|
||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Stray line. |
||||||
| instance = cls(bucket) | ||||||
| instance.update(resource) | ||||||
| return instance | ||||||
|
|
@@ -329,36 +356,54 @@ def bucket(self): | |||||
|
|
||||||
| @property | ||||||
| def bucket_policy_only_enabled(self): | ||||||
| """Alias for uniform_bucket_level_access_enabled""" | ||||||
| self.uniform_bucket_level_access_enabled() | ||||||
|
|
||||||
| @property | ||||||
| def uniform_bucket_level_access_enabled(self): | ||||||
| """If set, access checks only use bucket-level IAM policies or above. | ||||||
|
|
||||||
| :rtype: bool | ||||||
| :returns: whether the bucket is configured to allow only IAM. | ||||||
| """ | ||||||
| bpo = self.get("bucketPolicyOnly", {}) | ||||||
| return bpo.get("enabled", False) | ||||||
| ubla = self.get("uniformBucketLevelAccess", {}) | ||||||
| return ubla.get("enabled", False) | ||||||
|
|
||||||
| @bucket_policy_only_enabled.setter | ||||||
| def bucket_policy_only_enabled(self, value): | ||||||
| @uniform_bucket_level_access_enabled.setter | ||||||
| def uniform_bucket_level_access_enabled(self, value): | ||||||
| ubla = self.setdefault("uniformBucketLevelAccess", {}) | ||||||
| ubla["enabled"] = bool(value) | ||||||
| #### THIS IS A WORKAROUND #### | ||||||
| bpo = self.setdefault("bucketPolicyOnly", {}) | ||||||
| bpo["enabled"] = bool(value) | ||||||
| #### THIS IS A WORKAROUND #### | ||||||
| self.bucket._patch_property("iamConfiguration", self) | ||||||
|
|
||||||
| @bucket_policy_only_enabled.setter | ||||||
| def bucket_policy_only_enabled(self, value): | ||||||
| self.uniform_bucket_level_access_enabled(value) | ||||||
|
|
||||||
| @property | ||||||
| def bucket_policy_only_locked_time(self): | ||||||
| """Deadline for changing :attr:`bucket_policy_only_enabled` from true to false. | ||||||
| """Alias for uniform_bucket_level_access.""" | ||||||
| return self.uniform_bucket_level_access_locked_time() | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't call the property, just return it, e.g.:
Suggested change
|
||||||
|
|
||||||
| @property | ||||||
| def uniform_bucket_level_access_locked_time(self): | ||||||
| """Deadline for changing :attr:`uniform_bucket_level_access_enabled` from true to false. | ||||||
|
|
||||||
| If the bucket's :attr:`bucket_policy_only_enabled` is true, this property | ||||||
| If the bucket's :attr:`uniform_bucket_level_access_enabled` is true, this property | ||||||
| is time time after which that setting becomes immutable. | ||||||
|
|
||||||
| If the bucket's :attr:`bucket_policy_only_enabled` is false, this property | ||||||
| If the bucket's :attr:`uniform_bucket_level_access_enabled` is false, this property | ||||||
| is ``None``. | ||||||
|
|
||||||
| :rtype: Union[:class:`datetime.datetime`, None] | ||||||
| :returns: (readonly) Time after which :attr:`bucket_policy_only_enabled` will | ||||||
| :returns: (readonly) Time after which :attr:`uniform_bucket_level_access_enabled` will | ||||||
| be frozen as true. | ||||||
| """ | ||||||
| bpo = self.get("bucketPolicyOnly", {}) | ||||||
| stamp = bpo.get("lockedTime") | ||||||
| ubla = self.get("uniformBucketLevelAccess", {}) | ||||||
| stamp = ubla.get("lockedTime") | ||||||
| if stamp is not None: | ||||||
| stamp = _rfc3339_to_datetime(stamp) | ||||||
| return stamp | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -197,8 +197,8 @@ def test_ctor_defaults(self): | |
| config = self._make_one(bucket) | ||
|
|
||
| self.assertIs(config.bucket, bucket) | ||
| self.assertFalse(config.bucket_policy_only_enabled) | ||
| self.assertIsNone(config.bucket_policy_only_locked_time) | ||
| self.assertFalse(config.uniform_bucket_level_access_enabled) | ||
| self.assertIsNone(config.uniform_bucket_level_access_locked_time) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to retain tests for the deprecated properties (which would have exposed the "can't call properties" bugs I noted above). |
||
|
|
||
| def test_ctor_explicit(self): | ||
| import datetime | ||
|
|
@@ -208,12 +208,12 @@ def test_ctor_explicit(self): | |
| now = datetime.datetime.utcnow().replace(tzinfo=pytz.UTC) | ||
|
|
||
| config = self._make_one( | ||
| bucket, bucket_policy_only_enabled=True, bucket_policy_only_locked_time=now | ||
| bucket, uniform_bucket_level_access_enabled=True, uniform_bucket_level_access_locked_time=now | ||
| ) | ||
|
|
||
| self.assertIs(config.bucket, bucket) | ||
| self.assertTrue(config.bucket_policy_only_enabled) | ||
| self.assertEqual(config.bucket_policy_only_locked_time, now) | ||
| self.assertTrue(config.uniform_bucket_level_access_enabled) | ||
| self.assertEqual(config.uniform_bucket_level_access_locked_time, now) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Likewise, we need to continue to assert values for the deprecated properties here. We also need separate testcases for passing |
||
|
|
||
| def test_from_api_repr_w_empty_resource(self): | ||
| klass = self._get_target_class() | ||
|
|
@@ -223,30 +223,30 @@ def test_from_api_repr_w_empty_resource(self): | |
| config = klass.from_api_repr(resource, bucket) | ||
|
|
||
| self.assertIs(config.bucket, bucket) | ||
| self.assertFalse(config.bucket_policy_only_enabled) | ||
| self.assertIsNone(config.bucket_policy_only_locked_time) | ||
| self.assertFalse(config.uniform_bucket_level_access_enabled) | ||
| self.assertIsNone(config.uniform_bucket_level_access_locked_time) | ||
|
|
||
| def test_from_api_repr_w_empty_bpo(self): | ||
| def test_from_api_repr_w_empty_ubla(self): | ||
| klass = self._get_target_class() | ||
| bucket = self._make_bucket() | ||
| resource = {"bucketPolicyOnly": {}} | ||
| resource = {"uniformBucketLevelAccess": {}} | ||
|
|
||
| config = klass.from_api_repr(resource, bucket) | ||
|
|
||
| self.assertIs(config.bucket, bucket) | ||
| self.assertFalse(config.bucket_policy_only_enabled) | ||
| self.assertIsNone(config.bucket_policy_only_locked_time) | ||
| self.assertFalse(config.uniform_bucket_level_access_enabled) | ||
| self.assertIsNone(config.uniform_bucket_level_access_locked_time) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to retain the assertions about the deprecated properties here. Also, do we need a separate testcase for a resource which contains only an empty |
||
|
|
||
| def test_from_api_repr_w_disabled(self): | ||
| klass = self._get_target_class() | ||
| bucket = self._make_bucket() | ||
| resource = {"bucketPolicyOnly": {"enabled": False}} | ||
| resource = {"uniformBucketLevelAccess": {"enabled": False}} | ||
|
|
||
| config = klass.from_api_repr(resource, bucket) | ||
|
|
||
| self.assertIs(config.bucket, bucket) | ||
| self.assertFalse(config.bucket_policy_only_enabled) | ||
| self.assertIsNone(config.bucket_policy_only_locked_time) | ||
| self.assertFalse(config.uniform_bucket_level_access_enabled) | ||
| self.assertIsNone(config.uniform_bucket_level_access_locked_time) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Preserve the assertions about the deprecated properties here, too. Also, do we need a separate testcase for a resource which contains only a populated |
||
|
|
||
| def test_from_api_repr_w_enabled(self): | ||
| import datetime | ||
|
|
@@ -257,7 +257,7 @@ def test_from_api_repr_w_enabled(self): | |
| bucket = self._make_bucket() | ||
| now = datetime.datetime.utcnow().replace(tzinfo=pytz.UTC) | ||
| resource = { | ||
| "bucketPolicyOnly": { | ||
| "uniformBucketLevelAccess": { | ||
| "enabled": True, | ||
| "lockedTime": _datetime_to_rfc3339(now), | ||
| } | ||
|
|
@@ -266,16 +266,16 @@ def test_from_api_repr_w_enabled(self): | |
| config = klass.from_api_repr(resource, bucket) | ||
|
|
||
| self.assertIs(config.bucket, bucket) | ||
| self.assertTrue(config.bucket_policy_only_enabled) | ||
| self.assertEqual(config.bucket_policy_only_locked_time, now) | ||
| self.assertTrue(config.uniform_bucket_level_access_enabled) | ||
| self.assertEqual(config.uniform_bucket_level_access_locked_time, now) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Preserve the assertions about the deprecated properties here, too. Also, do we need a separate testcase for a resource which contains only a populated |
||
|
|
||
| def test_bucket_policy_only_enabled_setter(self): | ||
| def test_uniform_bucket_level_access_enabled_setter(self): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to preserve separate test cases for the deprecated properties' setters. |
||
| bucket = self._make_bucket() | ||
| config = self._make_one(bucket) | ||
|
|
||
| config.bucket_policy_only_enabled = True | ||
| config.uniform_bucket_level_access_enabled = True | ||
|
|
||
| self.assertTrue(config["bucketPolicyOnly"]["enabled"]) | ||
| self.assertTrue(config["uniformBucketLevelAccess"]["enabled"]) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Preserve the assertion about the deprecated prroperty here, too. |
||
| bucket._patch_property.assert_called_once_with("iamConfiguration", config) | ||
|
|
||
|
|
||
|
|
@@ -1249,8 +1249,8 @@ def test_iam_configuration_policy_missing(self): | |
|
|
||
| self.assertIsInstance(config, IAMConfiguration) | ||
| self.assertIs(config.bucket, bucket) | ||
| self.assertFalse(config.bucket_policy_only_enabled) | ||
| self.assertIsNone(config.bucket_policy_only_locked_time) | ||
| self.assertFalse(config.uniform_bucket_level_access_enabled) | ||
| self.assertIsNone(config.uniform_bucket_level_access_locked_time) | ||
|
|
||
| def test_iam_configuration_policy_w_entry(self): | ||
| import datetime | ||
|
|
@@ -1262,7 +1262,7 @@ def test_iam_configuration_policy_w_entry(self): | |
| NAME = "name" | ||
| properties = { | ||
| "iamConfiguration": { | ||
| "bucketPolicyOnly": { | ||
| "uniformBucketLevelAccess": { | ||
| "enabled": True, | ||
| "lockedTime": _datetime_to_rfc3339(now), | ||
| } | ||
|
|
@@ -1274,8 +1274,8 @@ def test_iam_configuration_policy_w_entry(self): | |
|
|
||
| self.assertIsInstance(config, IAMConfiguration) | ||
| self.assertIs(config.bucket, bucket) | ||
| self.assertTrue(config.bucket_policy_only_enabled) | ||
| self.assertEqual(config.bucket_policy_only_locked_time, now) | ||
| self.assertTrue(config.uniform_bucket_level_access_enabled) | ||
| self.assertEqual(config.uniform_bucket_level_access_locked_time, now) | ||
|
|
||
| def test_lifecycle_rules_getter_unknown_action_type(self): | ||
| NAME = "name" | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should
bucket_policy_only_enabledandbucket_policy_only_locked_timebe documented as deprecated?