Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge main into feature/anonymous-user #129

Merged
Merged
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 6 additions & 6 deletions docker-compose.test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -16,18 +16,18 @@ 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:
- OPENSLIDES_DEVELOPMENT=1
depends_on:
- media
- postgres
- dummy_presenter
- dummy_autoupdate
volumes:
- ./tests:/app/tests
postgres:
Expand Down
4 changes: 2 additions & 2 deletions requirements_development.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
-r requirements_production.txt

autoflake==2.3.1
black==24.4.2
flake8==7.0.0
black==24.8.0
flake8==7.1.1
isort==5.13.2
6 changes: 3 additions & 3 deletions requirements_production.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Flask==3.0.3
gunicorn==22.0.0
gunicorn==23.0.0
psycopg2==2.9.9
requests==2.32.1
requests==2.32.3

# authlib
git+https://github.com/OpenSlides/openslides-auth-service.git@c41f94cc0d2acebbc8bb48af38e09212ac43eedb#egg=authlib&subdirectory=auth/libraries/pip-auth
git+https://github.com/OpenSlides/openslides-auth-service.git@6548f50ca778ab4f27e530886080f1482681929d#egg=authlib&subdirectory=auth/libraries/pip-auth
2 changes: 1 addition & 1 deletion requirements_tests.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
-r requirements_development.txt

pytest==8.2.1
pytest==8.3.2
47 changes: 31 additions & 16 deletions src/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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"
4 changes: 2 additions & 2 deletions src/config_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}


Expand Down
8 changes: 4 additions & 4 deletions src/mediaserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
10 changes: 10 additions & 0 deletions tests/dummy_autoupdate/Dockerfile.dummy_autoupdate
Original file line number Diff line number Diff line change
@@ -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"]
62 changes: 62 additions & 0 deletions tests/dummy_autoupdate/dummy_autoupdate.py
Original file line number Diff line number Diff line change
@@ -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({})
10 changes: 0 additions & 10 deletions tests/dummy_presenter/Dockerfile.dummy_presenter

This file was deleted.

42 changes: 0 additions & 42 deletions tests/dummy_presenter/dummy_presenter.py

This file was deleted.

2 changes: 1 addition & 1 deletion tests/test_get.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down