Skip to content

Commit

Permalink
Merge pull request #1217 from tseaver/651-support_predefined_acls_by_…
Browse files Browse the repository at this point in the history
…name

Add 'ACL.save_predefined' method.
  • Loading branch information
tseaver committed Nov 17, 2015
2 parents 5d589a1 + 14fe52d commit 35730b4
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 13 deletions.
78 changes: 67 additions & 11 deletions gcloud/storage/acl.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,21 @@ class ACL(object):
"""Container class representing a list of access controls."""

_URL_PATH_ELEM = 'acl'
_PREDEFINED_QUERY_PARAM = 'predefinedAcl'

_PREDEFINED_ACLS = frozenset([
'private',
'project-private',
'public-read',
'public-read-write',
'authenticated-read',
'bucket-owner-read',
'bucket-owner-full-control',
])
"""See:
https://cloud.google.com/storage/docs/access-control#predefined-acl
"""

loaded = False

# Subclasses must override to provide these attributes (typically,
Expand Down Expand Up @@ -385,6 +400,39 @@ def reload(self, client=None):
for entry in found.get('items', ()):
self.add_entity(self.entity_from_dict(entry))

def _save(self, acl, predefined, client):
"""Helper for :meth:`save` and :meth:`save_predefined`.
:type acl: :class:`gcloud.storage.acl.ACL`, or a compatible list.
:param acl: The ACL object to save. If left blank, this will save
current entries.
:type predefined: string or None
:param predefined: An identifier for a predefined ACL. Must be one
of the keys in :attr:`_PREDEFINED_ACLS`
If passed, `acl` must be None.
:type client: :class:`gcloud.storage.client.Client` or ``NoneType``
:param client: Optional. The client to use. If not passed, falls back
to the ``client`` stored on the ACL's parent.
"""
query_params = {'projection': 'full'}
if predefined is not None:
acl = []
query_params[self._PREDEFINED_QUERY_PARAM] = predefined

path = self.save_path
client = self._require_client(client)
result = client.connection.api_request(
method='PATCH',
path=path,
data={self._URL_PATH_ELEM: list(acl)},
query_params=query_params)
self.entities.clear()
for entry in result.get(self._URL_PATH_ELEM, ()):
self.add_entity(self.entity_from_dict(entry))
self.loaded = True

def save(self, acl=None, client=None):
"""Save this ACL for the current bucket.
Expand All @@ -403,17 +451,24 @@ def save(self, acl=None, client=None):
save_to_backend = True

if save_to_backend:
path = self.save_path
client = self._require_client(client)
result = client.connection.api_request(
method='PATCH',
path=path,
data={self._URL_PATH_ELEM: list(acl)},
query_params={'projection': 'full'})
self.entities.clear()
for entry in result.get(self._URL_PATH_ELEM, ()):
self.add_entity(self.entity_from_dict(entry))
self.loaded = True
self._save(acl, None, client)

def save_predefined(self, predefined, client=None):
"""Save this ACL for the current bucket using a predefined ACL.
:type predefined: string
:param predefined: An identifier for a predefined ACL. Must be one
of the keys in :attr:`_PREDEFINED_ACLS`
If passed, `acl` must be None.
:type client: :class:`gcloud.storage.client.Client` or ``NoneType``
:param client: Optional. The client to use. If not passed, falls back
to the ``client`` stored on the ACL's parent.
"""
if predefined not in self._PREDEFINED_ACLS:
raise ValueError("Invalid predefined ACL: %s" % (predefined,))

self._save(None, predefined, client)

def clear(self, client=None):
"""Remove all ACL entries.
Expand Down Expand Up @@ -461,6 +516,7 @@ class DefaultObjectACL(BucketACL):
"""A class representing the default object ACL for a bucket."""

_URL_PATH_ELEM = 'defaultObjectAcl'
_PREDEFINED_QUERY_PARAM = 'predefinedDefaultObjectAcl'


class ObjectACL(ACL):
Expand Down
51 changes: 49 additions & 2 deletions gcloud/storage/test_acl.py
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ def test_save_existing_missing_none_passed(self):
self.assertEqual(kw[0]['data'], {'acl': []})
self.assertEqual(kw[0]['query_params'], {'projection': 'full'})

def test_save_no_arg(self):
def test_save_no_acl(self):
ROLE = 'role'
AFTER = [{'entity': 'allUsers', 'role': ROLE}]
connection = _Connection({'acl': AFTER})
Expand All @@ -599,7 +599,7 @@ def test_save_no_arg(self):
self.assertEqual(kw[0]['data'], {'acl': AFTER})
self.assertEqual(kw[0]['query_params'], {'projection': 'full'})

def test_save_w_arg(self):
def test_save_w_acl(self):
ROLE1 = 'role1'
ROLE2 = 'role2'
STICKY = {'entity': 'allUsers', 'role': ROLE2}
Expand All @@ -621,6 +621,53 @@ def test_save_w_arg(self):
self.assertEqual(kw[0]['data'], {'acl': new_acl})
self.assertEqual(kw[0]['query_params'], {'projection': 'full'})

def test_save_prefefined_invalid(self):
connection = _Connection()
client = _Client(connection)
acl = self._makeOne()
acl.save_path = '/testing'
acl.loaded = True
with self.assertRaises(ValueError):
acl.save_predefined('bogus', client=client)

def test_save_predefined_valid(self):
PREDEFINED = 'private'
connection = _Connection({'acl': []})
client = _Client(connection)
acl = self._makeOne()
acl.save_path = '/testing'
acl.loaded = True
acl.save_predefined(PREDEFINED, client=client)
entries = list(acl)
self.assertEqual(len(entries), 0)
kw = connection._requested
self.assertEqual(len(kw), 1)
self.assertEqual(kw[0]['method'], 'PATCH')
self.assertEqual(kw[0]['path'], '/testing')
self.assertEqual(kw[0]['data'], {'acl': []})
self.assertEqual(kw[0]['query_params'],
{'projection': 'full', 'predefinedAcl': PREDEFINED})

def test_save_predefined_valid_w_alternate_query_param(self):
# Cover case where subclass overrides _PREDEFINED_QUERY_PARAM
PREDEFINED = 'private'
connection = _Connection({'acl': []})
client = _Client(connection)
acl = self._makeOne()
acl.save_path = '/testing'
acl.loaded = True
acl._PREDEFINED_QUERY_PARAM = 'alternate'
acl.save_predefined(PREDEFINED, client=client)
entries = list(acl)
self.assertEqual(len(entries), 0)
kw = connection._requested
self.assertEqual(len(kw), 1)
self.assertEqual(kw[0]['method'], 'PATCH')
self.assertEqual(kw[0]['path'], '/testing')
self.assertEqual(kw[0]['data'], {'acl': []})
self.assertEqual(kw[0]['query_params'],
{'projection': 'full', 'alternate': PREDEFINED})

def test_clear(self):
ROLE1 = 'role1'
ROLE2 = 'role2'
Expand Down

0 comments on commit 35730b4

Please sign in to comment.