Skip to content
Snippets Groups Projects
Commit 2fb7dd19 authored by Daniel Göbel's avatar Daniel Göbel
Browse files

Merge branch 'bugfix/12-object-prefix' into 'development'

Validate file prefix when accessing s3 objects

See merge request denbi/object-storage-access!9
parents 8869f2c0 de34716e
No related branches found
No related tags found
No related merge requests found
......@@ -203,6 +203,8 @@ async def delete_bucket(
async def get_bucket_objects(
bucket: BucketDB = Depends(get_current_bucket),
s3: S3ServiceResource = Depends(get_s3_resource),
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
) -> list[S3ObjectMetaInformation]:
"""
Get the metadata of the objects in the bucket.
......@@ -219,6 +221,12 @@ async def get_bucket_objects(
objs : list[app.schemas.bucket.S3ObjectMetaInformation]
Meta information about all objects in the bucket.
"""
permission = await CRUDBucketPermission.get(db, bucket.name, current_user.uid)
if permission is not None and permission.file_prefix is not None:
return [
S3ObjectMetaInformation.from_native_s3_object(obj)
for obj in s3.Bucket(bucket.name).objects.filter(Prefix=permission.file_prefix).all()
]
return [S3ObjectMetaInformation.from_native_s3_object(obj) for obj in s3.Bucket(bucket.name).objects.all()]
......@@ -247,6 +255,8 @@ async def get_bucket_object(
},
},
),
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
) -> S3ObjectMetaInformation:
"""
Get the metadata of a specific object in a bucket.
......@@ -265,8 +275,11 @@ async def get_bucket_object(
objs : app.schemas.bucket.S3ObjectMetaInformation
Meta information about the specific object in the bucket.
"""
permission = await CRUDBucketPermission.get(db, bucket.name, current_user.uid)
try:
obj = s3.ObjectSummary(bucket_name=bucket.name, key=object_path)
return S3ObjectMetaInformation.from_native_s3_object(obj)
if permission is None or permission.file_prefix is None or object_path.startswith(permission.file_prefix):
obj = s3.ObjectSummary(bucket_name=bucket.name, key=object_path)
return S3ObjectMetaInformation.from_native_s3_object(obj)
raise HTTPException(status.HTTP_403_FORBIDDEN, detail="No rights for this object.")
except ClientError:
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Object not found")
import pytest
from fastapi import status
from httpx import AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.config import settings
from app.models.bucket import Bucket
from app.models.bucket_permission import BucketPermission, PermissionEnum
from app.models.user import User
from app.tests.mocks.mock_s3_resource import MockS3ServiceResource
from app.tests.utils.user import get_authorization_headers
from app.tests.utils.utils import random_lower_string
......@@ -13,6 +17,92 @@ class _TestS3ObjectsRoutes:
class TestS3ObjectsRoutesGet(_TestS3ObjectsRoutes):
@pytest.mark.asyncio
async def test_get_objects_with_right_for_specific_prefix(
self,
db: AsyncSession,
client: AsyncClient,
random_bucket: Bucket,
random_second_user: User,
mock_s3_service: MockS3ServiceResource,
) -> None:
"""
Test for getting the list of S3 objects in a bucket while only having rights for a specific prefix.
Parameters
----------
db : sqlalchemy.ext.asyncio.AsyncSession.
Async database session to perform query on. pytest fixture.
client : httpx.AsyncClient
HTTP Client to perform the request on. pytest fixture.
random_bucket : app.models.bucket.Bucket
Random bucket for testing. pytest fixture.
random_second_user : app.models.user.User
Random second user for testing. pytest fixture.
mock_s3_service : app.tests.mocks.mock_s3_resource.MockS3ServiceResource
Mock S3 Service to manipulate objects. pytest fixture.
"""
user_token_headers = await get_authorization_headers(client, random_second_user.uid)
mock_s3_service.create_object_in_bucket(bucket_name=random_bucket.name, key=random_lower_string())
obj = mock_s3_service.create_object_in_bucket(
bucket_name=random_bucket.name, key="pseudo/folder/" + random_lower_string()
)
permission = BucketPermission(
bucket_name=random_bucket.name,
user_id=random_second_user.uid,
permissions=PermissionEnum.READ,
file_prefix="pseudo/folder/",
)
db.add(permission)
await db.commit()
response = await client.get(f"{self.base_path}{random_bucket.name}/objects", headers=user_token_headers)
assert response.status_code == status.HTTP_200_OK
response_obj_list = response.json()
assert len(response_obj_list) == 1
assert response_obj_list[0]["key"] == obj.key
@pytest.mark.asyncio
async def test_get_object_without_right_for_specific_prefix(
self,
db: AsyncSession,
client: AsyncClient,
random_bucket: Bucket,
random_second_user: User,
mock_s3_service: MockS3ServiceResource,
) -> None:
"""
Test for getting a specific S3 object in a bucket while not having rights for the prefix.
Parameters
----------
db : sqlalchemy.ext.asyncio.AsyncSession.
Async database session to perform query on. pytest fixture.
client : httpx.AsyncClient
HTTP Client to perform the request on. pytest fixture.
random_bucket : app.models.bucket.Bucket
Random bucket for testing. pytest fixture.
random_second_user : app.models.user.User
Random second user for testing. pytest fixture.
mock_s3_service : app.tests.mocks.mock_s3_resource.MockS3ServiceResource
Mock S3 Service to manipulate objects. pytest fixture.
"""
user_token_headers = await get_authorization_headers(client, random_second_user.uid)
obj = mock_s3_service.create_object_in_bucket(
bucket_name=random_bucket.name, key="another/folder/" + random_lower_string()
)
permission = BucketPermission(
bucket_name=random_bucket.name,
user_id=random_second_user.uid,
permissions=PermissionEnum.READ,
file_prefix="pseudo/folder/",
)
db.add(permission)
await db.commit()
response = await client.get(
f"{self.base_path}{random_bucket.name}/objects/{obj.key}", headers=user_token_headers
)
assert response.status_code == status.HTTP_403_FORBIDDEN
@pytest.mark.asyncio
async def test_get_all_s3_object_from_bucket(
self,
......
......@@ -112,14 +112,16 @@ class MockS3Bucket:
---------
all() -> list[app.tests.mocks.mock_s3_resource.MockS3ObjectSummary]
Get the saved list.
filter(Prefix: str) -> app.tests.mocks.mock_s3_resource.MockS3Bucket.MockS3ObjectList
Filter the object in the list by the prefix all their keys should have.
add(obj: app.tests.mocks.mock_s3_resource.MockS3ObjectSummary) -> None
Add a MockS3ObjectSummary to the list.
delete(key: str) -> None
Delete a MockS3ObjectSummary from the list
"""
def __init__(self) -> None:
self._objs: list[MockS3ObjectSummary] = []
def __init__(self, obj_list: list[MockS3ObjectSummary] | None = None) -> None:
self._objs: list[MockS3ObjectSummary] = [] if obj_list is None else obj_list
def all(self) -> list[MockS3ObjectSummary]:
"""
......@@ -154,6 +156,22 @@ class MockS3Bucket:
"""
self._objs = [obj for obj in self._objs if obj.key != key]
def filter(self, Prefix: str) -> "MockS3Bucket.MockS3ObjectList":
"""
Filter the object in the list by the prefix all their keys should have.
Parameters
----------
Prefix : str
The prefix that all keys should have.
Returns
-------
obj_list : app.tests.mocks.mock_s3_resource.MockS3Bucket.MockS3ObjectList
The filtered list.
"""
return MockS3Bucket.MockS3ObjectList(obj_list=list(filter(lambda x: x.key.startswith(Prefix), self._objs)))
def __init__(self, name: str, parent_service: "MockS3ServiceResource"):
"""
Initialize a MockS3Bucket.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment