From 537765b2087790a1009323e1d71b069aae4af585 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20G=C3=B6bel?= <dgoebel@techfak.uni-bielefeld.de> Date: Tue, 16 Apr 2024 10:20:16 +0200 Subject: [PATCH] Update buckets public state instead of toggle it #78 --- app/api/endpoints/buckets.py | 24 ++++++++++------- app/tests/api/test_buckets.py | 49 ++++++++++++++++++++++++++++++++--- 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/app/api/endpoints/buckets.py b/app/api/endpoints/buckets.py index 90b3f7e..b312ba1 100644 --- a/app/api/endpoints/buckets.py +++ b/app/api/endpoints/buckets.py @@ -4,7 +4,7 @@ from uuid import UUID from botocore.exceptions import ClientError from clowmdb.models import Bucket -from fastapi import APIRouter, Depends, HTTPException, Query, status +from fastapi import APIRouter, Body, Depends, HTTPException, Query, status from opentelemetry import trace from pydantic.json_schema import SkipJsonSchema @@ -245,14 +245,15 @@ async def get_bucket( return bucket -@router.patch("/{bucket_name}/public", response_model=BucketOutSchema, summary="Toggle public status") -@start_as_current_span_async("api_toggle_bucket_public_state", tracer=tracer) +@router.patch("/{bucket_name}/public", response_model=BucketOutSchema, summary="update public status") +@start_as_current_span_async("api_update_bucket_public_state", tracer=tracer) async def update_bucket_public_state( bucket: CurrentBucket, current_user: CurrentUser, authorization: Authorization, db: DBSession, s3: S3Resource, + public: Annotated[bool, Body(..., embed=True, description="New State")], ) -> Bucket: """ Toggle the buckets public state. A bucket with an owner constraint can't be made public.\n @@ -271,28 +272,33 @@ async def update_bucket_public_state( Current user. Dependency Injection. s3 : boto3_type_annotations.s3.ServiceResource S3 Service to perform operations on buckets in Ceph. Dependency Injection. + public : bool + The new public state. HTTP Body. Returns ------- bucket : clowmdb.models.Bucket Bucket with the toggled public state. """ - trace.get_current_span().set_attributes({"bucket_name": bucket.name, "old_public_state": bucket.public}) + trace.get_current_span().set_attributes({"bucket_name": bucket.name, "public": public}) rbac_operation = "update" if bucket.owner_id == current_user.uid else "update_any" await authorization(rbac_operation) if bucket.owner_constraint is not None: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, - detail=f"can't make a bucket with owner constraint {bucket.owner_constraint} public", + detail=f"can't change the public state of a bucket with owner constraint {bucket.owner_constraint}", ) + if bucket.public == public: + return bucket s3_policy = get_s3_bucket_policy(s3, bucket_name=bucket.name) json_policy = json.loads(s3_policy.policy) - if bucket.public: - json_policy["Statement"] = [stmt for stmt in json_policy["Statement"] if stmt["Sid"] != ANONYMOUS_ACCESS_SID] - else: + if public: json_policy["Statement"] += get_anonymously_bucket_policy(bucket.name) + + else: + json_policy["Statement"] = [stmt for stmt in json_policy["Statement"] if stmt["Sid"] != ANONYMOUS_ACCESS_SID] put_s3_bucket_policy(s3, bucket_name=bucket.name, policy=json.dumps(json_policy)) - await CRUDBucket.update_public_state(db=db, public=not bucket.public, bucket_name=bucket.name) + await CRUDBucket.update_public_state(db=db, public=public, bucket_name=bucket.name) return bucket diff --git a/app/tests/api/test_buckets.py b/app/tests/api/test_buckets.py index 10b5f14..b771e86 100644 --- a/app/tests/api/test_buckets.py +++ b/app/tests/api/test_buckets.py @@ -271,7 +271,9 @@ class TestBucketRoutesUpdate(_TestBucketRoutes): mock_s3_service : app.tests.mocks.mock_s3_resource.MockS3ServiceResource Mock S3 Service to manipulate objects. """ - response = await client.patch(f"{self.base_path}/{random_bucket.name}/public", headers=random_user.auth_headers) + response = await client.patch( + f"{self.base_path}/{random_bucket.name}/public", headers=random_user.auth_headers, json={"public": True} + ) assert response.status_code == status.HTTP_200_OK bucket = BucketOut.model_validate_json(response.content) @@ -307,7 +309,46 @@ class TestBucketRoutesUpdate(_TestBucketRoutes): Async database session to perform query on. """ await make_bucket_public(db=db, s3=mock_s3_service, bucket_name=random_bucket.name) - response = await client.patch(f"{self.base_path}/{random_bucket.name}/public", headers=random_user.auth_headers) + response = await client.patch( + f"{self.base_path}/{random_bucket.name}/public", headers=random_user.auth_headers, json={"public": False} + ) + assert response.status_code == status.HTTP_200_OK + + bucket = BucketOut.model_validate_json(response.content) + + assert bucket.name == random_bucket.name + assert not bucket.public + + assert ANONYMOUS_ACCESS_SID not in mock_s3_service.Bucket(bucket.name).Policy().policy + + @pytest.mark.asyncio + async def test_make_private_bucket_private( + self, + client: AsyncClient, + random_bucket: Bucket, + random_user: UserWithAuthHeader, + mock_s3_service: MockS3ServiceResource, + db: AsyncSession, + ) -> None: + """ + Test for getting a foreign public bucket. + + Parameters + ---------- + client : httpx.AsyncClient + HTTP Client to perform the request on. + random_bucket : clowmdb.models.Bucket + Random bucket for testing. + random_user : app.tests.utils.user.UserWithAuthHeader + Random user who is owner of the bucket. + mock_s3_service : app.tests.mocks.mock_s3_resource.MockS3ServiceResource + Mock S3 Service to manipulate objects. + db : sqlalchemy.ext.asyncio.AsyncSession. + Async database session to perform query on. + """ + response = await client.patch( + f"{self.base_path}/{random_bucket.name}/public", headers=random_user.auth_headers, json={"public": False} + ) assert response.status_code == status.HTTP_200_OK bucket = BucketOut.model_validate_json(response.content) @@ -345,7 +386,9 @@ class TestBucketRoutesUpdate(_TestBucketRoutes): await db.execute(update_stmt) await db.commit() - response = await client.patch(f"{self.base_path}/{random_bucket.name}/public", headers=random_user.auth_headers) + response = await client.patch( + f"{self.base_path}/{random_bucket.name}/public", headers=random_user.auth_headers, json={"public": True} + ) assert response.status_code == status.HTTP_400_BAD_REQUEST -- GitLab