From c03c5a6e39b0dc24605a08e8adb5910c2b5783e5 Mon Sep 17 00:00:00 2001 From: Lova ANDRIARIMALALA <43842786+Xpirix@users.noreply.github.com> Date: Thu, 16 May 2024 12:10:23 +0300 Subject: [PATCH 1/8] Allow download from QGIS user agent only --- dockerize/sites-enabled/prod-ssl.conf | 13 +++++++++++++ dockerize/sites-enabled/prod.conf | 6 ++++++ 2 files changed, 19 insertions(+) diff --git a/dockerize/sites-enabled/prod-ssl.conf b/dockerize/sites-enabled/prod-ssl.conf index 589715f1..bca09fd1 100644 --- a/dockerize/sites-enabled/prod-ssl.conf +++ b/dockerize/sites-enabled/prod-ssl.conf @@ -90,6 +90,13 @@ server { } } + # New rule to allow download from QGIS user agent only + location ~* ^/plugins/[^/]+/version/[^/]+/download/$ { + if ($http_user_agent !~* "Mozilla/5.0 QGIS") { + return 403 "Forbidden: Please use QGIS to download .zip files."; + } + } + location /metabase/ { # set to webroot path proxy_pass http://metabase:3000/; @@ -209,6 +216,12 @@ server { } + # New rule to the download from QGIS user agent only + location ~* ^/plugins/[^/]+/version/[^/]+/download/$ { + if ($http_user_agent !~* "Mozilla/5.0 QGIS") { + return 403 "Forbidden: Please use QGIS to download .zip files."; + } + } location /metabase/ { # set to webroot path diff --git a/dockerize/sites-enabled/prod.conf b/dockerize/sites-enabled/prod.conf index 086d6903..830bfe88 100644 --- a/dockerize/sites-enabled/prod.conf +++ b/dockerize/sites-enabled/prod.conf @@ -88,6 +88,12 @@ server { } + # New rule to the download from QGIS user agent only + location ~* ^/plugins/[^/]+/version/[^/]+/download/$ { + if ($http_user_agent !~* "Mozilla/5.0 QGIS") { + return 403 "Forbidden: Please use QGIS to download .zip files."; + } + } location /metabase/ { # set to webroot path From 7c810e82567f9abeb191adce99a32c8a9de34802 Mon Sep 17 00:00:00 2001 From: Lova ANDRIARIMALALA <43842786+Xpirix@users.noreply.github.com> Date: Thu, 16 May 2024 14:44:57 +0300 Subject: [PATCH 2/8] Set a rate limit for nginx --- dockerize/sites-enabled/prod-ssl.conf | 12 ++++++++++++ dockerize/sites-enabled/prod.conf | 8 ++++++++ 2 files changed, 20 insertions(+) diff --git a/dockerize/sites-enabled/prod-ssl.conf b/dockerize/sites-enabled/prod-ssl.conf index bca09fd1..cd79e807 100644 --- a/dockerize/sites-enabled/prod-ssl.conf +++ b/dockerize/sites-enabled/prod-ssl.conf @@ -4,6 +4,9 @@ upstream uwsgi { server uwsgi:8080; } +# Define the rate limit zone +limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s; + server { # OTF gzip compression gzip on; @@ -66,6 +69,11 @@ server { } # Finally, send all non-media requests to the Django server. location / { + + # Apply rate limit + limit_req zone=one burst=20 nodelay; + limit_req_status 429; + uwsgi_pass uwsgi; # the uwsgi_params file you installed needs to be passed with each # request. @@ -191,6 +199,10 @@ server { } # Finally, send all non-media requests to the Django server. location / { + # Apply rate limit + limit_req zone=one burst=20 nodelay; + limit_req_status 429; + uwsgi_pass uwsgi; # the uwsgi_params file you installed needs to be passed with each # request. diff --git a/dockerize/sites-enabled/prod.conf b/dockerize/sites-enabled/prod.conf index 830bfe88..87eb4350 100644 --- a/dockerize/sites-enabled/prod.conf +++ b/dockerize/sites-enabled/prod.conf @@ -4,6 +4,9 @@ upstream uwsgi { server uwsgi:8080; } +# Define the rate limit zone +limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s; + server { # OTF gzip compression gzip on; @@ -63,6 +66,11 @@ server { } # Finally, send all non-media requests to the Django server. location / { + + # Apply rate limit + limit_req zone=one burst=20 nodelay; + limit_req_status 429; + uwsgi_pass uwsgi; # the uwsgi_params file you installed needs to be passed with each # request. From fd7b2df4f98051e1ed4fde8a99c4190de671a414 Mon Sep 17 00:00:00 2001 From: Lova ANDRIARIMALALA <43842786+Xpirix@users.noreply.github.com> Date: Thu, 16 May 2024 19:03:56 +0300 Subject: [PATCH 3/8] Add an human validation for plugins download --- .vscode/settings.json | 19 +++ qgis-app/plugins/forms.py | 26 +++- qgis-app/plugins/models.py | 2 +- .../templates/plugins/plugin_download.html | 114 ++++++++++++++++++ .../plugins/plugin_download_success.html | 42 +++++++ .../templates/plugins/plugin_list.html | 4 +- .../templates/plugins/version_detail.html | 2 +- qgis-app/plugins/urls.py | 6 + qgis-app/plugins/views.py | 36 +++++- 9 files changed, 244 insertions(+), 7 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 qgis-app/plugins/templates/plugins/plugin_download.html create mode 100644 qgis-app/plugins/templates/plugins/plugin_download_success.html diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..b189a5aa --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,19 @@ +{ + "files.associations": { + "**/*.html": "html", + "**/templates/*/*.html": "django-html", + "**/templates/*": "django-txt", + "**/requirements{/**,*}.{txt,in}": "pip-requirements" + }, + + "emmet.includeLanguages": { + "django-html": "html" + }, + "beautify.language": { + "html": [ + "htm", + "html", + "django-html" + ] + } +} \ No newline at end of file diff --git a/qgis-app/plugins/forms.py b/qgis-app/plugins/forms.py index 9f98f03f..a2b882b1 100644 --- a/qgis-app/plugins/forms.py +++ b/qgis-app/plugins/forms.py @@ -269,4 +269,28 @@ class Meta: model = PluginOutstandingToken fields = ( "description", - ) \ No newline at end of file + ) + +class VersionDownloadForm(forms.Form): + """Download confirmation for a plugin version""" + required_css_class = "required" + plugin_name = forms.CharField( + label=_("Confirm plugin name"), + required=True, + help_text=_( + "Please insert the plugin name shown above to proceed with the download." + ), + widget=forms.TextInput, + ) + def __init__(self, *args, original_name=None, **kwargs): + super(VersionDownloadForm, self).__init__(*args, **kwargs) + self.original_name = original_name + + def clean(self): + """ + Check if plugin name match + """ + super().clean() + if self.cleaned_data.get("plugin_name") != self.original_name: + raise ValidationError(_("Plugin name mismatch: Please ensure the plugin name matches the one displayed above in order to proceed with the download.")) + return super(VersionDownloadForm, self).clean() \ No newline at end of file diff --git a/qgis-app/plugins/models.py b/qgis-app/plugins/models.py index 72e8cc5a..b229c9b0 100644 --- a/qgis-app/plugins/models.py +++ b/qgis-app/plugins/models.py @@ -844,7 +844,7 @@ def get_absolute_url(self): def get_download_url(self): return reverse( - "version_download", + "version_get", args=( self.plugin.package_name, self.version, diff --git a/qgis-app/plugins/templates/plugins/plugin_download.html b/qgis-app/plugins/templates/plugins/plugin_download.html new file mode 100644 index 00000000..5c2ce708 --- /dev/null +++ b/qgis-app/plugins/templates/plugins/plugin_download.html @@ -0,0 +1,114 @@ +{% extends 'plugins/plugin_base.html' %}{% load i18n %} +{% load local_timezone %} +{% block content %} + +
+

{% trans "Plugin: " %}

+
+ {% trans "Copy to clipboard" %} + + {{plugin_name}} + + + +
+
+ +{% if form.errors %} +
+ +

{% trans "The form contains errors and cannot be submitted, please check the fields highlighted in red." %}

+
+{% endif %} +{% if form.non_field_errors %} +
+ + {% for error in form.non_field_errors %} +

{{ error }}

+ {% endfor %} +
+{% endif %} +
{% csrf_token %} + {% include "plugins/form_snippet.html" %} +
+ +
+
+{% endblock %} + + +{% block extrajs %} +{{ block.super }} + +{% endblock %} +{% block extracss %} +{{ block.super }} + +{% endblock %} \ No newline at end of file diff --git a/qgis-app/plugins/templates/plugins/plugin_download_success.html b/qgis-app/plugins/templates/plugins/plugin_download_success.html new file mode 100644 index 00000000..dbf50a2b --- /dev/null +++ b/qgis-app/plugins/templates/plugins/plugin_download_success.html @@ -0,0 +1,42 @@ +{% extends 'plugins/plugin_base.html' %}{% load i18n %} {% load local_timezone +%} {% block content %} +
+ The download of the plugin {{plugin_name}} will start shortly... +
+ +{% endblock %} {% block extrajs %} {{ block.super }} + +{% endblock %} {% block extracss %} {{ block.super }} + +{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/plugin_list.html b/qgis-app/plugins/templates/plugins/plugin_list.html index 788053d6..a322ac4a 100644 --- a/qgis-app/plugins/templates/plugins/plugin_list.html +++ b/qgis-app/plugins/templates/plugins/plugin_list.html @@ -104,8 +104,8 @@

{% if title %}{{title}}{% else %}{% trans "All plugins" %}{% endif %}

{{ object.latest_version_date|local_timezone:"SHORT_NATURAL_DAY" }} {{ object.created_on|local_timezone:"SHORT" }}
({{ object.rating_votes }})
- {% if object.stable %}{{ object.stable.version }}{% else %}—{% endif %} - {% if object.experimental %}{{ object.experimental.version }}{% else %}—{% endif %} + {% if object.stable %}{{ object.stable.version }}{% else %}—{% endif %} + {% if object.experimental %}{{ object.experimental.version }}{% else %}—{% endif %} {% if user.is_authenticated %}{% if user in object.editors or user.is_staff %} {% else %}{% endif %}{% endif %} diff --git a/qgis-app/plugins/templates/plugins/version_detail.html b/qgis-app/plugins/templates/plugins/version_detail.html index b9cd4874..847bf572 100644 --- a/qgis-app/plugins/templates/plugins/version_detail.html +++ b/qgis-app/plugins/templates/plugins/version_detail.html @@ -2,7 +2,7 @@ {% load local_timezone %} {% block content %}

{% trans "Version" %}: {{ version }}

-
{% trans "Download" %}
+
{% trans "Download" %}
{% if not version.created_by.is_active and not version.is_from_token %}
diff --git a/qgis-app/plugins/urls.py b/qgis-app/plugins/urls.py index 3d1c5da3..e7ef4f82 100644 --- a/qgis-app/plugins/urls.py +++ b/qgis-app/plugins/urls.py @@ -288,6 +288,12 @@ {}, name="version_download", ), + url( + r"^(?P[A-Za-z][A-Za-z0-9-_]+)/version/(?P[^\/]+)/get/$", + version_get, + {}, + name="version_get", + ), url( r"^(?P[A-Za-z][A-Za-z0-9-_]+)/version/(?P[^\/]+)/approve/$", version_approve, diff --git a/qgis-app/plugins/views.py b/qgis-app/plugins/views.py index 20dfca1c..7d1c55e4 100644 --- a/qgis-app/plugins/views.py +++ b/qgis-app/plugins/views.py @@ -16,7 +16,7 @@ from django.db.models.expressions import RawSQL from django.db.models.functions import Lower from django.http import Http404, HttpResponse, HttpResponseRedirect, JsonResponse -from django.shortcuts import get_object_or_404, render +from django.shortcuts import get_object_or_404, render, redirect from django.urls import reverse from django.utils.timezone import now from django.utils.decorators import method_decorator @@ -1506,8 +1506,38 @@ def version_feedback_delete(request, package_name, version, feedback): is_update_succeed: bool = True return JsonResponse({"success": is_update_succeed}) +def version_get(request, package_name, version): + """ + Download a plugin zip from the browser + """ + plugin = get_object_or_404(Plugin, package_name=package_name) + if request.method == "POST": + form = VersionDownloadForm(request.POST, original_name=plugin.name) + if form.is_valid(): + file_content, file_name = version_download(request, package_name, version, is_from_web=True) + return render( + request, + "plugins/plugin_download_success.html", + { + "file_content": file_content, + "file_name": file_name, + "plugin_name": plugin.name + } + ) + else: + form = VersionDownloadForm(original_name=plugin.name) + + return render( + request, + "plugins/plugin_download.html", + { + "form": form, + "plugin_name": plugin.name + } + ) + -def version_download(request, package_name, version): +def version_download(request, package_name, version, is_from_web=False): """ Update download counter(s) """ @@ -1534,6 +1564,8 @@ def version_download(request, package_name, version): version.package.file.file.close() zipfile = open(version.package.file.name, "rb") file_content = zipfile.read() + if is_from_web: + return [file_content, f"{version.plugin.package_name}-{version.version}"] response = HttpResponse(file_content, content_type="application/zip") response["Content-Disposition"] = "attachment; filename=%s-%s.zip" % ( version.plugin.package_name, From 59443dc3603d0efe52b767f61f1abbc7005011a1 Mon Sep 17 00:00:00 2001 From: Lova ANDRIARIMALALA <43842786+Xpirix@users.noreply.github.com> Date: Thu, 16 May 2024 19:46:03 +0300 Subject: [PATCH 4/8] Limit download from web to 10 per minute --- .../plugins/plugin_download_limit_exceed.html | 17 +++++++++++++++++ qgis-app/plugins/views.py | 16 ++++++++++++++++ qgis-app/settings.py | 4 ++-- 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 qgis-app/plugins/templates/plugins/plugin_download_limit_exceed.html diff --git a/qgis-app/plugins/templates/plugins/plugin_download_limit_exceed.html b/qgis-app/plugins/templates/plugins/plugin_download_limit_exceed.html new file mode 100644 index 00000000..e98a3ac1 --- /dev/null +++ b/qgis-app/plugins/templates/plugins/plugin_download_limit_exceed.html @@ -0,0 +1,17 @@ +{% extends 'plugins/plugin_base.html' %}{% load i18n %} +{% block content %} +
{% trans "Download rate limit exceeded. Try again later." %}
+{% endblock %} + + +{% block extracss %} +{{ block.super }} + + + +{% endblock %} \ No newline at end of file diff --git a/qgis-app/plugins/views.py b/qgis-app/plugins/views.py index 7d1c55e4..c8449dff 100644 --- a/qgis-app/plugins/views.py +++ b/qgis-app/plugins/views.py @@ -27,6 +27,7 @@ from django.views.decorators.http import require_POST from django.views.generic.detail import DetailView from django.db import transaction +from django.core.cache import cache # from sortable_listview import SortableListView from django.views.generic.list import ListView @@ -1510,11 +1511,26 @@ def version_get(request, package_name, version): """ Download a plugin zip from the browser """ + + ip = request.META['REMOTE_ADDR'] + key = f"download_limit_{ip}" + downloads = cache.get(key, 0) + + if downloads >= 10: + response = render( + request, + "plugins/plugin_download_limit_exceed.html", + {} + ) + response.status_code = 429 + return response + plugin = get_object_or_404(Plugin, package_name=package_name) if request.method == "POST": form = VersionDownloadForm(request.POST, original_name=plugin.name) if form.is_valid(): file_content, file_name = version_download(request, package_name, version, is_from_web=True) + cache.set(key, downloads + 1, timeout=60) # 60 seconds timeout for 10 requests per minute return render( request, "plugins/plugin_download_success.html", diff --git a/qgis-app/settings.py b/qgis-app/settings.py index 23d50828..a440b0a7 100644 --- a/qgis-app/settings.py +++ b/qgis-app/settings.py @@ -203,8 +203,8 @@ # See http://docs.djangoproject.com/en/dev/topics/cache/ CACHES = { "default": { - #'BACKEND': 'django.core.cache.backends.db.DatabaseCache', - "BACKEND": "django.core.cache.backends.dummy.DummyCache", + "BACKEND": "django.core.cache.backends.db.DatabaseCache", + # "BACKEND": "django.core.cache.backends.dummy.DummyCache", "LOCATION": "cache_table", } } From 0042e59d98473b671c5e602cc047297977d46f99 Mon Sep 17 00:00:00 2001 From: Lova ANDRIARIMALALA <43842786+Xpirix@users.noreply.github.com> Date: Fri, 17 May 2024 10:23:25 +0300 Subject: [PATCH 5/8] Refactoring, set rate limit in the environment variable --- dockerize/.env.template | 5 ++++- dockerize/docker-compose.yml | 1 + qgis-app/plugins/views.py | 28 +++++++++++++++------------- qgis-app/settings_docker.py | 5 ++++- 4 files changed, 24 insertions(+), 15 deletions(-) diff --git a/dockerize/.env.template b/dockerize/.env.template index 43f8d6bb..7147655c 100644 --- a/dockerize/.env.template +++ b/dockerize/.env.template @@ -31,4 +31,7 @@ DEFAULT_PLUGINS_SITE='https://plugins.qgis.org/' QGISPLUGINS_ENV=debug # Ldap -ENABLE_LDAP=True \ No newline at end of file +ENABLE_LDAP=True + +# Download limit per minute +DOWNLOAD_RATE_LIMIT=10 \ No newline at end of file diff --git a/dockerize/docker-compose.yml b/dockerize/docker-compose.yml index 225f9784..461b9c48 100644 --- a/dockerize/docker-compose.yml +++ b/dockerize/docker-compose.yml @@ -53,6 +53,7 @@ services: - EMAIL_HOST_USER=${EMAIL_HOST_USER:-automation} - EMAIL_HOST_PASSWORD=${EMAIL_HOST_PASSWORD} - DEFAULT_PLUGINS_SITE=${DEFAULT_PLUGINS_SITE:-https://plugins.qgis.org/} + - DOWNLOAD_RATE_LIMIT=${DOWNLOAD_RATE_LIMIT:-10} volumes: - ../qgis-app:/home/web/django_project - ./docker/uwsgi.conf:/uwsgi.conf diff --git a/qgis-app/plugins/views.py b/qgis-app/plugins/views.py index c8449dff..4242eabb 100644 --- a/qgis-app/plugins/views.py +++ b/qgis-app/plugins/views.py @@ -1516,7 +1516,7 @@ def version_get(request, package_name, version): key = f"download_limit_{ip}" downloads = cache.get(key, 0) - if downloads >= 10: + if downloads >= settings.DOWNLOAD_RATE_LIMIT: response = render( request, "plugins/plugin_download_limit_exceed.html", @@ -1529,8 +1529,8 @@ def version_get(request, package_name, version): if request.method == "POST": form = VersionDownloadForm(request.POST, original_name=plugin.name) if form.is_valid(): - file_content, file_name = version_download(request, package_name, version, is_from_web=True) - cache.set(key, downloads + 1, timeout=60) # 60 seconds timeout for 10 requests per minute + file_content, file_name = _get_file_content(package_name, version) + cache.set(key, downloads + 1, timeout=60) # 60 seconds timeout for limited downloads per minute return render( request, "plugins/plugin_download_success.html", @@ -1553,9 +1553,18 @@ def version_get(request, package_name, version): ) -def version_download(request, package_name, version, is_from_web=False): +def version_download(request, package_name, version): """ - Update download counter(s) + Download a plugin zip from QGIS + """ + file_content, file_name = _get_file_content(package_name, version) + response = HttpResponse(file_content, content_type="application/zip") + response["Content-Disposition"] = f"attachment; filename={file_name}.zip" + return response + +def _get_file_content(package_name, version): + """ + Update download counter(s) and return the file content """ plugin = get_object_or_404(Plugin, package_name=package_name) version = get_object_or_404(PluginVersion, plugin=plugin, version=version) @@ -1580,14 +1589,7 @@ def version_download(request, package_name, version, is_from_web=False): version.package.file.file.close() zipfile = open(version.package.file.name, "rb") file_content = zipfile.read() - if is_from_web: - return [file_content, f"{version.plugin.package_name}-{version.version}"] - response = HttpResponse(file_content, content_type="application/zip") - response["Content-Disposition"] = "attachment; filename=%s-%s.zip" % ( - version.plugin.package_name, - version.version, - ) - return response + return [file_content, f"{version.plugin.package_name}-{version.version}"] def version_detail(request, package_name, version): diff --git a/qgis-app/settings_docker.py b/qgis-app/settings_docker.py index c0b7be1e..995d6a62 100644 --- a/qgis-app/settings_docker.py +++ b/qgis-app/settings_docker.py @@ -159,4 +159,7 @@ MATOMO_URL="//matomo.qgis.org/" # Default primary key type -DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' \ No newline at end of file +DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' + +# Download limit per minute +DOWNLOAD_RATE_LIMIT = int(os.environ.get("DOWNLOAD_RATE_LIMIT", 10)) \ No newline at end of file From e0d8b000fdd22101d95275ce6b7c150df94361b9 Mon Sep 17 00:00:00 2001 From: Lova ANDRIARIMALALA <43842786+Xpirix@users.noreply.github.com> Date: Fri, 17 May 2024 10:49:46 +0300 Subject: [PATCH 6/8] Update unit test for web download --- qgis-app/plugins/tests/test_download.py | 81 +++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/qgis-app/plugins/tests/test_download.py b/qgis-app/plugins/tests/test_download.py index f18f0874..d8ab96be 100644 --- a/qgis-app/plugins/tests/test_download.py +++ b/qgis-app/plugins/tests/test_download.py @@ -6,6 +6,11 @@ from plugins.models import Plugin, PluginVersion, PluginVersionDownload from plugins.views import version_download +from django.conf import settings +from django.core.cache import cache +from django.urls import reverse +from unittest.mock import patch +from plugins.forms import VersionDownloadForm class TestVersionDownloadView(TestCase): def setUp(self): @@ -50,3 +55,79 @@ def test_version_download(self): self.assertEqual(self.version.downloads, 1) self.assertEqual(self.plugin.downloads, 1) self.assertEqual(download_record.download_count, 1) + + +class VersionGetViewTest(TestCase): + fixtures = [ + "fixtures/styles.json", + "fixtures/auth.json", + "fixtures/simplemenu.json", + ] + def setUp(self): + self.factory = RequestFactory() + + self.user = User.objects.create_user( + username='testuser', + password='12345' + ) + + self.plugin = Plugin.objects.create( + package_name="test-package", + created_by=self.user, + name="Test Package" + ) + + self.version = PluginVersion.objects.create( + plugin=self.plugin, + version="1.0.0", + downloads=0, + created_by=self.user, + package=SimpleUploadedFile("test.zip", b"file_content"), + min_qg_version='3.1.1', + max_qg_version='3.3.0' + ) + self.url = reverse( + 'version_get', + kwargs={ + 'package_name': self.plugin.package_name, + 'version': self.version.version + } + ) + + self.ip = '127.0.0.1' + self.cache_key = f'download_limit_{self.ip}' + settings.DOWNLOAD_RATE_LIMIT = 5 # Set a rate limit for testing + + def tearDown(self): + # Clear cache after each test + cache.clear() + + def test_rate_limit_not_exceeded(self): + response = self.client.get(self.url) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'plugins/plugin_download.html') + + def test_rate_limit_exceeded(self): + # Simulate exceeding the rate limit + cache.set(self.cache_key, settings.DOWNLOAD_RATE_LIMIT, timeout=60) + response = self.client.get(self.url) + self.assertEqual(response.status_code, 429) + self.assertTemplateUsed(response, 'plugins/plugin_download_limit_exceed.html') + + def test_successful_download_post(self): + # Mocking _get_file_content function + with patch('plugins.views._get_file_content') as mock_get_file_content: + mock_get_file_content.return_value = ('file_content', 'file.zip') + response = self.client.post(self.url, {'plugin_name': 'Test Package'}) # Adjust form data as needed + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'plugins/plugin_download_success.html') + self.assertIn('file_content', response.context) + self.assertIn('file_name', response.context) + self.assertIn('plugin_name', response.context) + + def test_form_display_on_get_request(self): + response = self.client.get(self.url) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'plugins/plugin_download.html') + self.assertIsInstance(response.context['form'], VersionDownloadForm) + self.assertEqual(response.context['plugin_name'], self.plugin.name) \ No newline at end of file From e2e73f33c81c681396b6a4d0bd1af668cbd3d6a3 Mon Sep 17 00:00:00 2001 From: Lova ANDRIARIMALALA <43842786+Xpirix@users.noreply.github.com> Date: Fri, 17 May 2024 11:51:42 +0300 Subject: [PATCH 7/8] Disable ldap authentication in the env template --- dockerize/.env.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dockerize/.env.template b/dockerize/.env.template index 7147655c..87086092 100644 --- a/dockerize/.env.template +++ b/dockerize/.env.template @@ -31,7 +31,7 @@ DEFAULT_PLUGINS_SITE='https://plugins.qgis.org/' QGISPLUGINS_ENV=debug # Ldap -ENABLE_LDAP=True +ENABLE_LDAP=False # Download limit per minute DOWNLOAD_RATE_LIMIT=10 \ No newline at end of file From b6fd056d730dbd473a9321d790f31d8f74f1f302 Mon Sep 17 00:00:00 2001 From: Lova ANDRIARIMALALA <43842786+Xpirix@users.noreply.github.com> Date: Fri, 17 May 2024 14:15:56 +0300 Subject: [PATCH 8/8] Fix plugin feedback test, clear django cache before next request --- .../plugins/tests/test_plugin_version_feedback.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/qgis-app/plugins/tests/test_plugin_version_feedback.py b/qgis-app/plugins/tests/test_plugin_version_feedback.py index 8f7c7702..bdf17a26 100644 --- a/qgis-app/plugins/tests/test_plugin_version_feedback.py +++ b/qgis-app/plugins/tests/test_plugin_version_feedback.py @@ -10,6 +10,7 @@ from plugins.models import Plugin, PluginVersion, PluginVersionFeedback from plugins.views import version_feedback_notify from django.conf import settings +from django.core.cache import cache class SetupMixin: fixtures = ["fixtures/auth.json", "fixtures/simplemenu.json"] @@ -150,6 +151,9 @@ def test_staff_should_see_plugin_feedback_received(self): self.assertContains(response, "test plugin 1") self.assertNotContains(response, "test plugin 2") + # Clear django cache before sending another request + cache.clear() + # add feedback for plugin 2 PluginVersionFeedback.objects.create( version=self.version_2, @@ -170,6 +174,10 @@ def test_approved_plugin_should_not_show_in_feedback_received_list(self): list(response.context['object_list']), [self.plugin_1] ) + + # Clear django cache before sending another request + cache.clear() + self.version_1.approved = True self.version_1.save() response = self.client.get(self.url) @@ -186,6 +194,10 @@ def setUp(self): super().setUp() self.url = reverse("feedback_pending_plugins") + def tearDown(self): + # Clear cache after each test + cache.clear() + def test_non_staff_should_not_see_plugin_feedback_pending_list(self): response = self.client.get(self.url) self.assertEqual(response.status_code, 404) @@ -208,6 +220,9 @@ def test_staff_should_see_plugin_feedback_pending_list(self): self.assertContains(response, "test plugin 2") self.assertNotContains(response, "test plugin 1") + # Clear django cache before sending another request + cache.clear() + # add feedback for plugin 2 PluginVersionFeedback.objects.create( version=self.version_2,