Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/google/cloud/storage/acl.py: 41%
189 statements
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-25 06:17 +0000
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-25 06:17 +0000
1# Copyright 2014 Google LLC
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.
15"""Manage access to objects and buckets."""
17from google.cloud.storage._helpers import _add_generation_match_parameters
18from google.cloud.storage.constants import _DEFAULT_TIMEOUT
19from google.cloud.storage.retry import DEFAULT_RETRY
20from google.cloud.storage.retry import DEFAULT_RETRY_IF_METAGENERATION_SPECIFIED
23class _ACLEntity(object):
24 """Class representing a set of roles for an entity.
26 This is a helper class that you likely won't ever construct
27 outside of using the factor methods on the :class:`ACL` object.
29 :type entity_type: str
30 :param entity_type: The type of entity (ie, 'group' or 'user').
32 :type identifier: str
33 :param identifier: (Optional) The ID or e-mail of the entity. For the special
34 entity types (like 'allUsers').
35 """
37 READER_ROLE = "READER"
38 WRITER_ROLE = "WRITER"
39 OWNER_ROLE = "OWNER"
41 def __init__(self, entity_type, identifier=None):
42 self.identifier = identifier
43 self.roles = set([])
44 self.type = entity_type
46 def __str__(self):
47 if not self.identifier:
48 return str(self.type)
49 else:
50 return "{acl.type}-{acl.identifier}".format(acl=self)
52 def __repr__(self):
53 return f"<ACL Entity: {self} ({', '.join(self.roles)})>"
55 def get_roles(self):
56 """Get the list of roles permitted by this entity.
58 :rtype: list of strings
59 :returns: The list of roles associated with this entity.
60 """
61 return self.roles
63 def grant(self, role):
64 """Add a role to the entity.
66 :type role: str
67 :param role: The role to add to the entity.
68 """
69 self.roles.add(role)
71 def revoke(self, role):
72 """Remove a role from the entity.
74 :type role: str
75 :param role: The role to remove from the entity.
76 """
77 if role in self.roles:
78 self.roles.remove(role)
80 def grant_read(self):
81 """Grant read access to the current entity."""
82 self.grant(_ACLEntity.READER_ROLE)
84 def grant_write(self):
85 """Grant write access to the current entity."""
86 self.grant(_ACLEntity.WRITER_ROLE)
88 def grant_owner(self):
89 """Grant owner access to the current entity."""
90 self.grant(_ACLEntity.OWNER_ROLE)
92 def revoke_read(self):
93 """Revoke read access from the current entity."""
94 self.revoke(_ACLEntity.READER_ROLE)
96 def revoke_write(self):
97 """Revoke write access from the current entity."""
98 self.revoke(_ACLEntity.WRITER_ROLE)
100 def revoke_owner(self):
101 """Revoke owner access from the current entity."""
102 self.revoke(_ACLEntity.OWNER_ROLE)
105class ACL(object):
106 """Container class representing a list of access controls."""
108 _URL_PATH_ELEM = "acl"
109 _PREDEFINED_QUERY_PARAM = "predefinedAcl"
111 PREDEFINED_XML_ACLS = {
112 # XML API name -> JSON API name
113 "project-private": "projectPrivate",
114 "public-read": "publicRead",
115 "public-read-write": "publicReadWrite",
116 "authenticated-read": "authenticatedRead",
117 "bucket-owner-read": "bucketOwnerRead",
118 "bucket-owner-full-control": "bucketOwnerFullControl",
119 }
121 PREDEFINED_JSON_ACLS = frozenset(
122 [
123 "private",
124 "projectPrivate",
125 "publicRead",
126 "publicReadWrite",
127 "authenticatedRead",
128 "bucketOwnerRead",
129 "bucketOwnerFullControl",
130 ]
131 )
132 """See
133 https://cloud.google.com/storage/docs/access-control/lists#predefined-acl
134 """
136 loaded = False
138 # Subclasses must override to provide these attributes (typically,
139 # as properties).
140 reload_path = None
141 save_path = None
142 user_project = None
144 def __init__(self):
145 self.entities = {}
147 def _ensure_loaded(self, timeout=_DEFAULT_TIMEOUT):
148 """Load if not already loaded.
150 :type timeout: float or tuple
151 :param timeout:
152 (Optional) The amount of time, in seconds, to wait
153 for the server response. See: :ref:`configuring_timeouts`
154 """
155 if not self.loaded:
156 self.reload(timeout=timeout)
158 @classmethod
159 def validate_predefined(cls, predefined):
160 """Ensures predefined is in list of predefined json values
162 :type predefined: str
163 :param predefined: name of a predefined acl
165 :type predefined: str
166 :param predefined: validated JSON name of predefined acl
168 :raises: :exc: `ValueError`: If predefined is not a valid acl
169 """
170 predefined = cls.PREDEFINED_XML_ACLS.get(predefined, predefined)
171 if predefined and predefined not in cls.PREDEFINED_JSON_ACLS:
172 raise ValueError(f"Invalid predefined ACL: {predefined}")
173 return predefined
175 def reset(self):
176 """Remove all entities from the ACL, and clear the ``loaded`` flag."""
177 self.entities.clear()
178 self.loaded = False
180 def __iter__(self):
181 self._ensure_loaded()
183 for entity in self.entities.values():
184 for role in entity.get_roles():
185 if role:
186 yield {"entity": str(entity), "role": role}
188 def entity_from_dict(self, entity_dict):
189 """Build an _ACLEntity object from a dictionary of data.
191 An entity is a mutable object that represents a list of roles
192 belonging to either a user or group or the special types for all
193 users and all authenticated users.
195 :type entity_dict: dict
196 :param entity_dict: Dictionary full of data from an ACL lookup.
198 :rtype: :class:`_ACLEntity`
199 :returns: An Entity constructed from the dictionary.
200 """
201 entity = entity_dict["entity"]
202 role = entity_dict["role"]
204 if entity == "allUsers":
205 entity = self.all()
207 elif entity == "allAuthenticatedUsers":
208 entity = self.all_authenticated()
210 elif "-" in entity:
211 entity_type, identifier = entity.split("-", 1)
212 entity = self.entity(entity_type=entity_type, identifier=identifier)
214 if not isinstance(entity, _ACLEntity):
215 raise ValueError(f"Invalid dictionary: {entity_dict}")
217 entity.grant(role)
218 return entity
220 def has_entity(self, entity):
221 """Returns whether or not this ACL has any entries for an entity.
223 :type entity: :class:`_ACLEntity`
224 :param entity: The entity to check for existence in this ACL.
226 :rtype: bool
227 :returns: True of the entity exists in the ACL.
228 """
229 self._ensure_loaded()
230 return str(entity) in self.entities
232 def get_entity(self, entity, default=None):
233 """Gets an entity object from the ACL.
235 :type entity: :class:`_ACLEntity` or string
236 :param entity: The entity to get lookup in the ACL.
238 :type default: anything
239 :param default: This value will be returned if the entity
240 doesn't exist.
242 :rtype: :class:`_ACLEntity`
243 :returns: The corresponding entity or the value provided
244 to ``default``.
245 """
246 self._ensure_loaded()
247 return self.entities.get(str(entity), default)
249 def add_entity(self, entity):
250 """Add an entity to the ACL.
252 :type entity: :class:`_ACLEntity`
253 :param entity: The entity to add to this ACL.
254 """
255 self._ensure_loaded()
256 self.entities[str(entity)] = entity
258 def entity(self, entity_type, identifier=None):
259 """Factory method for creating an Entity.
261 If an entity with the same type and identifier already exists,
262 this will return a reference to that entity. If not, it will
263 create a new one and add it to the list of known entities for
264 this ACL.
266 :type entity_type: str
267 :param entity_type: The type of entity to create
268 (ie, ``user``, ``group``, etc)
270 :type identifier: str
271 :param identifier: The ID of the entity (if applicable).
272 This can be either an ID or an e-mail address.
274 :rtype: :class:`_ACLEntity`
275 :returns: A new Entity or a reference to an existing identical entity.
276 """
277 entity = _ACLEntity(entity_type=entity_type, identifier=identifier)
278 if self.has_entity(entity):
279 entity = self.get_entity(entity)
280 else:
281 self.add_entity(entity)
282 return entity
284 def user(self, identifier):
285 """Factory method for a user Entity.
287 :type identifier: str
288 :param identifier: An id or e-mail for this particular user.
290 :rtype: :class:`_ACLEntity`
291 :returns: An Entity corresponding to this user.
292 """
293 return self.entity("user", identifier=identifier)
295 def group(self, identifier):
296 """Factory method for a group Entity.
298 :type identifier: str
299 :param identifier: An id or e-mail for this particular group.
301 :rtype: :class:`_ACLEntity`
302 :returns: An Entity corresponding to this group.
303 """
304 return self.entity("group", identifier=identifier)
306 def domain(self, domain):
307 """Factory method for a domain Entity.
309 :type domain: str
310 :param domain: The domain for this entity.
312 :rtype: :class:`_ACLEntity`
313 :returns: An entity corresponding to this domain.
314 """
315 return self.entity("domain", identifier=domain)
317 def all(self):
318 """Factory method for an Entity representing all users.
320 :rtype: :class:`_ACLEntity`
321 :returns: An entity representing all users.
322 """
323 return self.entity("allUsers")
325 def all_authenticated(self):
326 """Factory method for an Entity representing all authenticated users.
328 :rtype: :class:`_ACLEntity`
329 :returns: An entity representing all authenticated users.
330 """
331 return self.entity("allAuthenticatedUsers")
333 def get_entities(self):
334 """Get a list of all Entity objects.
336 :rtype: list of :class:`_ACLEntity` objects
337 :returns: A list of all Entity objects.
338 """
339 self._ensure_loaded()
340 return list(self.entities.values())
342 @property
343 def client(self):
344 """Abstract getter for the object client."""
345 raise NotImplementedError
347 def _require_client(self, client):
348 """Check client or verify over-ride.
350 :type client: :class:`~google.cloud.storage.client.Client` or
351 ``NoneType``
352 :param client: the client to use. If not passed, falls back to the
353 ``client`` stored on the current ACL.
355 :rtype: :class:`google.cloud.storage.client.Client`
356 :returns: The client passed in or the currently bound client.
357 """
358 if client is None:
359 client = self.client
360 return client
362 def reload(self, client=None, timeout=_DEFAULT_TIMEOUT, retry=DEFAULT_RETRY):
363 """Reload the ACL data from Cloud Storage.
365 If :attr:`user_project` is set, bills the API request to that project.
367 :type client: :class:`~google.cloud.storage.client.Client` or
368 ``NoneType``
369 :param client: (Optional) The client to use. If not passed, falls back
370 to the ``client`` stored on the ACL's parent.
371 :type timeout: float or tuple
372 :param timeout:
373 (Optional) The amount of time, in seconds, to wait
374 for the server response. See: :ref:`configuring_timeouts`
376 :type retry: :class:`~google.api_core.retry.Retry`
377 :param retry:
378 (Optional) How to retry the RPC. See: :ref:`configuring_retries`
379 """
380 path = self.reload_path
381 client = self._require_client(client)
382 query_params = {}
384 if self.user_project is not None:
385 query_params["userProject"] = self.user_project
387 self.entities.clear()
389 found = client._get_resource(
390 path,
391 query_params=query_params,
392 timeout=timeout,
393 retry=retry,
394 )
395 self.loaded = True
397 for entry in found.get("items", ()):
398 self.add_entity(self.entity_from_dict(entry))
400 def _save(
401 self,
402 acl,
403 predefined,
404 client,
405 if_generation_match=None,
406 if_generation_not_match=None,
407 if_metageneration_match=None,
408 if_metageneration_not_match=None,
409 timeout=_DEFAULT_TIMEOUT,
410 retry=DEFAULT_RETRY_IF_METAGENERATION_SPECIFIED,
411 ):
412 """Helper for :meth:`save` and :meth:`save_predefined`.
414 :type acl: :class:`google.cloud.storage.acl.ACL`, or a compatible list.
415 :param acl: The ACL object to save. If left blank, this will save
416 current entries.
418 :type predefined: str
419 :param predefined: An identifier for a predefined ACL. Must be one of the
420 keys in :attr:`PREDEFINED_JSON_ACLS` If passed, `acl` must be None.
422 :type client: :class:`~google.cloud.storage.client.Client` or
423 ``NoneType``
424 :param client: (Optional) The client to use. If not passed, falls back
425 to the ``client`` stored on the ACL's parent.
427 :type if_generation_match: long
428 :param if_generation_match:
429 (Optional) See :ref:`using-if-generation-match`
431 :type if_generation_not_match: long
432 :param if_generation_not_match:
433 (Optional) See :ref:`using-if-generation-not-match`
435 :type if_metageneration_match: long
436 :param if_metageneration_match:
437 (Optional) See :ref:`using-if-metageneration-match`
439 :type if_metageneration_not_match: long
440 :param if_metageneration_not_match:
441 (Optional) See :ref:`using-if-metageneration-not-match`
443 :type timeout: float or tuple
444 :param timeout:
445 (Optional) The amount of time, in seconds, to wait
446 for the server response. See: :ref:`configuring_timeouts`
448 :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
449 :param retry:
450 (Optional) How to retry the RPC. See: :ref:`configuring_retries`
451 """
452 client = self._require_client(client)
453 query_params = {"projection": "full"}
455 if predefined is not None:
456 acl = []
457 query_params[self._PREDEFINED_QUERY_PARAM] = predefined
459 if self.user_project is not None:
460 query_params["userProject"] = self.user_project
462 _add_generation_match_parameters(
463 query_params,
464 if_generation_match=if_generation_match,
465 if_generation_not_match=if_generation_not_match,
466 if_metageneration_match=if_metageneration_match,
467 if_metageneration_not_match=if_metageneration_not_match,
468 )
470 path = self.save_path
472 result = client._patch_resource(
473 path,
474 {self._URL_PATH_ELEM: list(acl)},
475 query_params=query_params,
476 timeout=timeout,
477 retry=retry,
478 )
480 self.entities.clear()
482 for entry in result.get(self._URL_PATH_ELEM, ()):
483 self.add_entity(self.entity_from_dict(entry))
485 self.loaded = True
487 def save(
488 self,
489 acl=None,
490 client=None,
491 if_generation_match=None,
492 if_generation_not_match=None,
493 if_metageneration_match=None,
494 if_metageneration_not_match=None,
495 timeout=_DEFAULT_TIMEOUT,
496 retry=DEFAULT_RETRY_IF_METAGENERATION_SPECIFIED,
497 ):
498 """Save this ACL for the current bucket.
500 If :attr:`user_project` is set, bills the API request to that project.
502 :type acl: :class:`google.cloud.storage.acl.ACL`, or a compatible list.
503 :param acl: The ACL object to save. If left blank, this will save
504 current entries.
506 :type client: :class:`~google.cloud.storage.client.Client` or
507 ``NoneType``
508 :param client: (Optional) The client to use. If not passed, falls back
509 to the ``client`` stored on the ACL's parent.
511 :type if_generation_match: long
512 :param if_generation_match:
513 (Optional) See :ref:`using-if-generation-match`
515 :type if_generation_not_match: long
516 :param if_generation_not_match:
517 (Optional) See :ref:`using-if-generation-not-match`
519 :type if_metageneration_match: long
520 :param if_metageneration_match:
521 (Optional) See :ref:`using-if-metageneration-match`
523 :type if_metageneration_not_match: long
524 :param if_metageneration_not_match:
525 (Optional) See :ref:`using-if-metageneration-not-match`
527 :type timeout: float or tuple
528 :param timeout:
529 (Optional) The amount of time, in seconds, to wait
530 for the server response. See: :ref:`configuring_timeouts`
532 :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
533 :param retry:
534 (Optional) How to retry the RPC. See: :ref:`configuring_retries`
535 """
536 if acl is None:
537 acl = self
538 save_to_backend = acl.loaded
539 else:
540 save_to_backend = True
542 if save_to_backend:
543 self._save(
544 acl,
545 None,
546 client,
547 if_generation_match=if_generation_match,
548 if_generation_not_match=if_generation_not_match,
549 if_metageneration_match=if_metageneration_match,
550 if_metageneration_not_match=if_metageneration_not_match,
551 timeout=timeout,
552 retry=retry,
553 )
555 def save_predefined(
556 self,
557 predefined,
558 client=None,
559 if_generation_match=None,
560 if_generation_not_match=None,
561 if_metageneration_match=None,
562 if_metageneration_not_match=None,
563 timeout=_DEFAULT_TIMEOUT,
564 retry=DEFAULT_RETRY_IF_METAGENERATION_SPECIFIED,
565 ):
566 """Save this ACL for the current bucket using a predefined ACL.
568 If :attr:`user_project` is set, bills the API request to that project.
570 :type predefined: str
571 :param predefined: An identifier for a predefined ACL. Must be one
572 of the keys in :attr:`PREDEFINED_JSON_ACLS`
573 or :attr:`PREDEFINED_XML_ACLS` (which will be
574 aliased to the corresponding JSON name).
575 If passed, `acl` must be None.
577 :type client: :class:`~google.cloud.storage.client.Client` or
578 ``NoneType``
579 :param client: (Optional) The client to use. If not passed, falls back
580 to the ``client`` stored on the ACL's parent.
582 :type if_generation_match: long
583 :param if_generation_match:
584 (Optional) See :ref:`using-if-generation-match`
586 :type if_generation_not_match: long
587 :param if_generation_not_match:
588 (Optional) See :ref:`using-if-generation-not-match`
590 :type if_metageneration_match: long
591 :param if_metageneration_match:
592 (Optional) See :ref:`using-if-metageneration-match`
594 :type if_metageneration_not_match: long
595 :param if_metageneration_not_match:
596 (Optional) See :ref:`using-if-metageneration-not-match`
598 :type timeout: float or tuple
599 :param timeout:
600 (Optional) The amount of time, in seconds, to wait
601 for the server response. See: :ref:`configuring_timeouts`
603 :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
604 :param retry:
605 (Optional) How to retry the RPC. See: :ref:`configuring_retries`
606 """
607 predefined = self.validate_predefined(predefined)
608 self._save(
609 None,
610 predefined,
611 client,
612 if_generation_match=if_generation_match,
613 if_generation_not_match=if_generation_not_match,
614 if_metageneration_match=if_metageneration_match,
615 if_metageneration_not_match=if_metageneration_not_match,
616 timeout=timeout,
617 retry=retry,
618 )
620 def clear(
621 self,
622 client=None,
623 if_generation_match=None,
624 if_generation_not_match=None,
625 if_metageneration_match=None,
626 if_metageneration_not_match=None,
627 timeout=_DEFAULT_TIMEOUT,
628 retry=DEFAULT_RETRY_IF_METAGENERATION_SPECIFIED,
629 ):
630 """Remove all ACL entries.
632 If :attr:`user_project` is set, bills the API request to that project.
634 Note that this won't actually remove *ALL* the rules, but it
635 will remove all the non-default rules. In short, you'll still
636 have access to a bucket that you created even after you clear
637 ACL rules with this method.
639 :type client: :class:`~google.cloud.storage.client.Client` or
640 ``NoneType``
641 :param client: (Optional) The client to use. If not passed, falls back
642 to the ``client`` stored on the ACL's parent.
644 :type if_generation_match: long
645 :param if_generation_match:
646 (Optional) See :ref:`using-if-generation-match`
648 :type if_generation_not_match: long
649 :param if_generation_not_match:
650 (Optional) See :ref:`using-if-generation-not-match`
652 :type if_metageneration_match: long
653 :param if_metageneration_match:
654 (Optional) See :ref:`using-if-metageneration-match`
656 :type if_metageneration_not_match: long
657 :param if_metageneration_not_match:
658 (Optional) See :ref:`using-if-metageneration-not-match`
660 :type timeout: float or tuple
661 :param timeout:
662 (Optional) The amount of time, in seconds, to wait
663 for the server response. See: :ref:`configuring_timeouts`
665 :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
666 :param retry:
667 (Optional) How to retry the RPC. See: :ref:`configuring_retries`
668 """
669 self.save(
670 [],
671 client=client,
672 if_generation_match=if_generation_match,
673 if_generation_not_match=if_generation_not_match,
674 if_metageneration_match=if_metageneration_match,
675 if_metageneration_not_match=if_metageneration_not_match,
676 timeout=timeout,
677 retry=retry,
678 )
681class BucketACL(ACL):
682 """An ACL specifically for a bucket.
684 :type bucket: :class:`google.cloud.storage.bucket.Bucket`
685 :param bucket: The bucket to which this ACL relates.
686 """
688 def __init__(self, bucket):
689 super(BucketACL, self).__init__()
690 self.bucket = bucket
692 @property
693 def client(self):
694 """The client bound to this ACL's bucket."""
695 return self.bucket.client
697 @property
698 def reload_path(self):
699 """Compute the path for GET API requests for this ACL."""
700 return f"{self.bucket.path}/{self._URL_PATH_ELEM}"
702 @property
703 def save_path(self):
704 """Compute the path for PATCH API requests for this ACL."""
705 return self.bucket.path
707 @property
708 def user_project(self):
709 """Compute the user project charged for API requests for this ACL."""
710 return self.bucket.user_project
713class DefaultObjectACL(BucketACL):
714 """A class representing the default object ACL for a bucket."""
716 _URL_PATH_ELEM = "defaultObjectAcl"
717 _PREDEFINED_QUERY_PARAM = "predefinedDefaultObjectAcl"
720class ObjectACL(ACL):
721 """An ACL specifically for a Cloud Storage object / blob.
723 :type blob: :class:`google.cloud.storage.blob.Blob`
724 :param blob: The blob that this ACL corresponds to.
725 """
727 def __init__(self, blob):
728 super(ObjectACL, self).__init__()
729 self.blob = blob
731 @property
732 def client(self):
733 """The client bound to this ACL's blob."""
734 return self.blob.client
736 @property
737 def reload_path(self):
738 """Compute the path for GET API requests for this ACL."""
739 return f"{self.blob.path}/acl"
741 @property
742 def save_path(self):
743 """Compute the path for PATCH API requests for this ACL."""
744 return self.blob.path
746 @property
747 def user_project(self):
748 """Compute the user project charged for API requests for this ACL."""
749 return self.blob.user_project