Skip to content
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]})