Skip to content

Commit ba69f4f

Browse files
authored
Merge pull request #581 from MerginMaps/upload-allowed-files
Introduce UPLOAD_FILES_WHITELIST
2 parents 099506a + e16b154 commit ba69f4f

File tree

8 files changed

+76
-17
lines changed

8 files changed

+76
-17
lines changed

LICENSES/CLA-signed-list.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ C/ My company has custom contribution contract with Lutra Consulting Ltd. or I a
2121
* luxusko, 25th August 2023
2222
* jozef-budac, 30th January 2024
2323
* fernandinand, 13th March 2025
24-
* xkello, 26th January 2026
24+
* wonder-sk, 9th February 2026
25+
* xkello, 26th January 2026

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ Try Mergin Maps at https://merginmaps.com/ - the SaaS service run by Lutra Consu
6666

6767
### Running locally
6868

69-
A step-by-step guide how to run local Mergin Maps instance can be found in our [documentation](https://merginmaps.com/docs/dev/mergince/).
69+
A step-by-step guide how to run local Mergin Maps instance can be found in our [documentation](https://merginmaps.com/docs/server/install/).
7070

7171
### Manage Mergin Maps
7272

deployment/enterprise/README.md

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,17 @@
33
Suitable for Ubuntu servers, single-node deployment using Docker Compose and system NGINX as a reverse proxy.
44

55
> [!IMPORTANT]
6-
> Docker images for Mergin Maps Enterprise Edition are stored in a private AWS ECR repository.
6+
> Docker images for Mergin Maps Enterprise Edition are stored in private Dockerhub repositories.
77
> To access them, you need a Mergin Maps Enterprise [subscription](https://merginmaps.com/pricing).
88
> Please contact the Mergin Maps [sales team](https://merginmaps.com/contact-sales)!
99
10-
## Login to Mergin Maps AWS ECR Repository
11-
12-
```shell
13-
aws ecr --region eu-west-1 get-login-password | docker login --username AWS --password-stdin 433835555346.dkr.ecr.eu-west-1.amazonaws.com
14-
```
15-
1610
## Load Docker Images, Configure, and Run Mergin Maps Stack
1711

18-
To run Mergin Maps, you need to load local Docker images (if any). Make sure you have access to Lutra's ECR repository. You can check this by running:
12+
Login to dockerhub (you should have already received your access token from Mergin Maps team).
13+
To run Mergin Maps, you need to load local Docker images (if any). Make sure you have access to Lutra's repositories. You can check this by running:
1914

2015
```shell
21-
docker pull 433835555346.dkr.ecr.eu-west-1.amazonaws.com/mergin/mergin-ee-back:2025.3.0
16+
docker pull lutraconsulting/merginmaps-backend-ee:2025.7.3
2217
```
2318

2419
Then modify the [docker-compose file](docker-compose.yml) and create the environment file `.prod.env` from `.env.template`. Details about configuration can be found in the [docs](https://merginmaps.com/docs/server/install/).

deployment/enterprise/docker-compose.maps.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ networks:
66
services:
77
qgis:
88
container_name: mergin-qgis
9-
image: 433835555346.dkr.ecr.eu-west-1.amazonaws.com/mergin/qgis-server-ee:2025.1.0
9+
image: lutraconsulting/qgis-server:2025.1.0
1010
user: 1000:999
1111
networks:
1212
- mergin-net
@@ -30,7 +30,7 @@ services:
3030
- ./qgis_nginx.conf:/etc/nginx/conf.d/default.conf
3131
qgis_extractor:
3232
container_name: mergin-qgis-extractor
33-
image: 433835555346.dkr.ecr.eu-west-1.amazonaws.com/mergin/qgis-extractor-ee:2025.3.0
33+
image: lutraconsulting/qgis-extractor:2025.3.1
3434
user: 901:999
3535
networks:
3636
- mergin-net

deployment/enterprise/docker-compose.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ networks:
55

66
services:
77
server:
8-
image: 433835555346.dkr.ecr.eu-west-1.amazonaws.com/mergin/mergin-ee-back:2025.7.3
8+
image: lutraconsulting/merginmaps-backend-ee:2025.7.3
99
container_name: merginmaps-server
1010
restart: always
1111
user: 901:999
@@ -22,7 +22,7 @@ services:
2222
networks:
2323
- mergin
2424
web:
25-
image: 433835555346.dkr.ecr.eu-west-1.amazonaws.com/mergin/mergin-ee-front:2025.7.3
25+
image: lutraconsulting/merginmaps-frontend-ee:2025.7.3
2626
container_name: merginmaps-web
2727
restart: always
2828
depends_on:
@@ -52,7 +52,7 @@ services:
5252
- server
5353

5454
celery-beat:
55-
image: 433835555346.dkr.ecr.eu-west-1.amazonaws.com/mergin/mergin-ee-back:2025.7.3
55+
image: lutraconsulting/merginmaps-backend-ee:2025.7.3
5656
container_name: merginmaps-celery-beat
5757
restart: always
5858
user: 901:999
@@ -66,7 +66,7 @@ services:
6666
- mergin
6767

6868
celery-worker:
69-
image: 433835555346.dkr.ecr.eu-west-1.amazonaws.com/mergin/mergin-ee-back:2025.7.3
69+
image: lutraconsulting/merginmaps-backend-ee:2025.7.3
7070
container_name: merginmaps-celery-worker
7171
restart: always
7272
user: 901:999

server/mergin/sync/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,5 +78,7 @@ class Configuration(object):
7878
EXCLUDED_CLONE_FILENAMES = config(
7979
"EXCLUDED_CLONE_FILENAMES", default="qgis_cfg.xml", cast=Csv()
8080
)
81+
# files that should be ignored during extension and MIME type checks
82+
UPLOAD_FILES_WHITELIST = config("UPLOAD_FILES_WHITELIST", default="", cast=Csv())
8183
# max batch size for fetch projects in batch endpoint
8284
MAX_BATCH_SIZE = config("MAX_BATCH_SIZE", default=100, cast=int)

server/mergin/sync/utils.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
from flask import current_app
2828
from pathlib import Path
2929

30+
from .config import Configuration
31+
3032

3133
def generate_checksum(file, chunk_size=4096):
3234
"""
@@ -349,6 +351,8 @@ def has_trailing_space(filepath: str) -> bool:
349351

350352
def is_supported_extension(filepath) -> bool:
351353
"""Check whether file's extension is supported."""
354+
if check_skip_validation(filepath):
355+
return True
352356
ext = os.path.splitext(filepath)[1].lower()
353357
return ext and ext not in FORBIDDEN_EXTENSIONS
354358

@@ -491,6 +495,15 @@ def is_supported_extension(filepath) -> bool:
491495
".xnk",
492496
}
493497

498+
499+
def check_skip_validation(file_path: str) -> bool:
500+
"""
501+
Check if we can skip validation for this file path.
502+
Some files are allowed even if they have forbidden extension or mime type.
503+
"""
504+
return file_path in Configuration.UPLOAD_FILES_WHITELIST
505+
506+
494507
FORBIDDEN_MIME_TYPES = {
495508
"application/x-msdownload",
496509
"application/x-sh",
@@ -515,6 +528,8 @@ def is_supported_extension(filepath) -> bool:
515528

516529
def is_supported_type(filepath) -> bool:
517530
"""Check whether the file mimetype is supported."""
531+
if check_skip_validation(filepath):
532+
return True
518533
mime_type = get_mimetype(filepath)
519534
return mime_type.startswith("image/") or mime_type not in FORBIDDEN_MIME_TYPES
520535

server/mergin/tests/test_utils.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,13 @@
2222
has_valid_characters,
2323
has_valid_first_character,
2424
check_filename,
25+
is_supported_extension,
26+
is_supported_type,
2527
is_valid_path,
2628
get_x_accel_uri,
2729
wkb2wkt,
2830
has_trailing_space,
31+
check_skip_validation,
2932
)
3033
from ..auth.models import LoginHistory, User
3134
from . import json_headers
@@ -322,3 +325,46 @@ class TestSchema(Schema):
322325
"size": "disk_usage",
323326
}
324327
assert schema_map == expected_map
328+
329+
330+
def test_check_skip_validation():
331+
ALLOWED_FILES = ["script.js", "config/script.js"]
332+
333+
# We patch the Configuration class attribute directly
334+
with patch("mergin.sync.utils.Configuration.UPLOAD_FILES_WHITELIST", ALLOWED_FILES):
335+
336+
# Test allowed files
337+
for file_path in ALLOWED_FILES:
338+
assert check_skip_validation(file_path)
339+
340+
# Test not allowed files
341+
assert not check_skip_validation("test.py")
342+
assert not check_skip_validation("/some/path/test.py")
343+
assert not check_skip_validation("image.png")
344+
345+
346+
def test_is_supported_extension():
347+
ALLOWED_FILES = ["script.js", "config/script.js"]
348+
349+
with patch("mergin.sync.utils.Configuration.UPLOAD_FILES_WHITELIST", ALLOWED_FILES):
350+
for file_path in ALLOWED_FILES:
351+
assert is_supported_extension(file_path)
352+
353+
# Allowed normal file
354+
assert is_supported_extension("image.png")
355+
356+
# Forbidden file
357+
assert not is_supported_extension("test.js")
358+
359+
360+
def test_mime_type_validation_skip():
361+
ALLOWED_FILES = ["script.js", "config/script.js"]
362+
# Mocking get_mimetype to return forbidden mime type
363+
with patch(
364+
"mergin.sync.utils.get_mimetype", return_value="application/x-python-code"
365+
), patch("mergin.sync.utils.Configuration.UPLOAD_FILES_WHITELIST", ALLOWED_FILES):
366+
for file_path in ALLOWED_FILES:
367+
assert is_supported_type(file_path)
368+
369+
# Should be forbidden
370+
assert not is_supported_type("other.js")

0 commit comments

Comments
 (0)