Skip to content

Commit 9cfe627

Browse files
committed
Move APIs for reload/save/clear ACLs onto ACL objects themselves.
Fixes #324.
1 parent 417333c commit 9cfe627

File tree

6 files changed

+434
-583
lines changed

6 files changed

+434
-583
lines changed

gcloud/storage/acl.py

Lines changed: 160 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,6 @@ class ACL(object):
172172
def __init__(self):
173173
self.entities = {}
174174

175-
def clear(self):
176-
"""Remove all entities from the ACL."""
177-
self.entities.clear()
178-
179175
def reset(self):
180176
"""Remove all entities from the ACL, and clear the ``loaded`` flag."""
181177
self.entities.clear()
@@ -338,17 +334,35 @@ def get_entities(self):
338334
"""
339335
return self.entities.values()
340336

341-
def save(self):
337+
def reload(self):
338+
"""Reload the ACL data from Cloud Storage.
339+
340+
:rtype: :class:`ACL`
341+
:returns: The current ACL.
342+
"""
343+
raise NotImplementedError
344+
345+
def save(self, acl=None):
342346
"""A method to be overridden by subclasses.
343347
348+
:type acl: :class:`gcloud.storage.acl.ACL`, or a compatible list.
349+
:param acl: The ACL object to save. If left blank, this will save
350+
current entries.
351+
344352
:raises: NotImplementedError
345353
"""
346354
raise NotImplementedError
347355

356+
def clear(self):
357+
"""Remove all entities from the ACL."""
358+
raise NotImplementedError
359+
348360

349361
class BucketACL(ACL):
350362
"""An ACL specifically for a bucket."""
351363

364+
_SUBKEY = 'acl'
365+
352366
def __init__(self, bucket):
353367
"""
354368
:type bucket: :class:`gcloud.storage.bucket.Bucket`
@@ -357,19 +371,103 @@ def __init__(self, bucket):
357371
super(BucketACL, self).__init__()
358372
self.bucket = bucket
359373

360-
def save(self):
361-
"""Save this ACL for the current bucket."""
374+
def reload(self):
375+
"""Reload the ACL data from Cloud Storage.
376+
377+
:rtype: :class:`gcloud.storage.acl.BucketACL`
378+
:returns: The current ACL.
379+
"""
380+
self.entities.clear()
381+
382+
url_path = '%s/%s' % (self.bucket.path, self._SUBKEY)
383+
found = self.bucket.connection.api_request(method='GET', path=url_path)
384+
for entry in found['items']:
385+
self.add_entity(self.entity_from_dict(entry))
386+
387+
# Even if we fetch no entries, the ACL is still loaded.
388+
self.loaded = True
389+
390+
return self
391+
392+
def save(self, acl=None):
393+
"""Save this ACL for the current bucket.
394+
395+
If called without arguments, this will save the entries
396+
currently stored on this ACL::
362397
363-
return self.bucket.save_acl(acl=self)
398+
>>> acl.save()
399+
400+
You can also provide a specific ACL to save instead of the one
401+
currently set on the Bucket object::
402+
403+
>>> acl.save(acl=my_other_acl)
404+
405+
You can use this to set access controls to be consistent from
406+
one bucket to another::
407+
408+
>>> bucket1 = connection.get_bucket(bucket1_name)
409+
>>> bucket2 = connection.get_bucket(bucket2_name)
410+
>>> bucket2.acl.save(bucket1.get_acl())
411+
412+
:type acl: :class:`gcloud.storage.acl.ACL`, or a compatible list.
413+
:param acl: The ACL object to save. If left blank, this will save
414+
current entries.
415+
416+
:rtype: :class:`gcloud.storage.acl.BucketACL`
417+
:returns: The current ACL.
418+
"""
419+
# We do things in this weird way because [] and None
420+
# both evaluate to False, but mean very different things.
421+
if acl is None:
422+
acl = self
423+
dirty = acl.loaded
424+
else:
425+
dirty = True
426+
427+
if dirty:
428+
result = self.bucket.connection.api_request(
429+
method='PATCH', path=self.bucket.path,
430+
data={self._SUBKEY: list(acl)},
431+
query_params={'projection': 'full'})
432+
self.entities.clear()
433+
for entry in result[self._SUBKEY]:
434+
self.entity(self.entity_from_dict(entry))
435+
self.loaded = True
436+
437+
return self
438+
439+
def clear(self):
440+
"""Remove all ACL entries.
441+
442+
Note that this won't actually remove *ALL* the rules, but it
443+
will remove all the non-default rules. In short, you'll still
444+
have access to a bucket that you created even after you clear
445+
ACL rules with this method.
446+
447+
For example, imagine that you granted access to this bucket to a
448+
bunch of coworkers::
449+
450+
>>> acl.user('[email protected]').grant_read()
451+
>>> acl.user('[email protected]').grant_read()
452+
>>> acl.save()
453+
454+
Now they work in another part of the company and you want to
455+
'start fresh' on who has access::
456+
457+
>>> acl.clear()
458+
459+
At this point all the custom rules you created have been removed.
460+
461+
:rtype: :class:`gcloud.storage.acl.BucketACL`
462+
:returns: The current ACL.
463+
"""
464+
return self.save([])
364465

365466

366467
class DefaultObjectACL(BucketACL):
367468
"""A class representing the default object ACL for a bucket."""
368469

369-
def save(self):
370-
"""Save this ACL as the default object ACL for the current bucket."""
371-
372-
return self.bucket.save_default_object_acl(acl=self)
470+
_SUBKEY = 'defaultObjectAcl'
373471

374472

375473
class ObjectACL(ACL):
@@ -383,7 +481,54 @@ def __init__(self, key):
383481
super(ObjectACL, self).__init__()
384482
self.key = key
385483

386-
def save(self):
387-
"""Save this ACL for the current key."""
484+
def reload(self):
485+
"""Reload the ACL data from Cloud Storage.
486+
487+
:rtype: :class:`ObjectACL`
488+
:returns: The current ACL.
489+
"""
490+
self.entities.clear()
388491

389-
return self.key.save_acl(acl=self)
492+
url_path = '%s/acl' % self.key.path
493+
found = self.key.connection.api_request(method='GET', path=url_path)
494+
for entry in found['items']:
495+
self.add_entity(self.entity_from_dict(entry))
496+
497+
# Even if we fetch no entries, the ACL is still loaded.
498+
self.loaded = True
499+
500+
return self
501+
502+
def save(self, acl=None):
503+
"""Save the ACL data for this key.
504+
505+
:type acl: :class:`gcloud.storage.acl.ACL`
506+
:param acl: The ACL object to save. If left blank, this will
507+
save the entries set locally on the ACL.
508+
"""
509+
if acl is None:
510+
acl = self
511+
dirty = acl.loaded
512+
else:
513+
dirty = True
514+
515+
if dirty:
516+
result = self.key.connection.api_request(
517+
method='PATCH', path=self.key.path, data={'acl': list(acl)},
518+
query_params={'projection': 'full'})
519+
self.entities.clear()
520+
for entry in result['acl']:
521+
self.entity(self.entity_from_dict(entry))
522+
self.loaded = True
523+
524+
return self
525+
526+
def clear(self):
527+
"""Remove all ACL rules from the key.
528+
529+
Note that this won't actually remove *ALL* the rules, but it
530+
will remove all the non-default rules. In short, you'll still
531+
have access to a key that you created even after you clear ACL
532+
rules with this method.
533+
"""
534+
return self.save([])

0 commit comments

Comments
 (0)