| @ -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 | |||
| @ -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 | |||