Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add 'ACL.save_predefined' method.
Allows setting ACL to a named, predefined value.

Closes: #651.
  • Loading branch information
tseaver committed Nov 10, 2015
commit 1a210341811aff3b4185f830b07e87b22c1ef73d
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",

This comment was marked as spam.

This comment was marked as spam.

"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):

This comment was marked as spam.

"""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