From 69207239352fea6558694eda1f5178bf112868f9 Mon Sep 17 00:00:00 2001 From: Olivier Leger Date: Tue, 12 Mar 2024 13:52:14 -0400 Subject: [PATCH] Remove audit log --- onadata/apps/api/viewsets/xform_viewset.py | 30 ----- onadata/apps/logger/views.py | 25 +--- onadata/apps/main/models/__init__.py | 1 - onadata/apps/main/models/audit.py | 73 ----------- onadata/apps/main/tests/test_audit_log.py | 30 ----- onadata/apps/viewer/views.py | 39 ------ onadata/libs/utils/log.py | 135 --------------------- onadata/settings/base.py | 11 -- 8 files changed, 1 insertion(+), 343 deletions(-) delete mode 100644 onadata/apps/main/models/audit.py delete mode 100644 onadata/apps/main/tests/test_audit_log.py delete mode 100644 onadata/libs/utils/log.py diff --git a/onadata/apps/api/viewsets/xform_viewset.py b/onadata/apps/api/viewsets/xform_viewset.py index ede928867..174f57f7c 100644 --- a/onadata/apps/api/viewsets/xform_viewset.py +++ b/onadata/apps/api/viewsets/xform_viewset.py @@ -1,7 +1,6 @@ # coding: utf-8 import json import os -from datetime import datetime from django.contrib.auth.models import User from django.core.exceptions import ValidationError @@ -30,7 +29,6 @@ from onadata.libs.mixins.labels_mixin import LabelsMixin from onadata.libs.renderers import renderers from onadata.libs.serializers.xform_serializer import XFormSerializer -from onadata.libs.utils import log from onadata.libs.utils.common_tags import SUBMISSION_TIME from onadata.libs.utils.csv_import import submit_csv from onadata.libs.utils.export_tools import ( @@ -108,17 +106,6 @@ def _generate_new_export(request, xform, query, export_type): export_type, extension, xform.user.username, xform.id_string, None, query ) - audit = { - "xform": xform.id_string, - "export_type": export_type - } - log.audit_log( - log.Actions.EXPORT_CREATED, request.user, xform.user, - t("Created %(export_type)s export on '%(id_string)s'.") % - { - 'id_string': xform.id_string, - 'export_type': export_type.upper() - }, audit, request) except NoRecordsFoundError: raise Http404(t("No records found to export")) else: @@ -172,21 +159,6 @@ def value_for_type(form, field, value): return value -def log_export(request, xform, export_type): - # log download as well - audit = { - "xform": xform.id_string, - "export_type": export_type - } - log.audit_log( - log.Actions.EXPORT_DOWNLOADED, request.user, xform.user, - t("Downloaded %(export_type)s export on '%(id_string)s'.") % - { - 'id_string': xform.id_string, - 'export_type': export_type.upper() - }, audit, request) - - def custom_response_handler(request, xform, query, export_type): export_type = _get_export_type(export_type) @@ -200,8 +172,6 @@ def custom_response_handler(request, xform, query, export_type): # tends to happen when using newset_export_for. export = _generate_new_export(request, xform, query, export_type) - log_export(request, xform, export_type) - # get extension from file_path, exporter could modify to # xlsx if it exceeds limits path, ext = os.path.splitext(export.filename) diff --git a/onadata/apps/logger/views.py b/onadata/apps/logger/views.py index 79c5bd9fe..d47d52388 100644 --- a/onadata/apps/logger/views.py +++ b/onadata/apps/logger/views.py @@ -2,12 +2,9 @@ import json import os import tempfile -import re -from datetime import datetime, date -from django.contrib.auth.decorators import login_required, user_passes_test +from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User -from django.contrib import messages from django.core.files.storage import default_storage from django.http import ( HttpResponse, @@ -16,7 +13,6 @@ HttpResponseNotFound, HttpResponseRedirect, StreamingHttpResponse, - Http404, ) from django.shortcuts import get_object_or_404 from django.shortcuts import render @@ -25,11 +21,8 @@ from django.views.decorators.http import require_POST from django.views.decorators.csrf import csrf_exempt -from onadata.apps.main.models import UserProfile from onadata.apps.logger.import_tools import import_instances_from_zip from onadata.apps.logger.models.xform import XForm -from onadata.libs.authentication import digest_authentication -from onadata.libs.utils.log import audit_log, Actions from onadata.libs.utils.logger_tools import BaseOpenRosaResponse from onadata.libs.utils.logger_tools import response_with_mimetype_and_name from onadata.libs.utils.user_auth import ( @@ -37,7 +30,6 @@ has_permission, add_cors_headers, ) -from .tasks import generate_stats_zip from ...koboform.pyxform_utils import convert_csv_to_xls IO_ERROR_STRINGS = [ @@ -128,11 +120,6 @@ def bulksubmission(request, username): 'rejected': total_count - success_count}, 'errors': "%d %s" % (len(errors), errors) } - audit = { - "bulk_submission_log": json_msg - } - audit_log(Actions.USER_BULK_SUBMISSION, request.user, posting_user, - t("Made bulk submissions."), audit, request) response = HttpResponse(json.dumps(json_msg)) response.status_code = 200 response['Location'] = request.build_absolute_uri(request.path) @@ -161,16 +148,6 @@ def download_xlsform(request, username, id_string): file_path = xform.xls.name if file_path != '' and default_storage.exists(file_path): - audit = { - "xform": xform.id_string - } - audit_log( - Actions.FORM_XLS_DOWNLOADED, request.user, xform.user, - t("Downloaded XLS file for form '%(id_string)s'.") % - { - "id_string": xform.id_string - }, audit, request) - if file_path.endswith('.csv'): with default_storage.open(file_path) as ff: xls_io = convert_csv_to_xls(ff.read()) diff --git a/onadata/apps/main/models/__init__.py b/onadata/apps/main/models/__init__.py index b48dbcbf8..97a653cb4 100644 --- a/onadata/apps/main/models/__init__.py +++ b/onadata/apps/main/models/__init__.py @@ -1,4 +1,3 @@ # coding: utf-8 from .user_profile import UserProfile # flake8: noqa from .meta_data import MetaData -from .audit import AuditLog diff --git a/onadata/apps/main/models/audit.py b/onadata/apps/main/models/audit.py deleted file mode 100644 index 8f5c593c7..000000000 --- a/onadata/apps/main/models/audit.py +++ /dev/null @@ -1,73 +0,0 @@ -# coding: utf-8 -from datetime import datetime, timedelta - -from django.conf import settings - -from onadata.apps.viewer.models.parsed_instance import DATETIME_FORMAT -from onadata.apps.api.mongo_helper import MongoHelper - -audit = settings.MONGO_DB.auditlog -DEFAULT_LIMIT = 1000 - - -class AuditLog: - ACCOUNT = "account" - DEFAULT_BATCHSIZE = 1000 - CREATED_ON = "created_on" - - def __init__(self, data): - self.data = data - - def save(self): - return audit.insert_one(self.data) - - @classmethod - def query_mongo(cls, username, query=None, fields=None, sort=None, start=0, - limit=DEFAULT_LIMIT, count=False): - query = MongoHelper.to_safe_dict(query) if query else {} - query[cls.ACCOUNT] = username - # TODO find better method - # check for the created_on key in query and turn its values into dates - if type(query) == dict and cls.CREATED_ON in query: - if type(query[cls.CREATED_ON]) is dict: - for op, val in query[cls.CREATED_ON].items(): - try: - query[cls.CREATED_ON][op] = datetime.strptime( - val, DATETIME_FORMAT) - except ValueError: - pass - elif isinstance(query[cls.CREATED_ON], str): - val = query[cls.CREATED_ON] - try: - created_on = datetime.strptime(val, DATETIME_FORMAT) - except ValueError: - pass - else: - # create start and end times for the entire day - start_time = created_on.replace(hour=0, minute=0, - second=0, microsecond=0) - end_time = start_time + timedelta(days=1) - query[cls.CREATED_ON] = {"$gte": start_time, - "$lte": end_time} - - if count: - return [{"count": audit.count_documents(query)}] - - fields_to_select = None - # TODO: current mongo (3.4 of this writing) - # cant mix including and excluding fields in a single query - if type(fields) == list and len(fields) > 0: - fields_to_select = dict([(MongoHelper.encode(field), 1) - for field in fields]) - - cursor = audit.find(query, fields_to_select) - - cursor.skip(max(start, 0)).limit(limit) - if type(sort) == dict and len(sort) == 1: - sort = MongoHelper.to_safe_dict(sort, reading=True) - sort_key = list(sort)[0] - sort_dir = int(sort[sort_key]) # -1 for desc, 1 for asc - cursor.sort(sort_key, sort_dir) - # set batch size for cursor iteration - cursor.batch_size = cls.DEFAULT_BATCHSIZE - return cursor diff --git a/onadata/apps/main/tests/test_audit_log.py b/onadata/apps/main/tests/test_audit_log.py deleted file mode 100644 index 9844c063e..000000000 --- a/onadata/apps/main/tests/test_audit_log.py +++ /dev/null @@ -1,30 +0,0 @@ -# coding: utf-8 -from django.contrib.auth.models import User -from django.test import TestCase -from django.test.client import RequestFactory - -from onadata.libs.utils.log import audit_log, Actions -from onadata.apps.main.models import AuditLog - - -class TestAuditLog(TestCase): - def test_audit_log_call(self): - account_user = User(username="alice") - request_user = User(username="bob") - request = RequestFactory().get("/") - # create a log - audit = {} - audit_log(Actions.FORM_PUBLISHED, request_user, account_user, - "Form published", audit, request) - # function should just run without exception so we are good at this - # point query for this log entry - sort = {"created_on": -1} - cursor = AuditLog.query_mongo( - account_user.username, None, None, sort, 0, 1) - result = AuditLog.query_mongo(account_user.username, count=True) - - self.assertTrue(result[0]['count'] > 0) - record = next(cursor) - self.assertEqual(record['account'], "alice") - self.assertEqual(record['user'], "bob") - self.assertEqual(record['action'], Actions.FORM_PUBLISHED) diff --git a/onadata/apps/viewer/views.py b/onadata/apps/viewer/views.py index c04eb237d..e8aa47616 100644 --- a/onadata/apps/viewer/views.py +++ b/onadata/apps/viewer/views.py @@ -31,7 +31,6 @@ from onadata.apps.viewer.tasks import create_async_export from onadata.libs.authentication import digest_authentication from onadata.libs.utils.image_tools import image_url -from onadata.libs.utils.log import audit_log, Actions from onadata.libs.utils.logger_tools import response_with_mimetype_and_name from onadata.libs.utils.user_auth import ( HttpResponseNotAuthorized, @@ -79,17 +78,6 @@ def create_export(request, username, id_string, export_type): return HttpResponseBadRequest( t("%s is not a valid export type" % export_type)) else: - audit = { - "xform": xform.id_string, - "export_type": export_type - } - audit_log( - Actions.EXPORT_CREATED, request.user, owner, - t("Created %(export_type)s export on '%(id_string)s'.") % - { - 'export_type': export_type.upper(), - 'id_string': xform.id_string, - }, audit, request) return HttpResponseRedirect(reverse( export_list, kwargs={ @@ -171,20 +159,6 @@ def export_download(request, username, id_string, export_type, filename): ext, mime_type = export_def_from_filename(export.filename) - audit = { - "xform": xform.id_string, - "export_type": export.export_type - } - audit_log( - Actions.EXPORT_DOWNLOADED, request.user, owner, - t("Downloaded %(export_type)s export '%(filename)s' " - "on '%(id_string)s'.") % - { - 'export_type': export.export_type.upper(), - 'filename': export.filename, - 'id_string': xform.id_string, - }, audit, request) - if not isinstance(default_storage, FileSystemStorage): return HttpResponseRedirect(default_storage.url(export.filepath)) @@ -213,19 +187,6 @@ def delete_export(request, username, id_string, export_type): export = get_object_or_404(Export, id=export_id) export.delete() - audit = { - "xform": xform.id_string, - "export_type": export.export_type - } - audit_log( - Actions.EXPORT_DOWNLOADED, request.user, owner, - t("Deleted %(export_type)s export '%(filename)s'" - " on '%(id_string)s'.") % - { - 'export_type': export.export_type.upper(), - 'filename': export.filename, - 'id_string': xform.id_string, - }, audit, request) return HttpResponseRedirect(reverse( export_list, kwargs={ diff --git a/onadata/libs/utils/log.py b/onadata/libs/utils/log.py deleted file mode 100644 index c8014369f..000000000 --- a/onadata/libs/utils/log.py +++ /dev/null @@ -1,135 +0,0 @@ -# coding: utf-8 -import logging -from datetime import datetime -from onadata.libs.utils.viewer_tools import get_client_ip - - -class Enum: - __name__ = "Enum" - - def __init__(self, **enums): - self.enums = enums - - def __getattr__(self, item): - return self.enums[item] - - def __getitem__(self, item): - return self.__getattr__(item) - - def __iter__(self): - return iter(self.enums.values()) - - -Actions = Enum( - PROFILE_ACCESSED="profile-accessed", - PUBLIC_PROFILE_ACCESSED="public-profile-accessed", - PROFILE_SETTINGS_UPDATED="profile-settings-updated", - USER_LOGIN="user-login", - USER_LOGOUT="user-logout", - USER_BULK_SUBMISSION="bulk-submissions-made", - USER_FORMLIST_REQUESTED="formlist-requested", - FORM_ACCESSED="form-accessed", - FORM_PUBLISHED="form-published", - FORM_UPDATED="form-updated", - FORM_XLS_DOWNLOADED="form-xls-downloaded", - FORM_XLS_UPDATED="form-xls-updated", - FORM_DELETED="form-deleted", - FORM_CLONED="form-cloned", - FORM_XML_DOWNLOADED="form-xml-downloaded", - FORM_JSON_DOWNLOADED="form-json-downloaded", - FORM_PERMISSIONS_UPDATED="form-permissions-updated", - FORM_ENTER_DATA_REQUESTED="form-enter-data-requested", - FORM_MAP_VIEWED="form-map-viewed", - FORM_DATA_VIEWED="form-data-viewed", - EXPORT_CREATED="export-created", - EXPORT_DOWNLOADED="export-downloaded", - EXPORT_DELETED="export-deleted", - EXPORT_LIST_REQUESTED="export-list-requested", - SUBMISSION_CREATED="submission-created", - SUBMISSION_UPDATED="submission-updated", - SUBMISSION_DELETED="submission-deleted", - SUBMISSION_ACCESSED="submission-accessed", - SUBMISSION_EDIT_REQUESTED="submission-edit-requested", -) - - -class AuditLogHandler(logging.Handler): - - def __init__(self, model=""): - super().__init__() - self.model_name = model - - def _format(self, record): - data = { - 'action': record.formhub_action, - 'user': record.request_username, - 'account': record.account_username, - 'audit': {}, - 'msg': record.msg, - # save as python datetime object - # to have mongo convert to ISO date and allow queries - 'created_on': datetime.utcfromtimestamp(record.created), - 'levelno': record.levelno, - 'levelname': record.levelname, - 'args': record.args, - 'funcName': record.funcName, - 'msecs': record.msecs, - 'relativeCreated': record.relativeCreated, - 'thread': record.thread, - 'name': record.name, - 'threadName': record.threadName, - 'exc_info': record.exc_info, - 'pathname': record.pathname, - 'exc_text': record.exc_text, - 'lineno': record.lineno, - 'process': record.process, - 'filename': record.filename, - 'module': record.module, - 'processName': record.processName - } - if hasattr(record, 'audit') and isinstance(record.audit, dict): - data['audit'] = record.audit - return data - - def emit(self, record): - data = self._format(record) - # save to mongodb audit_log - try: - model = self.get_model(self.model_name) - except: - pass - else: - log_entry = model(data) - log_entry.save() - - def get_model(self, name): - names = name.split('.') - mod = __import__('.'.join(names[:-1]), fromlist=names[-1:]) - return getattr(mod, names[-1]) - - -def audit_log(action, request_user, account_user, message, audit, request, - level=logging.DEBUG): - """ - Create a log message based on these params - - @param action: Action performed e.g. form-deleted - @param request_username: User performing the action - @param account_username: The formhub account the action was performed on - @param message: The message to be displayed on the log - @param level: log level - @param audit: a dict of key/values of other info pertaining to the action - e.g. form's id_string, submission uuid - @return: None - """ - logger = logging.getLogger("audit_logger") - extra = { - 'formhub_action': action, - 'request_username': - request_user.username if request_user.username else str(request_user), - 'account_username': - account_user.username if account_user.username else str(account_user), - 'client_ip': get_client_ip(request), - 'audit': audit - } - logger.log(level, message, extra=extra) diff --git a/onadata/settings/base.py b/onadata/settings/base.py index 1f779e620..826f35052 100644 --- a/onadata/settings/base.py +++ b/onadata/settings/base.py @@ -260,12 +260,6 @@ def skip_suspicious_operations(record): 'formatter': 'verbose', 'stream': sys.stdout }, - 'audit': { - 'level': 'DEBUG', - 'class': 'onadata.libs.utils.log.AuditLogHandler', - 'formatter': 'verbose', - 'model': 'onadata.apps.main.models.audit.AuditLog' - } }, 'loggers': { 'django.request': { @@ -278,11 +272,6 @@ def skip_suspicious_operations(record): 'level': 'DEBUG', 'propagate': True }, - 'audit_logger': { - 'handlers': ['audit'], - 'level': 'DEBUG', - 'propagate': True - } } }