131 lines
3.8 KiB
Python
131 lines
3.8 KiB
Python
from functools import lru_cache
|
|
from typing import Any, Dict
|
|
|
|
import boto3
|
|
from attrs import define
|
|
|
|
from vbv_lernwelt.files.utils import assert_settings
|
|
|
|
|
|
@define
|
|
class S3Credentials:
|
|
access_key_id: str
|
|
secret_access_key: str
|
|
region_name: str
|
|
bucket_name: str
|
|
default_acl: str
|
|
presigned_expiry: int
|
|
max_size: int
|
|
|
|
|
|
@lru_cache
|
|
def s3_get_credentials() -> S3Credentials:
|
|
required_config = assert_settings(
|
|
[
|
|
"AWS_S3_ACCESS_KEY_ID",
|
|
"AWS_S3_SECRET_ACCESS_KEY",
|
|
"AWS_S3_REGION_NAME",
|
|
"AWS_STORAGE_BUCKET_NAME",
|
|
"AWS_DEFAULT_ACL",
|
|
"AWS_PRESIGNED_EXPIRY",
|
|
"FILE_MAX_SIZE",
|
|
],
|
|
"S3 credentials not found.",
|
|
)
|
|
|
|
return S3Credentials(
|
|
access_key_id=required_config["AWS_S3_ACCESS_KEY_ID"],
|
|
secret_access_key=required_config["AWS_S3_SECRET_ACCESS_KEY"],
|
|
region_name=required_config["AWS_S3_REGION_NAME"],
|
|
bucket_name=required_config["AWS_STORAGE_BUCKET_NAME"],
|
|
default_acl=required_config["AWS_DEFAULT_ACL"],
|
|
presigned_expiry=required_config["AWS_PRESIGNED_EXPIRY"],
|
|
max_size=required_config["FILE_MAX_SIZE"],
|
|
)
|
|
|
|
|
|
def s3_get_client():
|
|
credentials = s3_get_credentials()
|
|
|
|
# This is needed until https://github.com/boto/boto3/issues/3015 is fixed
|
|
s3 = boto3.client("s3", region_name=credentials.region_name)
|
|
endpoint_url = s3.meta.endpoint_url
|
|
|
|
return boto3.client(
|
|
service_name="s3",
|
|
aws_access_key_id=credentials.access_key_id,
|
|
aws_secret_access_key=credentials.secret_access_key,
|
|
region_name=credentials.region_name,
|
|
endpoint_url=endpoint_url,
|
|
)
|
|
|
|
|
|
def s3_generate_presigned_post(
|
|
*, file_path: str, file_type: str, file_name: str
|
|
) -> Dict[str, Any]:
|
|
credentials = s3_get_credentials()
|
|
s3_client = s3_get_client()
|
|
|
|
acl = credentials.default_acl
|
|
expires_in = credentials.presigned_expiry
|
|
|
|
"""
|
|
TODO: Create a type for the presigned_data
|
|
It looks like this:
|
|
{
|
|
'fields': {
|
|
'Content-Type': 'image/png',
|
|
'acl': 'private',
|
|
'key': 'files/<hash>.png',
|
|
'policy': 'some-long-base64-string',
|
|
'x-amz-algorithm': 'AWS4-HMAC-SHA256',
|
|
'x-amz-credential': 'AKIASOZLZI5FJDJ6XTSZ/20220405/eu-central-1/s3/aws4_request',
|
|
'x-amz-date': '20220405T114912Z',
|
|
'x-amz-signature': '<hash>',
|
|
},
|
|
'url': 'https://django-styleguide-example.s3.amazonaws.com/'
|
|
}
|
|
"""
|
|
presigned_data = s3_client.generate_presigned_post(
|
|
credentials.bucket_name,
|
|
file_path,
|
|
Fields={
|
|
"acl": acl,
|
|
"Content-Type": file_type,
|
|
"Content-Disposition": f"attachment; filename={file_name}",
|
|
},
|
|
Conditions=[
|
|
{"acl": acl},
|
|
{"Content-Type": file_type},
|
|
# As an example, allow file size up to 10 MiB
|
|
# More on conditions, here:
|
|
# https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
|
|
["content-length-range", 1, credentials.max_size],
|
|
["starts-with", "$Content-Disposition", ""],
|
|
],
|
|
ExpiresIn=expires_in,
|
|
)
|
|
|
|
return presigned_data
|
|
|
|
|
|
def s3_generate_presigned_url(*, file_path: str) -> str:
|
|
credentials = s3_get_credentials()
|
|
s3_client = s3_get_client()
|
|
|
|
return s3_client.generate_presigned_url(
|
|
"get_object",
|
|
Params={"Bucket": credentials.bucket_name, "Key": file_path},
|
|
ExpiresIn=credentials.presigned_expiry,
|
|
)
|
|
|
|
|
|
def s3_delete_file(*, file_path: str):
|
|
credentials = s3_get_credentials()
|
|
s3_client = s3_get_client()
|
|
|
|
s3_client.delete_object(
|
|
Bucket=credentials.bucket_name,
|
|
Key=file_path,
|
|
)
|