Skip to content

Commit

Permalink
Merge branch 'main' into feature/anonymous-user
Browse files Browse the repository at this point in the history
  • Loading branch information
bastianjoel committed Aug 13, 2024
2 parents 8a1b9e5 + 283e78c commit 48c08ad
Show file tree
Hide file tree
Showing 14 changed files with 125 additions and 90 deletions.
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.1.0
black==24.8.0
flake8==7.1.1
isort==5.13.2
2 changes: 1 addition & 1 deletion requirements_production.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Flask==3.0.3
gunicorn==22.0.0
gunicorn==23.0.0
psycopg2==2.9.9
requests==2.32.3

Expand Down
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.2
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

0 comments on commit 48c08ad

Please sign in to comment.