From c7aed7c7f84eff4e42d70583a0651af37ad1af3f Mon Sep 17 00:00:00 2001 From: Sebastiaan Huber Date: Mon, 8 May 2023 15:43:39 +0200 Subject: [PATCH] Config: Add the `read_only` setting This setting, which is set to `True` by default, will determine whether the instance is to be read-only. The `protected_methods_middleware` function is added as middleware to the application. If the `read_only` setting is `True` and the request method is `DELETE`, `PATCH`, `POST` or `PUT`, a `405 Method Not Allowed` response is returned. --- README.md | 12 ++++++++++++ aiida_restapi/config.py | 11 +++++++++++ aiida_restapi/main.py | 5 +++++ aiida_restapi/middleware.py | 24 ++++++++++++++++++++++++ 4 files changed, 52 insertions(+) create mode 100644 aiida_restapi/middleware.py diff --git a/README.md b/README.md index 6289a2a..4dbe00f 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,18 @@ uvicorn aiida_restapi:app uvicorn aiida_restapi:app --reload ``` +By default all endpoints of the REST API are available. +The API can be made *read-only* by setting the `read_only` configuration settings to `True`. +This can either be done by setting the environment variable: +```bash +export READ_ONLY=True +``` +or by adding the following to the `.env` file: +```ini +read_only=true +``` +When the API is read-only, all `DELETE`, `PATCH`, `POST` and `PUT` requests will result in a `405 - Method Not Allowed` response. + ## Examples See the [examples](https://github.com/aiidateam/aiida-restapi/tree/master/examples) directory. diff --git a/aiida_restapi/config.py b/aiida_restapi/config.py index 4c840b8..1eb31ab 100644 --- a/aiida_restapi/config.py +++ b/aiida_restapi/config.py @@ -8,6 +8,14 @@ class Settings(BaseSettings): """Configuration settings for the application.""" + # pylint: disable=too-few-public-methods + + class Config: + """Config settings.""" + + env_file = ".env" + env_file_encoding = "utf-8" + secret_key: str = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7" """The secret key used to create access tokens.""" @@ -17,6 +25,9 @@ class Settings(BaseSettings): access_token_expire_minutes: int = 30 """The number of minutes an access token remains valid.""" + read_only: bool = False + """Whether the instance is read-only. If set to ``True`` all DELETE, PATCH, POST and PUT methods will raise 405.""" + @lru_cache() def get_settings(): diff --git a/aiida_restapi/main.py b/aiida_restapi/main.py index bbbc180..7904163 100644 --- a/aiida_restapi/main.py +++ b/aiida_restapi/main.py @@ -5,7 +5,12 @@ from aiida_restapi.graphql import main from aiida_restapi.routers import auth, computers, daemon, groups, nodes, process, users +from .middleware import protected_methods_middleware + app = FastAPI() + +app.middleware("http")(protected_methods_middleware) + app.include_router(auth.router) app.include_router(computers.router) app.include_router(daemon.router) diff --git a/aiida_restapi/middleware.py b/aiida_restapi/middleware.py new file mode 100644 index 0000000..806c451 --- /dev/null +++ b/aiida_restapi/middleware.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +"""Module with middleware.""" +from fastapi import Request +from fastapi.encoders import jsonable_encoder +from fastapi.responses import JSONResponse + +from .config import Settings, get_settings + + +async def protected_methods_middleware(request: Request, call_next): + """Middleware that will return a 405 if the instance is read only and the request method is mutating. + + Mutating request methods are `DELETE`, `PATCH`, `POST`, `PUT`. + """ + settings: Settings = get_settings() + + if settings.read_only and request.method in {"DELETE", "PATCH", "POST", "PUT"}: + return JSONResponse( + status_code=405, + content=jsonable_encoder({"reason": "This instance is read-only."}), + media_type="application/json", + ) + + return await call_next(request)