From 60c56180654379c3c06972a86f1a624fd8b23ddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Sun, 31 Dec 2023 11:09:08 +0100 Subject: [PATCH] misc: audit redirects to files on remote storage (#73481) --- tests/backoffice_pages/test_audit.py | 19 +++++++++++++++++++ tests/test_upload_storage.py | 13 ++++++++++++- wcs/audit.py | 1 + wcs/backoffice/journal.py | 5 ++++- wcs/forms/common.py | 2 ++ 5 files changed, 38 insertions(+), 2 deletions(-) diff --git a/tests/backoffice_pages/test_audit.py b/tests/backoffice_pages/test_audit.py index 7840f4a09..bdcf209f9 100644 --- a/tests/backoffice_pages/test_audit.py +++ b/tests/backoffice_pages/test_audit.py @@ -1,4 +1,5 @@ import datetime +import os import pytest @@ -184,6 +185,24 @@ def test_audit_journal(pub, superuser): ) +def test_audit_journal_remote_access(pub, superuser): + app = login(get_app(pub)) + resp = app.get('/backoffice/journal/') + assert 'Redirect to remote stored file' not in [x[2] for x in resp.form['action'].options] + + if not pub.site_options.has_section('options'): + pub.site_options.add_section('options') + pub.site_options.add_section('storage-remote') + pub.site_options.set('storage-remote', 'label', 'remote') + pub.site_options.set('storage-remote', 'class', 'wcs.qommon.upload_storage.RemoteOpaqueUploadStorage') + pub.site_options.set('storage-remote', 'ws', 'https://crypto.example.net/') + with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd: + pub.site_options.write(fd) + + resp = app.get('/backoffice/journal/') + assert 'Redirect to remote stored file' in [x[2] for x in resp.form['action'].options] + + def test_audit_journal_access(pub, superuser): role = pub.role_class(name='foobar') role.allows_backoffice_access = True diff --git a/tests/test_upload_storage.py b/tests/test_upload_storage.py index 9c2724b3a..c72c67b2c 100644 --- a/tests/test_upload_storage.py +++ b/tests/test_upload_storage.py @@ -8,8 +8,10 @@ from django.utils.encoding import force_bytes from webtest import Upload from wcs import fields +from wcs.audit import Audit from wcs.formdef import FormDef from wcs.qommon.ident.password_accounts import PasswordAccount +from wcs.sql import Equal from wcs.wf.register_comment import RegisterCommenterWorkflowStatusItem from .utilities import clean_temporary_pub, create_temporary_pub, get_app, login @@ -200,6 +202,12 @@ def test_form_file_field_upload_storage(wscall, pub): assert 'href="download?f=0"' in resp.text assert 'href="download?f=1"' in resp.text # link is present on backoffice + # check access is recorded + Audit.wipe() + resp = resp.click('remote.jpg') + assert resp.status_code == 302 + assert Audit.count([Equal('action', 'redirect remote stored file')]) == 1 + # file size limit verification formdef.fields[1].max_file_size = '1ko' formdef.store() @@ -343,9 +351,12 @@ def test_remoteopaque_in_attachmentevolutionpart(wscall, pub): resp = user_app.get('/test/%s/attachment?f=%s' % (formdata.id, remote_file_id)) assert resp.status_int == 302 resp = resp.follow(status=404) - # clic in backoffice, redirect to decryption system + # click in backoffice, redirect to decryption system + Audit.wipe() resp = admin_app.get('/backoffice/management/test/%s/attachment?f=%s' % (formdata.id, remote_file_id)) assert resp.status_int == 302 resp = resp.follow() assert resp.location.startswith('https://crypto.example.net/') assert '&signature=' in resp.location + # check access is recorded + assert Audit.count([Equal('action', 'redirect remote stored file')]) == 1 diff --git a/wcs/audit.py b/wcs/audit.py index 7b83df674..2ff2c94d7 100644 --- a/wcs/audit.py +++ b/wcs/audit.py @@ -77,6 +77,7 @@ class Audit(sql.Audit): 'export.ods': _('ODS Export'), 'download file': _('Download of attached file'), 'download files': _('Download of attached files (bundle)'), + 'redirect to remote stored file': _('Redirect to remote stored file'), 'view': _('View Data'), 'settings': _('Change to global settings'), } diff --git a/wcs/backoffice/journal.py b/wcs/backoffice/journal.py index 7c419f1b5..2c4903f5f 100644 --- a/wcs/backoffice/journal.py +++ b/wcs/backoffice/journal.py @@ -148,11 +148,14 @@ class JournalDirectory(Directory): widget = form.add(StringWidget, 'object_id', title=_('Form/Card Identifier')) if not form.get_widget('object').parse(): widget.is_hidden = True + options = Audit.get_action_labels().items() + if not get_publisher().get_site_storages(): + options = [x for x in options if x[0] != 'redirect to remote stored file'] form.add( SingleSelectWidget, 'action', title=_('Action'), - options=[('', '', '')] + [(x[0], x[1], x[0]) for x in Audit.get_action_labels().items()], + options=[('', '', '')] + [(x[0], x[1], x[0]) for x in options], ) form.add_submit('submit', _('Search')) return form diff --git a/wcs/forms/common.py b/wcs/forms/common.py index 9c8d94ea7..ee0b881de 100644 --- a/wcs/forms/common.py +++ b/wcs/forms/common.py @@ -82,6 +82,7 @@ class FileDirectory(Directory): if not redirect_url: raise errors.TraversalError() redirect_url = sign_url_auto_orig(redirect_url) + audit('redirect remote stored file', obj=self.formdata, extra_label=component) return redirect(redirect_url) if not self.thumbnails: @@ -788,6 +789,7 @@ class FormStatusPage(Directory, FormTemplateMixin): if not redirect_url: raise errors.TraversalError() redirect_url = sign_url_auto_orig(redirect_url) + audit('redirect remote stored file', obj=self.filled) return redirect(redirect_url) file_url = 'files/%s/' % fn