|
2 | 2 |
|
3 | 3 | import ldap |
4 | 4 | import pytest |
| 5 | +from fastapi import HTTPException |
5 | 6 | from pytest import MonkeyPatch |
6 | 7 |
|
7 | 8 | from mealie.core import security |
8 | | -from mealie.core.config import get_app_settings |
| 9 | +from mealie.core.config import get_app_dirs, get_app_settings |
9 | 10 | from mealie.core.dependencies import validate_file_token |
10 | 11 | from mealie.core.security.providers.credentials_provider import ( |
11 | 12 | CredentialsProvider, |
|
15 | 16 | from mealie.db.db_setup import session_context |
16 | 17 | from mealie.db.models.users.users import AuthMethod |
17 | 18 | from mealie.repos.repository_factory import AllRepositories |
| 19 | +from mealie.routes.utility_routes import download_file |
18 | 20 | from mealie.schema.user.auth import CredentialsRequestForm |
19 | 21 | from mealie.schema.user.user import PrivateUser |
20 | 22 | from tests.utils import random_string |
@@ -122,6 +124,46 @@ def test_create_file_token(): |
122 | 124 | assert file_path == validate_file_token(file_token) |
123 | 125 |
|
124 | 126 |
|
| 127 | +@pytest.mark.asyncio |
| 128 | +async def test_download_file_security_restrictions(): |
| 129 | + dirs = get_app_dirs() |
| 130 | + |
| 131 | + # Test 1: File in DATA_DIR but outside allowed dirs should be blocked |
| 132 | + secret_file = dirs.DATA_DIR / ".secret" |
| 133 | + |
| 134 | + with pytest.raises(HTTPException) as exc_info: |
| 135 | + await download_file(secret_file) |
| 136 | + assert exc_info.value.status_code == 400 |
| 137 | + |
| 138 | + # Test 2: File in BACKUP_DIR should be allowed (but only if it exists) |
| 139 | + backup_file = dirs.BACKUP_DIR / "test.zip" |
| 140 | + dirs.BACKUP_DIR.mkdir(parents=True, exist_ok=True) |
| 141 | + backup_file.write_text("test backup content") |
| 142 | + |
| 143 | + try: |
| 144 | + response = await download_file(backup_file) |
| 145 | + assert response.media_type == "application/octet-stream" |
| 146 | + assert response.path == backup_file |
| 147 | + finally: |
| 148 | + backup_file.unlink(missing_ok=True) |
| 149 | + |
| 150 | + # Test 3: File in GROUPS_DIR should be allowed (but only if it exists) |
| 151 | + export_dir = dirs.GROUPS_DIR / "some-group-id" / "export" |
| 152 | + export_dir.mkdir(parents=True, exist_ok=True) |
| 153 | + export_file = export_dir / "test.zip" |
| 154 | + export_file.write_text("test export content") |
| 155 | + |
| 156 | + try: |
| 157 | + response = await download_file(export_file) |
| 158 | + assert response.media_type == "application/octet-stream" |
| 159 | + assert response.path == export_file |
| 160 | + finally: |
| 161 | + export_file.unlink(missing_ok=True) |
| 162 | + # Clean up the directory structure |
| 163 | + export_dir.rmdir() |
| 164 | + (dirs.GROUPS_DIR / "some-group-id").rmdir() |
| 165 | + |
| 166 | + |
125 | 167 | def get_provider(session, username: str, password: str): |
126 | 168 | request_data = CredentialsRequest(username=username, password=password) |
127 | 169 | return LDAPProvider(session, request_data) |
|
0 commit comments