diff --git a/.env.example b/.env.example index 05d0a483..c3708431 100644 --- a/.env.example +++ b/.env.example @@ -1,9 +1,13 @@ DEBUG=True TEMPLATE_DEBUG=True -DJANGO_SECRET_KEY= DATABASE_ENGINE=django.db.backends.sqlite3 DATABASE_NAME=db.sqlite3 DATABASE_USER= DATABASE_PASSWORD= DATABASE_HOST= DATABASE_PORT= + +# ClamAV settings +CLAMAV_ENABLED=True +CLAMAV_TCP_PORT=3310 +CLAMAV_TCP_ADDR=localhost diff --git a/.gitignore b/.gitignore index f1a96cf4..38c3a5e0 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ var/ *.egg-info/ .installed.cfg *.egg +.python-version # PyInstaller # Usually these files are written by a python script from a template @@ -411,3 +412,5 @@ ASALocalRun/ # MFractors (Xamarin productivity tool) working folder .mfractor/ + +edivorce/share \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..7344e9f9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,12 @@ +version: "3.7" +services: + + # Antivirus + antivirus: + container_name: edivorce-antivirus + hostname: antivirus + build: https://github.com/bcgov/clamav.git + ports: + - "3310:3310" + restart: always + diff --git a/edivorce/apps/core/templates/poc/upload.html b/edivorce/apps/core/templates/poc/upload.html new file mode 100644 index 00000000..84a12da3 --- /dev/null +++ b/edivorce/apps/core/templates/poc/upload.html @@ -0,0 +1,47 @@ +{% extends 'base.html' %} +{% load input_field %} +{% load step_order %} +{% load load_json %} + +{% block title %}{{ block.super }}: POC{% endblock %} + +{% block progress %}{% include "partials/progress.html" %}{% endblock %} + +{% block content %} +

Proof of Concept:File scanning

+ +
+ {% csrf_token %} + +
+

Upload a file to test virus scanning.

+
+
{{ form.upload_file }}
+ {% if form.upload_file.errors %} + + {% for err in form.upload_file.errors %}{{ err }}{% endfor %} + + {% endif %} + {% if validation_success %}No viruses found{% endif %} +
+
+ +
+ +
+ +
+ +{% endblock %} + +{% block formbuttons %} + +{% endblock %} + +{% block sidebarNav %} + +{% endblock %} + +{% block sidebar %} + +{% endblock %} diff --git a/edivorce/apps/core/urls.py b/edivorce/apps/core/urls.py index 40d486c6..8eb2850b 100644 --- a/edivorce/apps/core/urls.py +++ b/edivorce/apps/core/urls.py @@ -1,6 +1,9 @@ from django.conf.urls import url +from django.conf import settings + +from .views import main, system, pdf, api, localdev, poc +from .decorators import bceid_required -from .views import main, system, pdf, api, localdev urlpatterns = [ # url(r'^guide$', styleguide.guide), @@ -32,3 +35,8 @@ urlpatterns = [ url(r'^current$', system.current, name="current"), url(r'^$', main.home, name="home"), ] + +if settings.DEBUG: + urlpatterns = urlpatterns + [ + url(r'poc/upload', bceid_required(poc.UploadScan.as_view()), name="poc-upload"), + ] \ No newline at end of file diff --git a/edivorce/apps/core/validators.py b/edivorce/apps/core/validators.py new file mode 100644 index 00000000..adf83148 --- /dev/null +++ b/edivorce/apps/core/validators.py @@ -0,0 +1,44 @@ +import logging +import clamd +import sys + +from django.core.exceptions import ValidationError +from django.conf import settings + +logger = logging.getLogger(__name__) + + +def file_scan_validation(file): + """ + This validator sends the file to ClamAV for scanning and returns returns to the form. By default, if antivirus + service is not available or there are errors, the validation will fail. + + Usage: + class UploadForm(forms.Form): + file = forms.FileField(validators=[file_scan_validation]) + :param file: + :return: + """ + logger.debug("starting file scanning with clamav") + if not settings.CLAMAV_ENABLED: + logger.warning('File scanning has been disabled.') + return + + # make sure we're at the beginning of the file stream + file.seek(0) + + # we're just going to assume a network connection to clamav here .. no local unix socket support + scanner = clamd.ClamdNetworkSocket(settings.CLAMAV_TCP_ADDR, settings.CLAMAV_TCP_PORT) + try: + result = scanner.instream(file) + except: + # it doesn't really matter what the actual error is .. log it and raise validation error + logger.error('Error occurred while trying to scan file. "{}"'.format(sys.exc_info()[0])) + raise ValidationError('Unable to scan file.', code='scanerror') + finally: + # reset file stream + file.seek(0) + + if result and result['stream'][0] == 'FOUND': + logger.warning('Virus found: {}'.format(file.name)) + raise ValidationError('Infected file found.', code='infected') diff --git a/edivorce/apps/core/views/poc.py b/edivorce/apps/core/views/poc.py new file mode 100644 index 00000000..ef04a514 --- /dev/null +++ b/edivorce/apps/core/views/poc.py @@ -0,0 +1,23 @@ +from django.shortcuts import render +from django.views.generic.edit import FormView +from django import forms + +from ..validators import file_scan_validation + +""" +Everything in this file is considered as proof of concept work and should not be used for production code. +""" + + +class UploadForm(forms.Form): + upload_file = forms.FileField(validators=[file_scan_validation]) + + +class UploadScan(FormView): + form_class = UploadForm + template_name = "poc/upload.html" + + def form_valid(self, form): + context = self.get_context_data() + context['validation_success'] = True + return render(self.request, self.template_name, context) diff --git a/edivorce/settings/base.py b/edivorce/settings/base.py index 516a0deb..e5e106dc 100644 --- a/edivorce/settings/base.py +++ b/edivorce/settings/base.py @@ -11,8 +11,12 @@ https://docs.djangoproject.com/en/1.8/ref/settings/ """ import os +from environs import Env from unipath import Path +env = Env() +env.read_env() # read .env file, if it exists + # Build paths inside the project like this: os.path.join(BASE_DIR, ...) PROJECT_ROOT = Path(__file__).parent.parent.parent BASE_DIR = Path(__file__).parent.parent @@ -145,3 +149,9 @@ DEBUG_TOOLBAR_CONFIG = { SECURE_BROWSER_XSS_FILTER = True LOGOUT_URL = '/accounts/logout/' + + +# CLAMAV settings +CLAMAV_ENABLED = env.bool('CLAMAV_ENABLED', True) +CLAMAV_TCP_PORT = env.int('CLAMAV_TCP_PORT', 3310) +CLAMAV_TCP_ADDR = env('CLAMAV_TCP_ADDR', 'localhost') diff --git a/requirements.txt b/requirements.txt index e6239d63..d150b7ab 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,26 @@ -Django>=2.2,<3.0 -django-compressor>=2.4,<3.0 -django-crispy-forms>=1.9,<2.0 -django-debug-toolbar>=2.2,<3.0 -django-sass-processor>=0.8,<1.0 -djangorestframework>=3.11,<4.0 -gunicorn>=20.0,<21.0 -libsass>=0.20.0<1.0 -psycopg2>=2.8,<3.0 -requests>=2.24,<3.0 -six>=1.15,<2.0 -Unipath>=1.1,<2.0 -whitenoise>=3.3.1,<4.0 +certifi==2020.6.20 +chardet==3.0.4 +clamd==1.0.2 +Django==2.2.15 +django-appconf==1.0.4 +django-compressor==2.4 +django-crispy-forms==1.9.2 +django-debug-toolbar==2.2 +django-sass-processor==0.8 +djangorestframework==3.11.1 +environs==8.0.0 +gunicorn==20.0.4 +idna==2.10 +libsass==0.20.0 +marshmallow==3.7.1 +psycopg2==2.8.5 +python-dotenv==0.14.0 +pytz==2020.1 +rcssmin==1.0.6 +requests==2.24.0 +rjsmin==1.1.0 +six==1.15.0 +sqlparse==0.3.1 +Unipath==1.1 +urllib3==1.25.10 +whitenoise==3.3.1