Skip to content
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

Factor common IAM policy bits into 'google.cloud.iam'. #3188

Merged
merged 10 commits into from
Apr 17, 2017
42 changes: 31 additions & 11 deletions core/google/cloud/iam.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
https://cloud.google.com/iam/docs/understanding-roles
"""

import collections

# Generic IAM roles

OWNER_ROLE = 'roles/owner'
Expand All @@ -29,7 +31,7 @@
"""Generic role implying rights to access an object."""


class Policy(object):
class Policy(collections.MutableMapping):
"""IAM Policy

See:
Expand All @@ -53,49 +55,64 @@ class Policy(object):
def __init__(self, etag=None, version=None):
self.etag = etag
self.version = version
self.bindings = {}
self._bindings = {}

def __iter__(self):
return iter(self._bindings)

def __len__(self):
return len(self._bindings)

def __getitem__(self, key):
return self._bindings[key]

def __setitem__(self, key, value):
self._bindings[key] = value

This comment was marked as spam.


def __delitem__(self, key):
del self._bindings[key]

@property
def owners(self):
"""Legacy access to owner role."""
result = set()
for role in self._OWNER_ROLES:
for member in self.bindings.get(role, ()):
for member in self._bindings.get(role, ()):
result.add(member)
return frozenset(result)

@owners.setter
def owners(self, value):
"""Update owners."""
self.bindings[OWNER_ROLE] = list(value)
self._bindings[OWNER_ROLE] = list(value)

@property
def editors(self):
"""Legacy access to editor role."""
result = set()
for role in self._EDITOR_ROLES:
for member in self.bindings.get(role, ()):
for member in self._bindings.get(role, ()):
result.add(member)
return frozenset(result)

@editors.setter
def editors(self, value):
"""Update editors."""
self.bindings[EDITOR_ROLE] = list(value)
self._bindings[EDITOR_ROLE] = list(value)

@property
def viewers(self):
"""Legacy access to viewer role."""
result = set()
for role in self._VIEWER_ROLES:
for member in self.bindings.get(role, ()):
for member in self._bindings.get(role, ()):
result.add(member)
return frozenset(result)

@viewers.setter
def viewers(self, value):
"""Update viewers."""
self.bindings[VIEWER_ROLE] = list(value)
self._bindings[VIEWER_ROLE] = list(value)

@staticmethod
def user(email):

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

Expand Down Expand Up @@ -179,7 +196,7 @@ def from_api_repr(cls, resource):
for binding in resource.get('bindings', ()):
role = binding['role']
members = sorted(binding['members'])

This comment was marked as spam.

policy.bindings[role] = members
policy._bindings[role] = members
return policy

def to_api_repr(self):
Expand All @@ -196,9 +213,9 @@ def to_api_repr(self):
if self.version is not None:
resource['version'] = self.version

if len(self.bindings) > 0:
if len(self._bindings) > 0:
bindings = resource['bindings'] = []
for role, members in sorted(self.bindings.items()):
for role, members in sorted(self._bindings.items()):
if len(members) > 0:
bindings.append(
{'role': role, 'members': sorted(set(members))})
Expand All @@ -207,3 +224,6 @@ def to_api_repr(self):
del resource['bindings']

return resource


collections.MutableMapping.register(Policy)
39 changes: 33 additions & 6 deletions core/tests/unit/test_iam.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ def test_ctor_defaults(self):
self.assertEqual(list(policy.editors), [])
self.assertIsInstance(policy.viewers, frozenset)
self.assertEqual(list(policy.viewers), [])
self.assertEqual(dict(policy.bindings), {})
self.assertEqual(len(policy), 0)
self.assertEqual(dict(policy), {})

def test_ctor_explicit(self):
VERSION = 17
Expand All @@ -47,7 +48,33 @@ def test_ctor_explicit(self):
self.assertEqual(list(policy.owners), [])
self.assertEqual(list(policy.editors), [])
self.assertEqual(list(policy.viewers), [])
self.assertEqual(dict(policy.bindings), {})
self.assertEqual(len(policy), 0)
self.assertEqual(dict(policy), {})

def test___getitem___miss(self):
policy = self._make_one()
with self.assertRaises(KeyError):
policy['nonesuch']

def test___setitem__(self):
USER = 'user:phred@example.com'
policy = self._make_one()
policy['rolename'] = [USER]
self.assertEqual(policy['rolename'], [USER])
self.assertEqual(len(policy), 1)
self.assertEqual(dict(policy), {'rolename': [USER]})

def test___delitem___hit(self):
policy = self._make_one()
policy._bindings['rolename'] = ['phred@example.com']
del policy['rolename']
self.assertEqual(len(policy), 0)
self.assertEqual(dict(policy), {})

def test___delitem___miss(self):
policy = self._make_one()
with self.assertRaises(KeyError):
del policy['nonesuch']

def test_user(self):
EMAIL = 'phred@example.com'
Expand Down Expand Up @@ -92,7 +119,7 @@ def test_from_api_repr_only_etag(self):
self.assertEqual(list(policy.owners), [])
self.assertEqual(list(policy.editors), [])
self.assertEqual(list(policy.viewers), [])
self.assertEqual(dict(policy.bindings), {})
self.assertEqual(dict(policy), {})

def test_from_api_repr_complete(self):
from google.cloud.iam import (
Expand Down Expand Up @@ -124,7 +151,7 @@ def test_from_api_repr_complete(self):
self.assertEqual(sorted(policy.editors), [EDITOR1, EDITOR2])
self.assertEqual(sorted(policy.viewers), [VIEWER1, VIEWER2])
self.assertEqual(
dict(policy.bindings), {
dict(policy), {
OWNER_ROLE: [OWNER1, OWNER2],
EDITOR_ROLE: [EDITOR1, EDITOR2],
VIEWER_ROLE: [VIEWER1, VIEWER2],
Expand All @@ -144,7 +171,7 @@ def test_from_api_repr_unknown_role(self):
policy = klass.from_api_repr(RESOURCE)
self.assertEqual(policy.etag, 'DEADBEEF')
self.assertEqual(policy.version, 17)
self.assertEqual(policy.bindings, {'unknown': [GROUP, USER]})
self.assertEqual(dict(policy), {'unknown': [GROUP, USER]})

def test_to_api_repr_defaults(self):
policy = self._make_one()
Expand All @@ -156,7 +183,7 @@ def test_to_api_repr_only_etag(self):

def test_to_api_repr_binding_wo_members(self):
policy = self._make_one()
policy.bindings['empty'] = []
policy['empty'] = []
self.assertEqual(policy.to_api_repr(), {})

def test_to_api_repr_binding_w_duplicates(self):
Expand Down
8 changes: 4 additions & 4 deletions pubsub/google/cloud/pubsub/iam.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,19 +109,19 @@ class Policy(_BasePolicy):
@property
def publishers(self):
"""Legacy access to owner role."""
return frozenset(self.bindings.get(PUBSUB_PUBLISHER_ROLE, ()))
return frozenset(self._bindings.get(PUBSUB_PUBLISHER_ROLE, ()))

@publishers.setter
def publishers(self, value):
"""Update publishers."""
self.bindings[PUBSUB_PUBLISHER_ROLE] = list(value)
self._bindings[PUBSUB_PUBLISHER_ROLE] = list(value)

@property
def subscribers(self):
"""Legacy access to owner role."""
return frozenset(self.bindings.get(PUBSUB_SUBSCRIBER_ROLE, ()))
return frozenset(self._bindings.get(PUBSUB_SUBSCRIBER_ROLE, ()))

@subscribers.setter
def subscribers(self, value):
"""Update subscribers."""
self.bindings[PUBSUB_SUBSCRIBER_ROLE] = list(value)
self._bindings[PUBSUB_SUBSCRIBER_ROLE] = list(value)
4 changes: 2 additions & 2 deletions pubsub/tests/unit/test_iam.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def test_publishers_setter(self):

self.assertEqual(sorted(policy.publishers), [PUBLISHER])
self.assertEqual(
policy.bindings, {PUBSUB_PUBLISHER_ROLE: [PUBLISHER]})
dict(policy), {PUBSUB_PUBLISHER_ROLE: [PUBLISHER]})

def test_subscribers_setter(self):
from google.cloud.pubsub.iam import (
Expand All @@ -75,4 +75,4 @@ def test_subscribers_setter(self):

self.assertEqual(sorted(policy.subscribers), [SUBSCRIBER])
self.assertEqual(
policy.bindings, {PUBSUB_SUBSCRIBER_ROLE: [SUBSCRIBER]})
dict(policy), {PUBSUB_SUBSCRIBER_ROLE: [SUBSCRIBER]})