From c1fd22af9b771f23cbd6170eb6dc62433cbf5c4a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Valenta?= <git@imaniti.org>
Date: Mon, 3 Apr 2023 23:35:32 +0200
Subject: [PATCH] add ClamAV support

---
 Dockerfile                                    |  2 ++
 .../0032_alter_signee_address_country.py      | 19 ++++++++++++
 contracts/models.py                           |  6 +++-
 env.example                                   |  3 ++
 registry/settings/base.py                     | 11 +++++--
 requirements/base.txt                         |  1 +
 shared/middlewares.py                         | 31 +++++++++++++++++++
 7 files changed, 70 insertions(+), 3 deletions(-)
 create mode 100644 contracts/migrations/0032_alter_signee_address_country.py
 create mode 100644 shared/middlewares.py

diff --git a/Dockerfile b/Dockerfile
index ee761c2..8419d0d 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -20,6 +20,8 @@ RUN DATABASE_URL=postgres://x/x \
     OIDC_RP_REALM_URL=x \
     OIDC_RP_CLIENT_ID=x \
     OIDC_RP_CLIENT_SECRET=x \
+    CLAMD_TCP_SOCKET=x \
+    CLAMD_TCP_ADDR=x \
     DEFAULT_COUNTRY=x \
     DEFAULT_CONTRACTEE_NAME=x \
     DEFAULT_CONTRACTEE_STREET=x \
diff --git a/contracts/migrations/0032_alter_signee_address_country.py b/contracts/migrations/0032_alter_signee_address_country.py
new file mode 100644
index 0000000..a4158bc
--- /dev/null
+++ b/contracts/migrations/0032_alter_signee_address_country.py
@@ -0,0 +1,19 @@
+# Generated by Django 4.1.4 on 2023-04-03 21:35
+
+import contracts.models
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('contracts', '0031_alter_contract_cost_unit_other_and_more'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='signee',
+            name='address_country',
+            field=models.CharField(blank=True, default=contracts.models.get_default_country, max_length=256, null=True, verbose_name='Země'),
+        ),
+    ]
diff --git a/contracts/models.py b/contracts/models.py
index 8fa16d2..ca97769 100644
--- a/contracts/models.py
+++ b/contracts/models.py
@@ -81,6 +81,10 @@ class RepresentativeMixin:
         return result
 
 
+def get_default_country():
+    return settings.DEFAULT_COUNTRY
+
+
 class Signee(CreatedByMixin, OwnPermissionsMixin, SignatureCountMixin, models.Model):
     name = models.CharField(
         max_length=256,
@@ -129,7 +133,7 @@ class Signee(CreatedByMixin, OwnPermissionsMixin, SignatureCountMixin, models.Mo
         blank=True,
         null=True,
         verbose_name="Země",
-        default=settings.DEFAULT_COUNTRY,
+        default=get_default_country,
     )
 
     ico_number = models.CharField(
diff --git a/env.example b/env.example
index 38e3261..797ac95 100644
--- a/env.example
+++ b/env.example
@@ -10,6 +10,9 @@ OIDC_RP_CLIENT_SECRET=VCn4LVAUc6RGLSup7VaAKsmrKUbWguaP
 
 DEFAULT_COUNTRY="Česká Republika"
 
+CLAMD_TCP_SOCKET=3310
+CLAMD_TCP_ADDR=127.0.0.1
+
 DEFAULT_CONTRACTEE_NAME="Česká pirátská strana"
 DEFAULT_CONTRACTEE_STREET="Na Moráni 360/3"
 DEFAULT_CONTRACTEE_ZIP="128 00"
diff --git a/registry/settings/base.py b/registry/settings/base.py
index e4da047..380f476 100644
--- a/registry/settings/base.py
+++ b/registry/settings/base.py
@@ -70,14 +70,14 @@ INSTALLED_APPS = [
 MIDDLEWARE = [
     "django.middleware.security.SecurityMiddleware",
     "django.contrib.sessions.middleware.SessionMiddleware",
+    "django.contrib.auth.middleware.AuthenticationMiddleware",
     "django.middleware.common.CommonMiddleware",
     "django.middleware.csrf.CsrfViewMiddleware",
-    "django.contrib.auth.middleware.AuthenticationMiddleware",
     "django.contrib.messages.middleware.MessageMiddleware",
     "django.middleware.clickjacking.XFrameOptionsMiddleware",
     "django_http_exceptions.middleware.ExceptionHandlerMiddleware",
     "django_http_exceptions.middleware.ThreadLocalRequestMiddleware",
-    # "django_downloadview.SmartDownloadMiddleware",
+    "shared.middlewares.ClamAVMiddleware"
 ]
 
 ROOT_URLCONF = "registry.urls"
@@ -216,6 +216,13 @@ ADMIN_ORDERING = {
 }
 
 
+# ClamAV
+
+CLAMD_USE_TCP = True
+CLAMD_TCP_SOCKET = env.int("CLAMD_TCP_SOCKET")
+CLAMD_TCP_ADDR = env.str("CLAMD_TCP_ADDR")
+
+
 ## App-specific
 
 DEFAULT_CONTRACTEE_NAME = env.str("DEFAULT_CONTRACTEE_NAME")
diff --git a/requirements/base.txt b/requirements/base.txt
index dc65d55..d4143b6 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -1,3 +1,4 @@
+clamd==1.0.2
 django==4.1.4
 django-admin-index==2.0.2
 django-admin-interface==0.24.2
diff --git a/shared/middlewares.py b/shared/middlewares.py
new file mode 100644
index 0000000..bd3058c
--- /dev/null
+++ b/shared/middlewares.py
@@ -0,0 +1,31 @@
+import clamd
+
+from io import BytesIO
+from django.http import HttpResponseForbidden
+
+class ClamAVMiddleware:
+    def __init__(self, get_response):
+        self.get_response = get_response
+        # One-time configuration and initialization.
+
+    def __call__(self, request):
+        # Code to be executed for each request before
+        # the view (and later middleware) are called.
+
+        cd = clamd.ClamdUnixSocket()
+
+        if request.method == "POST" and len(request.FILES) > 0:
+            for file_ in request.FILES.values():
+                scan_result = cd.instream(BytesIO(file_.read()))
+
+                if scan_result["stream"][0] == "FOUND":
+                    return HttpResponseForbidden(
+                        "Nahraný soubor obsahuje potenciálně škodlivý obsah."
+                    )
+
+        response = self.get_response(request)
+
+        # Code to be executed for each request/response after
+        # the view is called.
+
+        return response
-- 
GitLab