Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
feat(api_core): support version 3 policy bindings (#9869)
* feat(api_core): support version 3 policy bindings

* fix(doc): fix documenting bindings structure

* try fixing docs

* fix pytype error

* fill test coverage

* indent docs

* fix docs

* improve test coverage

* linty

* remove unused variable
  • Loading branch information
jkwlui committed Jan 9, 2020
1 parent cdc6233 commit a9dee32
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 13 deletions.
54 changes: 46 additions & 8 deletions google/cloud/bigtable/policy.py
Expand Up @@ -72,6 +72,22 @@ class Policy(BasePolicy):
If no etag is provided in the call to setIamPolicy, then the
existing policy is overwritten blindly.
:type version: int
:param version: The syntax schema version of the policy.
Note:
Using conditions in bindings requires the policy's version to be set
to `3` or greater, depending on the versions that are currently supported.
Accessing the policy using dict operations will raise InvalidOperationException
when the policy's version is set to 3.
Use the policy.bindings getter/setter to retrieve and modify the policy's bindings.
See:
IAM Policy https://cloud.google.com/iam/reference/rest/v1/Policy
Policy versions https://cloud.google.com/iam/docs/policies#versions
Conditions overview https://cloud.google.com/iam/docs/conditions-overview.
"""

def __init__(self, etag=None, version=None):
Expand All @@ -83,59 +99,67 @@ def __init__(self, etag=None, version=None):
def bigtable_admins(self):
"""Access to bigtable.admin role memebers
Raise InvalidOperationException if version is greater than 1 or policy contains conditions.
For example:
.. literalinclude:: snippets.py
:start-after: [START bigtable_admins_policy]
:end-before: [END bigtable_admins_policy]
"""
result = set()
for member in self._bindings.get(BIGTABLE_ADMIN_ROLE, ()):
for member in self.get(BIGTABLE_ADMIN_ROLE, ()):
result.add(member)
return frozenset(result)

@property
def bigtable_readers(self):
"""Access to bigtable.reader role memebers
Raise InvalidOperationException if version is greater than 1 or policy contains conditions.
For example:
.. literalinclude:: snippets.py
:start-after: [START bigtable_readers_policy]
:end-before: [END bigtable_readers_policy]
"""
result = set()
for member in self._bindings.get(BIGTABLE_READER_ROLE, ()):
for member in self.get(BIGTABLE_READER_ROLE, ()):
result.add(member)
return frozenset(result)

@property
def bigtable_users(self):
"""Access to bigtable.user role memebers
Raise InvalidOperationException if version is greater than 1 or policy contains conditions.
For example:
.. literalinclude:: snippets.py
:start-after: [START bigtable_users_policy]
:end-before: [END bigtable_users_policy]
"""
result = set()
for member in self._bindings.get(BIGTABLE_USER_ROLE, ()):
for member in self.get(BIGTABLE_USER_ROLE, ()):
result.add(member)
return frozenset(result)

@property
def bigtable_viewers(self):
"""Access to bigtable.viewer role memebers
Raise InvalidOperationException if version is greater than 1 or policy contains conditions.
For example:
.. literalinclude:: snippets.py
:start-after: [START bigtable_viewers_policy]
:end-before: [END bigtable_viewers_policy]
"""
result = set()
for member in self._bindings.get(BIGTABLE_VIEWER_ROLE, ()):
for member in self.get(BIGTABLE_VIEWER_ROLE, ()):
result.add(member)
return frozenset(result)

Expand All @@ -152,8 +176,17 @@ def from_pb(cls, policy_pb):
"""
policy = cls(policy_pb.etag, policy_pb.version)

for binding in policy_pb.bindings:
policy[binding.role] = sorted(binding.members)
policy.bindings = bindings = []
for binding_pb in policy_pb.bindings:
binding = {"role": binding_pb.role, "members": set(binding_pb.members)}
condition = binding_pb.condition
if condition and condition.expression:
binding["condition"] = {
"title": condition.title,
"description": condition.description,
"expression": condition.expression,
}
bindings.append(binding)

return policy

Expand All @@ -169,8 +202,13 @@ def to_pb(self):
etag=self.etag,
version=self.version or 0,
bindings=[
policy_pb2.Binding(role=role, members=sorted(self[role]))
for role in self
policy_pb2.Binding(
role=binding["role"],
members=sorted(binding["members"]),
condition=binding.get("condition"),
)
for binding in self.bindings
if binding["members"]
],
)

Expand Down
83 changes: 78 additions & 5 deletions tests/unit/test_policy.py
Expand Up @@ -38,7 +38,7 @@ def test_ctor_defaults(self):
self.assertEqual(dict(policy), {})

def test_ctor_explicit(self):
VERSION = 17
VERSION = 1
ETAG = b"ETAG"
empty = frozenset()
policy = self._make_one(ETAG, VERSION)
Expand Down Expand Up @@ -108,7 +108,7 @@ def test_from_pb_non_empty(self):
from google.cloud.bigtable.policy import BIGTABLE_ADMIN_ROLE

ETAG = b"ETAG"
VERSION = 17
VERSION = 1
members = ["serviceAccount:service_acc1@test.com", "user:user1@test.com"]
empty = frozenset()
message = policy_pb2.Policy(
Expand All @@ -127,6 +127,45 @@ def test_from_pb_non_empty(self):
self.assertEqual(len(policy), 1)
self.assertEqual(dict(policy), {BIGTABLE_ADMIN_ROLE: set(members)})

def test_from_pb_with_condition(self):
import pytest
from google.iam.v1 import policy_pb2
from google.api_core.iam import InvalidOperationException, _DICT_ACCESS_MSG
from google.cloud.bigtable.policy import BIGTABLE_ADMIN_ROLE

ETAG = b"ETAG"
VERSION = 3
members = ["serviceAccount:service_acc1@test.com", "user:user1@test.com"]
BINDINGS = [
{
"role": BIGTABLE_ADMIN_ROLE,
"members": members,
"condition": {
"title": "request_time",
"description": "Requests made before 2021-01-01T00:00:00Z",
"expression": 'request.time < timestamp("2021-01-01T00:00:00Z")',
},
}
]
message = policy_pb2.Policy(etag=ETAG, version=VERSION, bindings=BINDINGS,)
klass = self._get_target_class()
policy = klass.from_pb(message)
self.assertEqual(policy.etag, ETAG)
self.assertEqual(policy.version, VERSION)
self.assertEqual(policy.bindings[0]["role"], BIGTABLE_ADMIN_ROLE)
self.assertEqual(policy.bindings[0]["members"], set(members))
self.assertEqual(policy.bindings[0]["condition"], BINDINGS[0]["condition"])
with pytest.raises(InvalidOperationException, match=_DICT_ACCESS_MSG):
policy.bigtable_admins
with pytest.raises(InvalidOperationException, match=_DICT_ACCESS_MSG):
policy.bigtable_readers
with pytest.raises(InvalidOperationException, match=_DICT_ACCESS_MSG):
policy.bigtable_users
with pytest.raises(InvalidOperationException, match=_DICT_ACCESS_MSG):
policy.bigtable_viewers
with pytest.raises(InvalidOperationException, match=_DICT_ACCESS_MSG):
len(policy)

def test_to_pb_empty(self):
from google.iam.v1 import policy_pb2

Expand All @@ -139,7 +178,7 @@ def test_to_pb_explicit(self):
from google.iam.v1 import policy_pb2
from google.cloud.bigtable.policy import BIGTABLE_ADMIN_ROLE

VERSION = 17
VERSION = 1
ETAG = b"ETAG"
members = ["serviceAccount:service_acc1@test.com", "user:user1@test.com"]
policy = self._make_one(ETAG, VERSION)
Expand All @@ -154,8 +193,42 @@ def test_to_pb_explicit(self):

self.assertEqual(policy.to_pb(), expected)

def test_to_pb_with_condition(self):
from google.iam.v1 import policy_pb2
from google.cloud.bigtable.policy import BIGTABLE_ADMIN_ROLE

VERSION = 3
ETAG = b"ETAG"
members = ["serviceAccount:service_acc1@test.com", "user:user1@test.com"]
condition = {
"title": "request_time",
"description": "Requests made before 2021-01-01T00:00:00Z",
"expression": 'request.time < timestamp("2021-01-01T00:00:00Z")',
}
policy = self._make_one(ETAG, VERSION)
policy.bindings = [
{
"role": BIGTABLE_ADMIN_ROLE,
"members": set(members),
"condition": condition,
}
]
expected = policy_pb2.Policy(
etag=ETAG,
version=VERSION,
bindings=[
policy_pb2.Binding(
role=BIGTABLE_ADMIN_ROLE,
members=sorted(members),
condition=condition,
)
],
)

self.assertEqual(policy.to_pb(), expected)

def test_from_api_repr_wo_etag(self):
VERSION = 17
VERSION = 1
empty = frozenset()
resource = {"version": VERSION}
klass = self._get_target_class()
Expand Down Expand Up @@ -187,7 +260,7 @@ def test_from_api_repr_w_etag(self):
self.assertEqual(dict(policy), {})

def test_to_api_repr_wo_etag(self):
VERSION = 17
VERSION = 1
resource = {"version": VERSION}
policy = self._make_one(version=VERSION)
self.assertEqual(policy.to_api_repr(), resource)
Expand Down

0 comments on commit a9dee32

Please sign in to comment.