| @ -1,9 +1,13 @@ | |||||
| DEBUG=True | DEBUG=True | ||||
| TEMPLATE_DEBUG=True | TEMPLATE_DEBUG=True | ||||
| DJANGO_SECRET_KEY= | |||||
| DATABASE_ENGINE=django.db.backends.sqlite3 | DATABASE_ENGINE=django.db.backends.sqlite3 | ||||
| DATABASE_NAME=db.sqlite3 | DATABASE_NAME=db.sqlite3 | ||||
| DATABASE_USER= | DATABASE_USER= | ||||
| DATABASE_PASSWORD= | DATABASE_PASSWORD= | ||||
| DATABASE_HOST= | DATABASE_HOST= | ||||
| DATABASE_PORT= | DATABASE_PORT= | ||||
| # ClamAV settings | |||||
| CLAMAV_ENABLED=True | |||||
| CLAMAV_TCP_PORT=3310 | |||||
| CLAMAV_TCP_ADDR=localhost | |||||
| @ -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 | |||||
| @ -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 %} | |||||
| <h1><small>Proof of Concept:</small>File scanning</h1> | |||||
| <form method="post" enctype="multipart/form-data"> | |||||
| {% csrf_token %} | |||||
| <div class="question-well"> | |||||
| <h3>Upload a file to test virus scanning.</h3> | |||||
| <div class="form-group {% if form.upload_file.errors %}has-error{% elif validation_success %}has-success{% endif %}"> | |||||
| <div>{{ form.upload_file }}</div> | |||||
| {% if form.upload_file.errors %} | |||||
| <span class="help-block"> | |||||
| {% for err in form.upload_file.errors %}{{ err }}{% endfor %} | |||||
| </span> | |||||
| {% endif %} | |||||
| {% if validation_success %}<span class="help-block">No viruses found</span>{% endif %} | |||||
| </div> | |||||
| </div> | |||||
| <div class="form-buttons clearfix"> | |||||
| <button type="submit" class="btn btn-primary pull-right">Submit</button> | |||||
| </div> | |||||
| </form> | |||||
| {% endblock %} | |||||
| {% block formbuttons %} | |||||
| {% endblock %} | |||||
| {% block sidebarNav %} | |||||
| <!-- no sidebar --> | |||||
| {% endblock %} | |||||
| {% block sidebar %} | |||||
| <!-- no sidebar --> | |||||
| {% endblock %} | |||||
| @ -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') | |||||
| @ -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) | |||||
| @ -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 | |||||