diff --git a/.env.env_var.template b/.env.env_var.template index 20c1252e..f905d0d6 100644 --- a/.env.env_var.template +++ b/.env.env_var.template @@ -24,3 +24,7 @@ export RATELIMIT_STORAGE_URI=memory:// # record bucket variables export RECORD_BUCKET_NAME= + +export FLASKS3_ACTIVE=False +export FLASKS3_CDN_DOMAIN= +export FLASKS3_BUCKET_NAME= diff --git a/README.md b/README.md index 9e689aaf..0f95773f 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,10 @@ Properties configurable at runtime: - `SECRET_KEY`: Secret key used for Flask session and security. - `DEFAULT_PAGE_SIZE`: set value for no. of records to show on browse/search view. - `DEFAULT_DATE_FORMAT`: set value to show date in specific format cross the application. i.e. "DD/MM/YYYY" +- `RECORD_BUCKET_NAME`: name of s3 bucket that holds all of the record objects themselves +- `FLASKS3_ACTIVE`: whether to fetch static assets from s3/Cloudfront rather than the usual `url_for`. +- `FLASKS3_CDN_DOMAIN`: CDN domain to fetch assets from if `FLASKS3_ACTIVE` is set to `True` +- `FLASKS3_BUCKET_NAME`: S3 bucket assets are uploaded to and served to Cloudfront from. Calculated values: diff --git a/app/__init__.py b/app/__init__.py index 36a9ccd0..2ba8d60e 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -2,6 +2,7 @@ from flask_compress import Compress from flask_limiter import Limiter from flask_limiter.util import get_remote_address +from flask_s3 import FlaskS3 from flask_talisman import Talisman from govuk_frontend_wtf.main import WTFormsHelpers from jinja2 import ChoiceLoader, PackageLoader, PrefixLoader @@ -14,6 +15,7 @@ get_remote_address, default_limits=["2 per second", "60 per minute"] ) talisman = Talisman() +s3 = FlaskS3() def null_to_dash(value): @@ -49,8 +51,7 @@ def create_app(config_class, database_uri=None): # Set content security policy csp = { - "default-src": "'self'", - "script-src": ["'self'"], + "default-src": f"'self' {app.config['FLASKS3_CDN_DOMAIN']}", } # setup database uri for testing @@ -60,6 +61,7 @@ def create_app(config_class, database_uri=None): # Initialise app extensions setup_logging(app) db.init_app(app) + s3.init_app(app) compress.init_app(app) limiter.init_app(app) talisman.init_app(app, content_security_policy=csp, force_https=force_https) diff --git a/app/tests/test_config.py b/app/tests/test_config.py index 14419ae6..1e66c352 100644 --- a/app/tests/test_config.py +++ b/app/tests/test_config.py @@ -33,7 +33,10 @@ def test_local_env_vars_config_initialized(monkeypatch): monkeypatch.setenv("DEFAULT_PAGE_SIZE", "test_default_page_size") monkeypatch.setenv("DEFAULT_DATE_FORMAT", "test_default_date_format") monkeypatch.setenv("RATELIMIT_STORAGE_URI", "test_ratelimit_storage_uri") - monkeypatch.setenv("RECORD_BUCKET_NAME", "test-bucket") + monkeypatch.setenv("RECORD_BUCKET_NAME", "test_record_bucket_name") + monkeypatch.setenv("FLASKS3_ACTIVE", "False") + monkeypatch.setenv("FLASKS3_CDN_DOMAIN", "test_flasks3_cdn_domain") + monkeypatch.setenv("FLASKS3_BUCKET_NAME", "test_flasks3_bucket_name") config = EnvConfig() @@ -52,6 +55,10 @@ def test_local_env_vars_config_initialized(monkeypatch): assert config.DEFAULT_PAGE_SIZE == "test_default_page_size" assert config.DEFAULT_DATE_FORMAT == "test_default_date_format" assert config.RATELIMIT_STORAGE_URI == "test_ratelimit_storage_uri" + assert config.RECORD_BUCKET_NAME == "test_record_bucket_name" + assert config.FLASKS3_ACTIVE is False + assert config.FLASKS3_CDN_DOMAIN == "test_flasks3_cdn_domain" + assert config.FLASKS3_BUCKET_NAME == "test_flasks3_bucket_name" @mock_aws @@ -69,6 +76,9 @@ def test_aws_secrets_manager_config_initialized(monkeypatch): "KEYCLOAK_REALM_NAME": "test_keycloack_realm_name", "KEYCLOAK_CLIENT_SECRET": "test_keycloak_client_secret", # pragma: allowlist secret "RECORD_BUCKET_NAME": "test_record_bucket_name", + "FLASKS3_ACTIVE": "False", + "FLASKS3_CDN_DOMAIN": "test_flasks3_cdn_domain", + "FLASKS3_BUCKET_NAME": "test_flasks3_bucket_name", "RATELIMIT_STORAGE_URI": "test_ratelimit_storage_uri", "DEFAULT_DATE_FORMAT": "test_default_date_format", "SECRET_KEY": "test_secret_key", # pragma: allowlist secret @@ -111,4 +121,8 @@ def test_aws_secrets_manager_config_initialized(monkeypatch): assert config.SECRET_KEY == "test_secret_key" # pragma: allowlist secret assert config.DEFAULT_PAGE_SIZE == "test_default_page_size" assert config.DEFAULT_DATE_FORMAT == "test_default_date_format" - assert config.RATELIMIT_STORAGE_URI == "test_ratelimit_storage_uri" + + assert config.RECORD_BUCKET_NAME == "test_record_bucket_name" + assert config.FLASKS3_ACTIVE is False + assert config.FLASKS3_CDN_DOMAIN == "test_flasks3_cdn_domain" + assert config.FLASKS3_BUCKET_NAME == "test_flasks3_bucket_name" diff --git a/configs/base_config.py b/configs/base_config.py index ebfa4396..750bad7b 100644 --- a/configs/base_config.py +++ b/configs/base_config.py @@ -82,5 +82,17 @@ def DEFAULT_DATE_FORMAT(self): def RECORD_BUCKET_NAME(self): return self._get_config_value("RECORD_BUCKET_NAME") + @property + def FLASKS3_ACTIVE(self): + return self._get_config_value("FLASKS3_ACTIVE") == "True" + + @property + def FLASKS3_CDN_DOMAIN(self): + return self._get_config_value("FLASKS3_CDN_DOMAIN") + + @property + def FLASKS3_BUCKET_NAME(self): + return self._get_config_value("FLASKS3_BUCKET_NAME") + def _get_config_value(self, variable_name): pass diff --git a/poetry.lock b/poetry.lock index 2c05a8f5..8771b34b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. [[package]] name = "argcomplete" @@ -954,6 +954,22 @@ memcached = ["limits[memcached]"] mongodb = ["limits[mongodb]"] redis = ["limits[redis]"] +[[package]] +name = "flask-s3" +version = "0.3.3" +description = "Seamlessly serve the static files of your Flask app from Amazon S3" +optional = false +python-versions = "*" +files = [ + {file = "Flask-S3-0.3.3.tar.gz", hash = "sha256:1d49061d4b78759df763358a901f4ed32bb43f672c9f8e1ec7226793f6ae0fd2"}, + {file = "Flask_S3-0.3.3-py3-none-any.whl", hash = "sha256:23cbbb1db4c29c313455dbe16f25be078d6318f0a11abcbb610f99e116945b62"}, +] + +[package.dependencies] +Boto3 = ">=1.1.1" +Flask = "*" +six = "*" + [[package]] name = "flask-sqlalchemy" version = "3.1.1" @@ -2266,7 +2282,6 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -2418,24 +2433,24 @@ python-versions = ">=3.6" files = [ {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:d92f81886165cb14d7b067ef37e142256f1c6a90a65cd156b063a43da1708cfd"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win32.whl", hash = "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:b5edda50e5e9e15e54a6a8a0070302b00c518a9d32accc2346ad6c984aacd279"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_24_aarch64.whl", hash = "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:7048c338b6c86627afb27faecf418768acb6331fc24cfa56c93e8c9780f815fa"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win32.whl", hash = "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa"}, @@ -2443,7 +2458,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:3fcc54cb0c8b811ff66082de1680b4b14cf8a81dce0d4fbf665c2265a81e07a1"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3"}, @@ -2451,7 +2466,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:665f58bfd29b167039f714c6998178d27ccd83984084c286110ef26b230f259f"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d"}, @@ -2459,7 +2474,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:9eb5dee2772b0f704ca2e45b1713e4e5198c18f515b52743576d196348f374d3"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880"}, @@ -3020,4 +3035,4 @@ wheel = "*" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "b8c20dd483c4f2a147cce4d8bae805e2b8a157881acd6295b5000972ba21fd73" +content-hash = "225f73f9d2cd6007a7efc9a453fc2aff0c2ab1310a1368fceb9d39bc0444af47" diff --git a/pyproject.toml b/pyproject.toml index 4fe0ddfe..cbe241f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ psycopg2-binary = "^2.9.9" setuptools = "^69.2.0" zappa = "^0.58.0" pytest-playwright-visual = "^2.1.2" +flask-s3 = "^0.3.3" [tool.poetry.group.dev.dependencies] testing-postgresql = "^1.3.0"