diff --git a/attachments/exceptions.py b/attachments/exceptions.py index 518c306..29415a1 100644 --- a/attachments/exceptions.py +++ b/attachments/exceptions.py @@ -1,3 +1,11 @@ class VirusFoundException(Exception): """Exception raised for detecting a virus in a file upload""" pass + +class FileSizeException(Exception): + """Exception raised for too large file size in a file upload""" + pass + +class FileTypeException(Exception): + """Exception raised for unallowable type in a file upload""" + pass diff --git a/attachments/templates/attachments/virus_email.txt b/attachments/templates/attachments/virus_email.txt new file mode 100644 index 0000000..794fa9e --- /dev/null +++ b/attachments/templates/attachments/virus_email.txt @@ -0,0 +1 @@ +{{ user|default_if_none:"A User" }} attempted to upload this file: {{ filename }} with virus signature: {{ virus_signature }} at {% now 'c' %}}. \ No newline at end of file diff --git a/attachments/templates/attachments/virus_subject.txt b/attachments/templates/attachments/virus_subject.txt new file mode 100644 index 0000000..029b4d2 --- /dev/null +++ b/attachments/templates/attachments/virus_subject.txt @@ -0,0 +1 @@ +VIRUS UPLOAD ALERT: {{ user|default_if_none:"A user" }} attempted to upload a file containing a virus to the system \ No newline at end of file diff --git a/attachments/utils.py b/attachments/utils.py index 767148a..a6d74b6 100644 --- a/attachments/utils.py +++ b/attachments/utils.py @@ -12,6 +12,13 @@ import uuid +def sizeof_fmt(num, suffix='B'): + for unit in ['','Ki','Mi','Gi','Ti','Pi','Ei','Zi']: + if abs(num) < 1024.0: + return "%3.1f%s%s" % (num, unit, suffix) + num /= 1024.0 + return "%.1f%s%s" % (num, 'Yi', suffix) + def get_context_key(context): if context: return 'attachments-%s' % context diff --git a/attachments/views.py b/attachments/views.py index 4aa6cf2..c10e1b3 100644 --- a/attachments/views.py +++ b/attachments/views.py @@ -4,8 +4,10 @@ from django.template import loader from django.utils.encoding import force_text from django.views.decorators.csrf import csrf_exempt +from django.core.mail import send_mail -from attachments.exceptions import VirusFoundException + +from attachments.exceptions import VirusFoundException, FileSizeException, FileTypeException from .forms import PropertyForm from .models import Attachment, Session @@ -30,6 +32,12 @@ def attach(request, session_id): content_type = 'text/plain' if request.POST.get('X-Requested-With', '') == 'IFrame' else 'application/json' try: f = request.FILES['attachment'] + max_file_size = getattr(settings, 'ATTACHMENTS_MAXIMUM_FILE_SIZE', False) + if max_file_size and f.size > max_file_size: + raise FileSizeException("File is too large to be uploaded, file cannot be greater than %s" % sizeof_fmt(max_file_size)) + allowed_file_types = getattr(settings, 'ATTACHMENTS_ALLOWED_FILE_TYPES', False) + if allowed_file_types and f.content_type not in allowed_file_types and f.content_type.split('/') not in allowed_file_types: + raise FileTypeException("You cannot upload this file type") file_uploaded.send(sender=f, request=request, session=session) # Copy the Django attachment (which may be a file or in memory) over to a temp file. temp_dir = getattr(settings, 'ATTACHMENT_TEMP_DIR', None) @@ -45,7 +53,7 @@ def attach(request, session_id): cd = pyclamd.ClamdUnixSocket() virus = cd.scan_file(path) if virus is not None: - # if ATTACHMENTS_QUARANTINE_PATH is set, move the offending file to the quaranine, otherwise delete + #if ATTACHMENTS_QUARANTINE_PATH is set, move the offending file to the quarantine, otherwise delete if getattr(settings, 'ATTACHMENTS_QUARANTINE_PATH', False): quarantine_path = os.path.join(getattr(settings, 'ATTACHMENTS_QUARANTINE_PATH'), os.path.basename(path)) os.rename(path, quarantine_path) @@ -60,8 +68,24 @@ def attach(request, session_id): session.data = {key: value} session.save() return JsonResponse({'ok': True, 'file_name': f.name, 'file_size': f.size}, content_type=content_type) + except FileSizeException as ex: + return JsonResponse({'ok': False, 'error': unicode(ex)}, content_type=content_type) + except FileTypeException as ex: + return JsonResponse({'ok': False, 'error': unicode(ex)}, content_type=content_type) except VirusFoundException as ex: - logger.exception(str(ex)) + user = getattr(request, 'user', None) + log_message = loader.render_to_string('attachments/virus_email.txt', + {'user': user, + 'filename' : f.name, + 'virus_signature' : virus[path][1], + }) + logger.exception(log_message) + #if ATTACHMENTS_VIRUS_EMAIL is set to a list/tuple of email addresses to send to it will send email alert + if getattr(settings, 'ATTACHMENTS_VIRUS_EMAIL', False): + #send email to email list + email_list = getattr(settings, 'ATTACHMENTS_VIRUS_EMAIL') + subject = loader.render_to_string('attachments/virus_subject.txt', {'user': user }) + send_mail(subject, log_message, settings.DEFAULT_FROM_EMAIL, email_list) return JsonResponse({'ok': False, 'error': force_text(ex)}, content_type=content_type) except Exception as ex: logger.exception('Error attaching file to session %s', session_id) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index a5f7315..85a4e03 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -56,4 +56,8 @@ Quickstart Guide 6. Set the ``ATTACHMENT_TEMP_DIR`` setting to the temporary directory you would like files to save in a settings file -7. (OPTIONAL) If you have the clamav daemon running on your server set ``ATTACHMENTS_CLAMD`` to true in a settings file. If you would like to set a path to quarantine infected files that are uploaded set ``ATTACHMENTS_QUARANTINE_PATH`` to desired path, if not set the default behavior will be to remove the files. Note that this currently only works for linux servers and the path to the clam socket will need to be set in /etc/clamav/clamd.conf or /etc/clamd.conf for this to work. +7. (OPTIONAL) If you have the clamav daemon running on your server set ``ATTACHMENTS_CLAMD`` to true in a settings file. If you would like email alerts set ``ATTACHMENTS_VIRUS_EMAIL`` to a list of recipients. If you would like to set a path to quarantine infected files that are uploaded set ``ATTACHMENTS_QUARANTINE_PATH`` to desired path, if not set the default behavior will be to remove the files. Note that this currently only works for linux servers and the path to the clam socket will need to be set in /etc/clamav/clamd.conf or /etc/clamd.conf for this to work. + +8. (OPTIONAL) If you would like to limit the the file types users are able to upload set ``ATTACHMENTS_ALLOWED_FILE_TYPES`` to a list or tuple of mimetypes in a settings file. IE: ALLOWED_FILE_TYPES = ['text/html'] (allowing only html files. You can also just set it to the the text after the first '/' as in [html]. Note that for some files like excel they have mimetypes like `application/vnd.ms-excel` so make sure that you check the mimetypes you want before setting them! + +9. (OPTIONAL) If you would like to set a maximum file size that users are able to upload set ``ATTACHMENTS_MAXIMUM_FILE_SIZE`` to an int of the maximum bytes you would like to allow. So a maximum size of 10MB would be MAXIMUM_FILE_SIZE = 10485760 Note that it is the binary bytes not the decimal! \ No newline at end of file