diff --git a/Makefile b/Makefile index d6f150b..482f4d5 100644 --- a/Makefile +++ b/Makefile @@ -4,10 +4,10 @@ build-dev: build-tests: docker build . -f Dockerfile.tests --tag openslides-media-tests -build-dummy-presenter: - docker build . -f tests/dummy_presenter/Dockerfile.dummy_presenter --tag openslides-media-dummy-presenter +build-dummy-autoupdate: + docker build . -f tests/dummy_autoupdate/Dockerfile.dummy_autoupdate --tag openslides-media-dummy-autoupdate -start-test-setup: | build-dev build-tests build-dummy-presenter +start-test-setup: | build-dev build-tests build-dummy-autoupdate docker compose -f docker-compose.test.yml up -d docker compose -f docker-compose.test.yml exec -T tests wait-for-it "media:9006" diff --git a/README.md b/README.md index fdc1557..fc986e9 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,8 @@ Delivers media files for OpenSlides. It stores the data in the database. password (default: `/run/secrets/postgres_password`; in dev mode the password is always assumed to be `openslides`) - `MEDIA_BLOCK_SIZE`: The size of the blocks, the file is chunked into (default: `4096`) - `MEDIA_CLIENT_CACHE_DURATION`: The duration in seconds a file should be cached by a client (default: `86400`; disabled when: `0`) -- `PRESENTER_HOST`: Host of the presenter service (default: `backend`) -- `PRESENTER_PORT`: Port of the presenter service (default: `9003`) +- `AUTOUPDATE_HOST`: Host of the autoupdate service (default: `autoupdate`) +- `AUTOUPDATE_PORT`: Port of the autoupdate service (default: `9012`) ## Production setup Use the provided Dockerfile. It creates the tables in Postgresql, if they don't diff --git a/docker-compose.test.yml b/docker-compose.test.yml index 30a554f..a4ff3ab 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -6,8 +6,8 @@ services: - MEDIA_DATABASE_HOST=postgres - MEDIA_DATABASE_PORT=5432 - MEDIA_DATABASE_NAME=openslides - - PRESENTER_HOST=dummy_presenter - - PRESENTER_PORT=9003 + - AUTOUPDATE_HOST=dummy_autoupdate + - AUTOUPDATE_PORT=9012 - MESSAGE_BUS_HOST=redis - CACHE_HOST=redis - OPENSLIDES_DEVELOPMENT=1 @@ -16,10 +16,10 @@ services: - ./src:/app/src ports: - 9006:9006 - dummy_presenter: - image: openslides-media-dummy-presenter + dummy_autoupdate: + image: openslides-media-dummy-autoupdate volumes: - - ./tests/dummy_presenter:/app/dummy_presenter + - ./tests/dummy_autoupdate:/app/dummy_autoupdate tests: image: openslides-media-tests environment: @@ -27,7 +27,7 @@ services: depends_on: - media - postgres - - dummy_presenter + - dummy_autoupdate volumes: - ./tests:/app/tests postgres: diff --git a/requirements_development.txt b/requirements_development.txt index 3ebccee..e02c3cd 100644 --- a/requirements_development.txt +++ b/requirements_development.txt @@ -1,6 +1,6 @@ -r requirements_production.txt autoflake==2.3.1 -black==24.4.2 -flake8==7.1.0 +black==24.8.0 +flake8==7.1.1 isort==5.13.2 diff --git a/requirements_production.txt b/requirements_production.txt index 890f699..52cc99e 100644 --- a/requirements_production.txt +++ b/requirements_production.txt @@ -1,5 +1,5 @@ Flask==3.0.3 -gunicorn==22.0.0 +gunicorn==23.0.0 psycopg2==2.9.9 requests==2.32.3 diff --git a/requirements_tests.txt b/requirements_tests.txt index 36d6b7c..9461019 100644 --- a/requirements_tests.txt +++ b/requirements_tests.txt @@ -1,3 +1,3 @@ -r requirements_development.txt -pytest==8.2.2 +pytest==8.3.2 diff --git a/src/auth.py b/src/auth.py index c31d979..d34df92 100644 --- a/src/auth.py +++ b/src/auth.py @@ -25,18 +25,33 @@ def check_login_valid(): return True -def check_file_id(file_id, presenter_headers): +def check_file_id(file_id, autoupdate_headers): """ Returns a triple: ok, filename, auth_header. filename is given, if ok=True. If ok=false, the user has no perms. if auth_header is returned, it must be set in the response. """ - presenter_url = get_presenter_url() - payload = [{"presenter": "check_mediafile_id", "data": {"mediafile_id": file_id}}] - app.logger.debug(f"Send check request: {presenter_url}: {payload}") + auth_handler = AuthHandler(app.logger.debug) + cookie = request.cookies.get(COOKIE_NAME, "") + try: + user_id = auth_handler.authenticate_only_refresh_id(parse.unquote(cookie)) + except (AuthenticateException, InvalidCredentialsException): + raise ServerError("Could not parse auth cookie") + + autoupdate_url = get_autoupdate_url(user_id) + payload = [ + { + "collection": "mediafile", + "fields": {"id": None, "filename": None}, + "ids": [file_id], + } + ] + app.logger.debug(f"Send check request: {autoupdate_url}: {payload}") try: - response = requests.post(presenter_url, headers=presenter_headers, json=payload) + response = requests.post( + autoupdate_url, headers=autoupdate_headers, json=payload + ) except requests.exceptions.ConnectionError as e: app.logger.error(str(e)) raise ServerError("The server didn't respond") @@ -53,24 +68,24 @@ def check_file_id(file_id, presenter_headers): content = response.json() except ValueError: raise ServerError("The Response does not contain valid JSON.") - if not isinstance(content, list) or len(content) != 1: - raise ServerError("The returned json is not a list of length 1.") - content = content[0] if not isinstance(content, dict): raise ServerError("The returned content is not a dict.") auth_header = response.headers.get(AUTHENTICATION_HEADER) - if not content.get("ok", False): + if ( + f"mediafile/{file_id}/id" not in content + or content[f"mediafile/{file_id}/id"] != file_id + ): return False, None, auth_header - if "filename" not in content: - raise ServerError("The presenter did not provide a filename") + if f"mediafile/{file_id}/filename" not in content: + raise ServerError("The autoupdate did not provide a filename") - return True, content["filename"], auth_header + return True, content[f"mediafile/{file_id}/filename"], auth_header -def get_presenter_url(): - presenter_host = app.config["PRESENTER_HOST"] - presenter_port = app.config["PRESENTER_PORT"] - return f"http://{presenter_host}:{presenter_port}/system/presenter/handle_request" +def get_autoupdate_url(user_id): + autoupdate_host = app.config["AUTOUPDATE_HOST"] + autoupdate_port = app.config["AUTOUPDATE_PORT"] + return f"http://{autoupdate_host}:{autoupdate_port}/internal/autoupdate?user_id={user_id}&single=1" diff --git a/src/config_handling.py b/src/config_handling.py index 75db8b7..cf5901d 100644 --- a/src/config_handling.py +++ b/src/config_handling.py @@ -14,8 +14,8 @@ "MEDIA_DATABASE_PASSWORD_FILE": "/run/secrets/postgres_password", "MEDIA_BLOCK_SIZE": 4096, "MEDIA_CLIENT_CACHE_DURATION": 86400, - "PRESENTER_HOST": "backend", - "PRESENTER_PORT": 9003, + "AUTOUPDATE_HOST": "autoupdate", + "AUTOUPDATE_PORT": 9012, } diff --git a/src/mediaserver.py b/src/mediaserver.py index dff05c1..b0a0478 100644 --- a/src/mediaserver.py +++ b/src/mediaserver.py @@ -39,11 +39,11 @@ def serve(file_id): return redirect("/") # get file id - presenter_headers = dict(request.headers) - del_keys = [key for key in presenter_headers if "content" in key] + autoupdate_headers = dict(request.headers) + del_keys = [key for key in autoupdate_headers if "content" in key] for key in del_keys: - del presenter_headers[key] - ok, filename, auth_header = check_file_id(file_id, presenter_headers) + del autoupdate_headers[key] + ok, filename, auth_header = check_file_id(file_id, autoupdate_headers) if not ok: raise NotFoundError() diff --git a/tests/dummy_autoupdate/Dockerfile.dummy_autoupdate b/tests/dummy_autoupdate/Dockerfile.dummy_autoupdate new file mode 100644 index 0000000..77aba8d --- /dev/null +++ b/tests/dummy_autoupdate/Dockerfile.dummy_autoupdate @@ -0,0 +1,10 @@ +FROM python:3.10.13-slim-bookworm + +WORKDIR /app + +RUN pip install flask + +ENV FLASK_APP ./dummy_autoupdate/dummy_autoupdate.py +ENV FLASK_ENV development + +CMD ["flask", "run", "--host", "0.0.0.0", "--port", "9012"] diff --git a/tests/dummy_autoupdate/dummy_autoupdate.py b/tests/dummy_autoupdate/dummy_autoupdate.py new file mode 100644 index 0000000..2964e2f --- /dev/null +++ b/tests/dummy_autoupdate/dummy_autoupdate.py @@ -0,0 +1,62 @@ +from flask import Flask, jsonify, request + +app = Flask(__name__) + +app.logger.info("Started Dummy-Autoupdate") + + +# for testing +@app.route("/internal/autoupdate", methods=["POST"]) +def dummy_autoupdate(): + app.logger.debug(f"dummy_autoupdate gets: {request.json}") + file_id = request.json[0]["ids"][0] + + # Valid response from autoupdate, but not found in DB + if file_id == 1: + return jsonify( + { + f"mediafile/{file_id}/id": file_id, + f"mediafile/{file_id}/filename": "Does not exist", + } + ) + + # OK-cases for dummy data + if file_id == 2: + return jsonify( + { + f"mediafile/{file_id}/id": file_id, + f"mediafile/{file_id}/filename": "A.txt", + } + ) + if file_id == 3: + return jsonify( + { + f"mediafile/{file_id}/id": file_id, + f"mediafile/{file_id}/filename": "in.jpg", + } + ) + + # OK-cases for uploaded data + if file_id in (4, 5, 6, 7): + return jsonify( + { + f"mediafile/{file_id}/id": file_id, + f"mediafile/{file_id}/filename": str(file_id), + } + ) + + # invalid responses + if file_id == 10: + return jsonify([None]) + if file_id == 11: + return "some text" + if file_id == 12: + return "An error", 500 + if file_id == 13: + return [] + if file_id == 14: + return jsonify({f"mediafile/{file_id}/id": file_id}) + + # not found or no perms + if file_id == 20: + return jsonify({}) diff --git a/tests/dummy_presenter/Dockerfile.dummy_presenter b/tests/dummy_presenter/Dockerfile.dummy_presenter deleted file mode 100644 index 203700d..0000000 --- a/tests/dummy_presenter/Dockerfile.dummy_presenter +++ /dev/null @@ -1,10 +0,0 @@ -FROM python:3.10.13-slim-bookworm - -WORKDIR /app - -RUN pip install flask - -ENV FLASK_APP ./dummy_presenter/dummy_presenter.py -ENV FLASK_ENV development - -CMD ["flask", "run", "--host", "0.0.0.0", "--port", "9003"] diff --git a/tests/dummy_presenter/dummy_presenter.py b/tests/dummy_presenter/dummy_presenter.py deleted file mode 100644 index c93fad8..0000000 --- a/tests/dummy_presenter/dummy_presenter.py +++ /dev/null @@ -1,42 +0,0 @@ -from flask import Flask, jsonify, request - -app = Flask(__name__) - -app.logger.info("Started Dummy-Presenter") - - -# for testing -@app.route("/system/presenter/handle_request", methods=["POST"]) -def dummy_presenter(): - app.logger.debug(f"dummy_presenter gets: {request.json}") - file_id = request.json[0]["data"]["mediafile_id"] - - # Valid response from presenter, but not found in DB - if file_id == 1: - return jsonify([{"ok": True, "filename": "Does not exist"}]) - - # OK-cases for dummy data - if file_id == 2: - return jsonify([{"ok": True, "filename": "A.txt"}]) - if file_id == 3: - return jsonify([{"ok": True, "filename": "in.jpg"}]) - - # OK-cases for uploaded data - if file_id in (4, 5, 6, 7): - return jsonify([{"ok": True, "filename": str(file_id)}]) - - # invalid responses - if file_id == 10: - return jsonify([None]) - if file_id == 11: - return "some text" - if file_id == 12: - return "An error", 500 - if file_id == 13: - return {"ok": False} - if file_id == 14: - return jsonify([{"ok": True}]) - - # not found or no perms - if file_id == 20: - return jsonify([{"ok": False}]) diff --git a/tests/test_get.py b/tests/test_get.py index 5584651..1aa4a2d 100644 --- a/tests/test_get.py +++ b/tests/test_get.py @@ -21,7 +21,7 @@ def test_invalid_responses(): assert "message" in response.json() -def test_not_ok_from_presenter(): +def test_not_ok_from_autoupdate(): response = get_mediafile(20) assert response.status_code == 404 assert "message" in response.json()