Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/google/cloud/storage/acl.py: 42%
190 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 07:13 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 07:13 +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 client = None
141 reload_path = None
142 save_path = None
143 user_project = None
145 def __init__(self):
146 self.entities = {}
148 def _ensure_loaded(self, timeout=_DEFAULT_TIMEOUT):
149 """Load if not already loaded.
151 :type timeout: float or tuple
152 :param timeout:
153 (Optional) The amount of time, in seconds, to wait
154 for the server response. See: :ref:`configuring_timeouts`
155 """
156 if not self.loaded:
157 self.reload(timeout=timeout)
159 @classmethod
160 def validate_predefined(cls, predefined):
161 """Ensures predefined is in list of predefined json values
163 :type predefined: str
164 :param predefined: name of a predefined acl
166 :type predefined: str
167 :param predefined: validated JSON name of predefined acl
169 :raises: :exc: `ValueError`: If predefined is not a valid acl
170 """
171 predefined = cls.PREDEFINED_XML_ACLS.get(predefined, predefined)
172 if predefined and predefined not in cls.PREDEFINED_JSON_ACLS:
173 raise ValueError(f"Invalid predefined ACL: {predefined}")
174 return predefined
176 def reset(self):
177 """Remove all entities from the ACL, and clear the ``loaded`` flag."""
178 self.entities.clear()
179 self.loaded = False
181 def __iter__(self):
182 self._ensure_loaded()
184 for entity in self.entities.values():
185 for role in entity.get_roles():
186 if role:
187 yield {"entity": str(entity), "role": role}
189 def entity_from_dict(self, entity_dict):
190 """Build an _ACLEntity object from a dictionary of data.
192 An entity is a mutable object that represents a list of roles
193 belonging to either a user or group or the special types for all
194 users and all authenticated users.
196 :type entity_dict: dict
197 :param entity_dict: Dictionary full of data from an ACL lookup.
199 :rtype: :class:`_ACLEntity`
200 :returns: An Entity constructed from the dictionary.
201 """
202 entity = entity_dict["entity"]
203 role = entity_dict["role"]
205 if entity == "allUsers":
206 entity = self.all()
208 elif entity == "allAuthenticatedUsers":
209 entity = self.all_authenticated()
211 elif "-" in entity:
212 entity_type, identifier = entity.split("-", 1)
213 entity = self.entity(entity_type=entity_type, identifier=identifier)
215 if not isinstance(entity, _ACLEntity):
216 raise ValueError(f"Invalid dictionary: {entity_dict}")
218 entity.grant(role)
219 return entity
221 def has_entity(self, entity):
222 """Returns whether or not this ACL has any entries for an entity.
224 :type entity: :class:`_ACLEntity`
225 :param entity: The entity to check for existence in this ACL.
227 :rtype: bool
228 :returns: True of the entity exists in the ACL.
229 """
230 self._ensure_loaded()
231 return str(entity) in self.entities
233 def get_entity(self, entity, default=None):
234 """Gets an entity object from the ACL.
236 :type entity: :class:`_ACLEntity` or string
237 :param entity: The entity to get lookup in the ACL.
239 :type default: anything
240 :param default: This value will be returned if the entity
241 doesn't exist.
243 :rtype: :class:`_ACLEntity`
244 :returns: The corresponding entity or the value provided
245 to ``default``.
246 """
247 self._ensure_loaded()
248 return self.entities.get(str(entity), default)
250 def add_entity(self, entity):
251 """Add an entity to the ACL.
253 :type entity: :class:`_ACLEntity`
254 :param entity: The entity to add to this ACL.
255 """
256 self._ensure_loaded()
257 self.entities[str(entity)] = entity
259 def entity(self, entity_type, identifier=None):
260 """Factory method for creating an Entity.
262 If an entity with the same type and identifier already exists,
263 this will return a reference to that entity. If not, it will
264 create a new one and add it to the list of known entities for
265 this ACL.
267 :type entity_type: str
268 :param entity_type: The type of entity to create
269 (ie, ``user``, ``group``, etc)
271 :type identifier: str
272 :param identifier: The ID of the entity (if applicable).
273 This can be either an ID or an e-mail address.
275 :rtype: :class:`_ACLEntity`
276 :returns: A new Entity or a reference to an existing identical entity.
277 """
278 entity = _ACLEntity(entity_type=entity_type, identifier=identifier)
279 if self.has_entity(entity):
280 entity = self.get_entity(entity)
281 else:
282 self.add_entity(entity)
283 return entity
285 def user(self, identifier):
286 """Factory method for a user Entity.
288 :type identifier: str
289 :param identifier: An id or e-mail for this particular user.
291 :rtype: :class:`_ACLEntity`
292 :returns: An Entity corresponding to this user.
293 """
294 return self.entity("user", identifier=identifier)
296 def group(self, identifier):
297 """Factory method for a group Entity.
299 :type identifier: str
300 :param identifier: An id or e-mail for this particular group.
302 :rtype: :class:`_ACLEntity`
303 :returns: An Entity corresponding to this group.
304 """
305 return self.entity("group", identifier=identifier)
307 def domain(self, domain):
308 """Factory method for a domain Entity.
310 :type domain: str
311 :param domain: The domain for this entity.
313 :rtype: :class:`_ACLEntity`
314 :returns: An entity corresponding to this domain.
315 """
316 return self.entity("domain", identifier=domain)
318 def all(self):
319 """Factory method for an Entity representing all users.
321 :rtype: :class:`_ACLEntity`
322 :returns: An entity representing all users.
323 """
324 return self.entity("allUsers")
326 def all_authenticated(self):
327 """Factory method for an Entity representing all authenticated users.
329 :rtype: :class:`_ACLEntity`
330 :returns: An entity representing all authenticated users.
331 """
332 return self.entity("allAuthenticatedUsers")
334 def get_entities(self):
335 """Get a list of all Entity objects.
337 :rtype: list of :class:`_ACLEntity` objects
338 :returns: A list of all Entity objects.
339 """
340 self._ensure_loaded()
341 return list(self.entities.values())
343 @property
344 def client(self):
345 """Abstract getter for the object client."""
346 raise NotImplementedError
348 def _require_client(self, client):
349 """Check client or verify over-ride.
351 :type client: :class:`~google.cloud.storage.client.Client` or
352 ``NoneType``
353 :param client: the client to use. If not passed, falls back to the
354 ``client`` stored on the current ACL.
356 :rtype: :class:`google.cloud.storage.client.Client`
357 :returns: The client passed in or the currently bound client.
358 """
359 if client is None:
360 client = self.client
361 return client
363 def reload(self, client=None, timeout=_DEFAULT_TIMEOUT, retry=DEFAULT_RETRY):
364 """Reload the ACL data from Cloud Storage.
366 If :attr:`user_project` is set, bills the API request to that project.
368 :type client: :class:`~google.cloud.storage.client.Client` or
369 ``NoneType``
370 :param client: (Optional) The client to use. If not passed, falls back
371 to the ``client`` stored on the ACL's parent.
372 :type timeout: float or tuple
373 :param timeout:
374 (Optional) The amount of time, in seconds, to wait
375 for the server response. See: :ref:`configuring_timeouts`
377 :type retry: :class:`~google.api_core.retry.Retry`
378 :param retry:
379 (Optional) How to retry the RPC. See: :ref:`configuring_retries`
380 """
381 path = self.reload_path
382 client = self._require_client(client)
383 query_params = {}
385 if self.user_project is not None:
386 query_params["userProject"] = self.user_project
388 self.entities.clear()
390 found = client._get_resource(
391 path,
392 query_params=query_params,
393 timeout=timeout,
394 retry=retry,
395 )
396 self.loaded = True
398 for entry in found.get("items", ()):
399 self.add_entity(self.entity_from_dict(entry))
401 def _save(
402 self,
403 acl,
404 predefined,
405 client,
406 if_generation_match=None,
407 if_generation_not_match=None,
408 if_metageneration_match=None,
409 if_metageneration_not_match=None,
410 timeout=_DEFAULT_TIMEOUT,
411 retry=DEFAULT_RETRY_IF_METAGENERATION_SPECIFIED,
412 ):
413 """Helper for :meth:`save` and :meth:`save_predefined`.
415 :type acl: :class:`google.cloud.storage.acl.ACL`, or a compatible list.
416 :param acl: The ACL object to save. If left blank, this will save
417 current entries.
419 :type predefined: str
420 :param predefined: An identifier for a predefined ACL. Must be one of the
421 keys in :attr:`PREDEFINED_JSON_ACLS` If passed, `acl` must be None.
423 :type client: :class:`~google.cloud.storage.client.Client` or
424 ``NoneType``
425 :param client: (Optional) The client to use. If not passed, falls back
426 to the ``client`` stored on the ACL's parent.
428 :type if_generation_match: long
429 :param if_generation_match:
430 (Optional) See :ref:`using-if-generation-match`
432 :type if_generation_not_match: long
433 :param if_generation_not_match:
434 (Optional) See :ref:`using-if-generation-not-match`
436 :type if_metageneration_match: long
437 :param if_metageneration_match:
438 (Optional) See :ref:`using-if-metageneration-match`
440 :type if_metageneration_not_match: long
441 :param if_metageneration_not_match:
442 (Optional) See :ref:`using-if-metageneration-not-match`
444 :type timeout: float or tuple
445 :param timeout:
446 (Optional) The amount of time, in seconds, to wait
447 for the server response. See: :ref:`configuring_timeouts`
449 :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
450 :param retry:
451 (Optional) How to retry the RPC. See: :ref:`configuring_retries`
452 """
453 client = self._require_client(client)
454 query_params = {"projection": "full"}
456 if predefined is not None:
457 acl = []
458 query_params[self._PREDEFINED_QUERY_PARAM] = predefined
460 if self.user_project is not None:
461 query_params["userProject"] = self.user_project
463 _add_generation_match_parameters(
464 query_params,
465 if_generation_match=if_generation_match,
466 if_generation_not_match=if_generation_not_match,
467 if_metageneration_match=if_metageneration_match,
468 if_metageneration_not_match=if_metageneration_not_match,
469 )
471 path = self.save_path
473 result = client._patch_resource(
474 path,
475 {self._URL_PATH_ELEM: list(acl)},
476 query_params=query_params,
477 timeout=timeout,
478 retry=retry,
479 )
481 self.entities.clear()
483 for entry in result.get(self._URL_PATH_ELEM, ()):
484 self.add_entity(self.entity_from_dict(entry))
486 self.loaded = True
488 def save(
489 self,
490 acl=None,
491 client=None,
492 if_generation_match=None,
493 if_generation_not_match=None,
494 if_metageneration_match=None,
495 if_metageneration_not_match=None,
496 timeout=_DEFAULT_TIMEOUT,
497 retry=DEFAULT_RETRY_IF_METAGENERATION_SPECIFIED,
498 ):
499 """Save this ACL for the current bucket.
501 If :attr:`user_project` is set, bills the API request to that project.
503 :type acl: :class:`google.cloud.storage.acl.ACL`, or a compatible list.
504 :param acl: The ACL object to save. If left blank, this will save
505 current entries.
507 :type client: :class:`~google.cloud.storage.client.Client` or
508 ``NoneType``
509 :param client: (Optional) The client to use. If not passed, falls back
510 to the ``client`` stored on the ACL's parent.
512 :type if_generation_match: long
513 :param if_generation_match:
514 (Optional) See :ref:`using-if-generation-match`
516 :type if_generation_not_match: long
517 :param if_generation_not_match:
518 (Optional) See :ref:`using-if-generation-not-match`
520 :type if_metageneration_match: long
521 :param if_metageneration_match:
522 (Optional) See :ref:`using-if-metageneration-match`
524 :type if_metageneration_not_match: long
525 :param if_metageneration_not_match:
526 (Optional) See :ref:`using-if-metageneration-not-match`
528 :type timeout: float or tuple
529 :param timeout:
530 (Optional) The amount of time, in seconds, to wait
531 for the server response. See: :ref:`configuring_timeouts`
533 :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
534 :param retry:
535 (Optional) How to retry the RPC. See: :ref:`configuring_retries`
536 """
537 if acl is None:
538 acl = self
539 save_to_backend = acl.loaded
540 else:
541 save_to_backend = True
543 if save_to_backend:
544 self._save(
545 acl,
546 None,
547 client,
548 if_generation_match=if_generation_match,
549 if_generation_not_match=if_generation_not_match,
550 if_metageneration_match=if_metageneration_match,
551 if_metageneration_not_match=if_metageneration_not_match,
552 timeout=timeout,
553 retry=retry,
554 )
556 def save_predefined(
557 self,
558 predefined,
559 client=None,
560 if_generation_match=None,
561 if_generation_not_match=None,
562 if_metageneration_match=None,
563 if_metageneration_not_match=None,
564 timeout=_DEFAULT_TIMEOUT,
565 retry=DEFAULT_RETRY_IF_METAGENERATION_SPECIFIED,
566 ):
567 """Save this ACL for the current bucket using a predefined ACL.
569 If :attr:`user_project` is set, bills the API request to that project.
571 :type predefined: str
572 :param predefined: An identifier for a predefined ACL. Must be one
573 of the keys in :attr:`PREDEFINED_JSON_ACLS`
574 or :attr:`PREDEFINED_XML_ACLS` (which will be
575 aliased to the corresponding JSON name).
576 If passed, `acl` must be None.
578 :type client: :class:`~google.cloud.storage.client.Client` or
579 ``NoneType``
580 :param client: (Optional) The client to use. If not passed, falls back
581 to the ``client`` stored on the ACL's parent.
583 :type if_generation_match: long
584 :param if_generation_match:
585 (Optional) See :ref:`using-if-generation-match`
587 :type if_generation_not_match: long
588 :param if_generation_not_match:
589 (Optional) See :ref:`using-if-generation-not-match`
591 :type if_metageneration_match: long
592 :param if_metageneration_match:
593 (Optional) See :ref:`using-if-metageneration-match`
595 :type if_metageneration_not_match: long
596 :param if_metageneration_not_match:
597 (Optional) See :ref:`using-if-metageneration-not-match`
599 :type timeout: float or tuple
600 :param timeout:
601 (Optional) The amount of time, in seconds, to wait
602 for the server response. See: :ref:`configuring_timeouts`
604 :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
605 :param retry:
606 (Optional) How to retry the RPC. See: :ref:`configuring_retries`
607 """
608 predefined = self.validate_predefined(predefined)
609 self._save(
610 None,
611 predefined,
612 client,
613 if_generation_match=if_generation_match,
614 if_generation_not_match=if_generation_not_match,
615 if_metageneration_match=if_metageneration_match,
616 if_metageneration_not_match=if_metageneration_not_match,
617 timeout=timeout,
618 retry=retry,
619 )
621 def clear(
622 self,
623 client=None,
624 if_generation_match=None,
625 if_generation_not_match=None,
626 if_metageneration_match=None,
627 if_metageneration_not_match=None,
628 timeout=_DEFAULT_TIMEOUT,
629 retry=DEFAULT_RETRY_IF_METAGENERATION_SPECIFIED,
630 ):
631 """Remove all ACL entries.
633 If :attr:`user_project` is set, bills the API request to that project.
635 Note that this won't actually remove *ALL* the rules, but it
636 will remove all the non-default rules. In short, you'll still
637 have access to a bucket that you created even after you clear
638 ACL rules with this method.
640 :type client: :class:`~google.cloud.storage.client.Client` or
641 ``NoneType``
642 :param client: (Optional) The client to use. If not passed, falls back
643 to the ``client`` stored on the ACL's parent.
645 :type if_generation_match: long
646 :param if_generation_match:
647 (Optional) See :ref:`using-if-generation-match`
649 :type if_generation_not_match: long
650 :param if_generation_not_match:
651 (Optional) See :ref:`using-if-generation-not-match`
653 :type if_metageneration_match: long
654 :param if_metageneration_match:
655 (Optional) See :ref:`using-if-metageneration-match`
657 :type if_metageneration_not_match: long
658 :param if_metageneration_not_match:
659 (Optional) See :ref:`using-if-metageneration-not-match`
661 :type timeout: float or tuple
662 :param timeout:
663 (Optional) The amount of time, in seconds, to wait
664 for the server response. See: :ref:`configuring_timeouts`
666 :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
667 :param retry:
668 (Optional) How to retry the RPC. See: :ref:`configuring_retries`
669 """
670 self.save(
671 [],
672 client=client,
673 if_generation_match=if_generation_match,
674 if_generation_not_match=if_generation_not_match,
675 if_metageneration_match=if_metageneration_match,
676 if_metageneration_not_match=if_metageneration_not_match,
677 timeout=timeout,
678 retry=retry,
679 )
682class BucketACL(ACL):
683 """An ACL specifically for a bucket.
685 :type bucket: :class:`google.cloud.storage.bucket.Bucket`
686 :param bucket: The bucket to which this ACL relates.
687 """
689 def __init__(self, bucket):
690 super(BucketACL, self).__init__()
691 self.bucket = bucket
693 @property
694 def client(self):
695 """The client bound to this ACL's bucket."""
696 return self.bucket.client
698 @property
699 def reload_path(self):
700 """Compute the path for GET API requests for this ACL."""
701 return f"{self.bucket.path}/{self._URL_PATH_ELEM}"
703 @property
704 def save_path(self):
705 """Compute the path for PATCH API requests for this ACL."""
706 return self.bucket.path
708 @property
709 def user_project(self):
710 """Compute the user project charged for API requests for this ACL."""
711 return self.bucket.user_project
714class DefaultObjectACL(BucketACL):
715 """A class representing the default object ACL for a bucket."""
717 _URL_PATH_ELEM = "defaultObjectAcl"
718 _PREDEFINED_QUERY_PARAM = "predefinedDefaultObjectAcl"
721class ObjectACL(ACL):
722 """An ACL specifically for a Cloud Storage object / blob.
724 :type blob: :class:`google.cloud.storage.blob.Blob`
725 :param blob: The blob that this ACL corresponds to.
726 """
728 def __init__(self, blob):
729 super(ObjectACL, self).__init__()
730 self.blob = blob
732 @property
733 def client(self):
734 """The client bound to this ACL's blob."""
735 return self.blob.client
737 @property
738 def reload_path(self):
739 """Compute the path for GET API requests for this ACL."""
740 return f"{self.blob.path}/acl"
742 @property
743 def save_path(self):
744 """Compute the path for PATCH API requests for this ACL."""
745 return self.blob.path
747 @property
748 def user_project(self):
749 """Compute the user project charged for API requests for this ACL."""
750 return self.blob.user_project