Skip to content

Commit f270f5c

Browse files
authored
Merge pull request googleapis#3188 from tseaver/1679-generalize-pubsub-iam-for-storage
Factor common IAM policy bits into 'google.cloud.iam'.
2 parents 1cb4ab0 + def8eff commit f270f5c

File tree

9 files changed

+649
-334
lines changed

9 files changed

+649
-334
lines changed

core/google/cloud/iam.py

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
# Copyright 2017 Google Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
"""Non-API-specific IAM policy definitions
15+
16+
For allowed roles / permissions, see:
17+
https://cloud.google.com/iam/docs/understanding-roles
18+
"""
19+
20+
import collections
21+
import warnings
22+
23+
# Generic IAM roles
24+
25+
OWNER_ROLE = 'roles/owner'
26+
"""Generic role implying all rights to an object."""
27+
28+
EDITOR_ROLE = 'roles/editor'
29+
"""Generic role implying rights to modify an object."""
30+
31+
VIEWER_ROLE = 'roles/viewer'
32+
"""Generic role implying rights to access an object."""
33+
34+
_ASSIGNMENT_DEPRECATED_MSG = """\
35+
Assigning to '{}' is deprecated. Replace with 'policy[{}] = members."""
36+
37+
38+
class Policy(collections.MutableMapping):
39+
"""IAM Policy
40+
41+
See:
42+
https://cloud.google.com/iam/reference/rest/v1/Policy
43+
44+
:type etag: str
45+
:param etag: ETag used to identify a unique of the policy
46+
47+
:type version: int
48+
:param version: unique version of the policy
49+
"""
50+
_OWNER_ROLES = (OWNER_ROLE,)
51+
"""Roles mapped onto our ``owners`` attribute."""
52+
53+
_EDITOR_ROLES = (EDITOR_ROLE,)
54+
"""Roles mapped onto our ``editors`` attribute."""
55+
56+
_VIEWER_ROLES = (VIEWER_ROLE,)
57+
"""Roles mapped onto our ``viewers`` attribute."""
58+
59+
def __init__(self, etag=None, version=None):
60+
self.etag = etag
61+
self.version = version
62+
self._bindings = {}
63+
64+
def __iter__(self):
65+
return iter(self._bindings)
66+
67+
def __len__(self):
68+
return len(self._bindings)
69+
70+
def __getitem__(self, key):
71+
return self._bindings[key]
72+
73+
def __setitem__(self, key, value):
74+
self._bindings[key] = frozenset(value)
75+
76+
def __delitem__(self, key):
77+
del self._bindings[key]
78+
79+
@property
80+
def owners(self):
81+
"""Legacy access to owner role."""
82+
result = set()
83+
for role in self._OWNER_ROLES:
84+
for member in self._bindings.get(role, ()):
85+
result.add(member)
86+
return frozenset(result)
87+
88+
@owners.setter
89+
def owners(self, value):
90+
"""Update owners."""
91+
warnings.warn(
92+
_ASSIGNMENT_DEPRECATED_MSG.format('owners', OWNER_ROLE),
93+
DeprecationWarning)
94+
self._bindings[OWNER_ROLE] = list(value)
95+
96+
@property
97+
def editors(self):
98+
"""Legacy access to editor role."""
99+
result = set()
100+
for role in self._EDITOR_ROLES:
101+
for member in self._bindings.get(role, ()):
102+
result.add(member)
103+
return frozenset(result)
104+
105+
@editors.setter
106+
def editors(self, value):
107+
"""Update editors."""
108+
warnings.warn(
109+
_ASSIGNMENT_DEPRECATED_MSG.format('editors', EDITOR_ROLE),
110+
DeprecationWarning)
111+
self._bindings[EDITOR_ROLE] = list(value)
112+
113+
@property
114+
def viewers(self):
115+
"""Legacy access to viewer role."""
116+
result = set()
117+
for role in self._VIEWER_ROLES:
118+
for member in self._bindings.get(role, ()):
119+
result.add(member)
120+
return frozenset(result)
121+
122+
@viewers.setter
123+
def viewers(self, value):
124+
"""Update viewers."""
125+
warnings.warn(
126+
_ASSIGNMENT_DEPRECATED_MSG.format('viewers', VIEWER_ROLE),
127+
DeprecationWarning)
128+
self._bindings[VIEWER_ROLE] = list(value)
129+
130+
@staticmethod
131+
def user(email):
132+
"""Factory method for a user member.
133+
134+
:type email: str
135+
:param email: E-mail for this particular user.
136+
137+
:rtype: str
138+
:returns: A member string corresponding to the given user.
139+
"""
140+
return 'user:%s' % (email,)
141+
142+
@staticmethod
143+
def service_account(email):
144+
"""Factory method for a service account member.
145+
146+
:type email: str
147+
:param email: E-mail for this particular service account.
148+
149+
:rtype: str
150+
:returns: A member string corresponding to the given service account.
151+
"""
152+
return 'serviceAccount:%s' % (email,)
153+
154+
@staticmethod
155+
def group(email):
156+
"""Factory method for a group member.
157+
158+
:type email: str
159+
:param email: An id or e-mail for this particular group.
160+
161+
:rtype: str
162+
:returns: A member string corresponding to the given group.
163+
"""
164+
return 'group:%s' % (email,)
165+
166+
@staticmethod
167+
def domain(domain):
168+
"""Factory method for a domain member.
169+
170+
:type domain: str
171+
:param domain: The domain for this member.
172+
173+
:rtype: str
174+
:returns: A member string corresponding to the given domain.
175+
"""
176+
return 'domain:%s' % (domain,)
177+
178+
@staticmethod
179+
def all_users():
180+
"""Factory method for a member representing all users.
181+
182+
:rtype: str
183+
:returns: A member string representing all users.
184+
"""
185+
return 'allUsers'
186+
187+
@staticmethod
188+
def authenticated_users():
189+
"""Factory method for a member representing all authenticated users.
190+
191+
:rtype: str
192+
:returns: A member string representing all authenticated users.
193+
"""
194+
return 'allAuthenticatedUsers'
195+
196+
@classmethod
197+
def from_api_repr(cls, resource):
198+
"""Create a policy from the resource returned from the API.
199+
200+
:type resource: dict
201+
:param resource: resource returned from the ``getIamPolicy`` API.
202+
203+
:rtype: :class:`Policy`
204+
:returns: the parsed policy
205+
"""
206+
version = resource.get('version')
207+
etag = resource.get('etag')
208+
policy = cls(etag, version)
209+
for binding in resource.get('bindings', ()):
210+
role = binding['role']
211+
members = sorted(binding['members'])
212+
policy._bindings[role] = members
213+
return policy
214+
215+
def to_api_repr(self):
216+
"""Construct a Policy resource.
217+
218+
:rtype: dict
219+
:returns: a resource to be passed to the ``setIamPolicy`` API.
220+
"""
221+
resource = {}
222+
223+
if self.etag is not None:
224+
resource['etag'] = self.etag
225+
226+
if self.version is not None:
227+
resource['version'] = self.version
228+
229+
if len(self._bindings) > 0:
230+
bindings = resource['bindings'] = []
231+
for role, members in sorted(self._bindings.items()):
232+
if len(members) > 0:
233+
bindings.append(
234+
{'role': role, 'members': sorted(set(members))})
235+
236+
if len(bindings) == 0:
237+
del resource['bindings']
238+
239+
return resource
240+
241+
242+
collections.MutableMapping.register(Policy)

0 commit comments

Comments
 (0)