From f70f60713e22ed3bf1b73951651c9abc5b4becfe Mon Sep 17 00:00:00 2001 From: Marcin Lulek Date: Thu, 3 Oct 2019 13:32:59 +0200 Subject: [PATCH] audit-logs: expose download user audit logs as JSON file. Primarly in larger organizations often this needs to be performed for some employees. Before we delegated users to API or DB fetch. --- rhodecode/apps/admin/__init__.py | 4 ++++ .../apps/admin/tests/test_admin_users.py | 23 +++++++++++++------ rhodecode/apps/admin/views/users.py | 23 +++++++++++++++++++ rhodecode/public/js/rhodecode/routes.js | 1 + .../admin/users/user_edit_audit.mako | 1 + 5 files changed, 45 insertions(+), 7 deletions(-) diff --git a/rhodecode/apps/admin/__init__.py b/rhodecode/apps/admin/__init__.py index d0635f44..3f7dd1d2 100644 --- a/rhodecode/apps/admin/__init__.py +++ b/rhodecode/apps/admin/__init__.py @@ -378,6 +378,10 @@ def admin_routes(config): name='edit_user_audit_logs', pattern='/users/{user_id:\d+}/edit/audit', user_route=True) + config.add_route( + name='edit_user_audit_logs_download', + pattern='/users/{user_id:\d+}/edit/audit/download', user_route=True) + # user caches config.add_route( name='edit_user_caches', diff --git a/rhodecode/apps/admin/tests/test_admin_users.py b/rhodecode/apps/admin/tests/test_admin_users.py index 01520d58..49c800ea 100644 --- a/rhodecode/apps/admin/tests/test_admin_users.py +++ b/rhodecode/apps/admin/tests/test_admin_users.py @@ -91,6 +91,9 @@ def route_path(name, params=None, **kwargs): 'edit_user_audit_logs': ADMIN_PREFIX + '/users/{user_id}/edit/audit', + 'edit_user_audit_logs_download': + ADMIN_PREFIX + '/users/{user_id}/edit/audit/download', + }[name].format(**kwargs) if params: @@ -318,7 +321,6 @@ class TestAdminUsersView(TestController): route_path('edit_user_emails', user_id=user_id)) response.mustcontain(no=['example@rhodecode.com']) - def test_create(self, request, xhr_header): self.log_user() username = 'newtestuser' @@ -531,8 +533,7 @@ class TestAdminUsersView(TestController): params={'csrf_token': self.csrf_token}) msg = 'user "%s" still owns 1 repositories and cannot be removed. ' \ - 'Switch owners or remove those repositories:%s' % (username, - obj_name) + 'Switch owners or remove those repositories:%s' % (username, obj_name) assert_session_flash(response, msg) fixture.destroy_repo(obj_name) @@ -583,8 +584,7 @@ class TestAdminUsersView(TestController): params={'csrf_token': self.csrf_token}) msg = 'user "%s" still owns 1 repository groups and cannot be removed. ' \ - 'Switch owners or remove those repository groups:%s' % (username, - obj_name) + 'Switch owners or remove those repository groups:%s' % (username, obj_name) assert_session_flash(response, msg) fixture.destroy_repo_group(obj_name) @@ -635,8 +635,7 @@ class TestAdminUsersView(TestController): params={'csrf_token': self.csrf_token}) msg = 'user "%s" still owns 1 user groups and cannot be removed. ' \ - 'Switch owners or remove those user groups:%s' % (username, - obj_name) + 'Switch owners or remove those user groups:%s' % (username, obj_name) assert_session_flash(response, msg) fixture.destroy_user_group(obj_name) @@ -779,3 +778,13 @@ class TestAdminUsersView(TestController): user = self.log_user() self.app.get( route_path('edit_user_audit_logs', user_id=user['user_id'])) + + def test_audit_log_page_download(self): + user = self.log_user() + user_id = user['user_id'] + response = self.app.get( + route_path('edit_user_audit_logs_download', user_id=user_id)) + + assert response.content_disposition == \ + 'attachment; filename=user_{}_audit_logs.json'.format(user_id) + assert response.content_type == "application/json" diff --git a/rhodecode/apps/admin/views/users.py b/rhodecode/apps/admin/views/users.py index 08710d01..4e104b17 100644 --- a/rhodecode/apps/admin/views/users.py +++ b/rhodecode/apps/admin/views/users.py @@ -1197,6 +1197,29 @@ class UsersView(UserAppView): c.filter_term = filter_term return self._get_template_context(c) + @LoginRequired() + @HasPermissionAllDecorator('hg.admin') + @view_config( + route_name='edit_user_audit_logs_download', request_method='GET', + renderer='string') + def user_audit_logs_download(self): + _ = self.request.translate + c = self.load_default_context() + c.user = self.db_user + + user_log = UserModel().get_user_log(c.user, filter_term=None) + + audit_log_data = {} + for entry in user_log: + audit_log_data[entry.user_log_id] = entry.get_dict() + + response = Response(json.dumps(audit_log_data, indent=4)) + response.content_disposition = str( + 'attachment; filename=%s' % 'user_{}_audit_logs.json'.format(c.user.user_id)) + response.content_type = 'application/json' + + return response + @LoginRequired() @HasPermissionAllDecorator('hg.admin') @view_config( diff --git a/rhodecode/public/js/rhodecode/routes.js b/rhodecode/public/js/rhodecode/routes.js index d72ad5e5..c57305fb 100644 --- a/rhodecode/public/js/rhodecode/routes.js +++ b/rhodecode/public/js/rhodecode/routes.js @@ -121,6 +121,7 @@ function registerRCRoutes() { pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']); pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']); pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']); + pyroutes.register('edit_user_audit_logs_download', '/_admin/users/%(user_id)s/edit/audit/download', ['user_id']); pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']); pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']); pyroutes.register('user_groups', '/_admin/user_groups', []); diff --git a/rhodecode/templates/admin/users/user_edit_audit.mako b/rhodecode/templates/admin/users/user_edit_audit.mako index b08d40ad..5fe0e011 100644 --- a/rhodecode/templates/admin/users/user_edit_audit.mako +++ b/rhodecode/templates/admin/users/user_edit_audit.mako @@ -7,6 +7,7 @@

${_('User Audit Logs')} - ${_ungettext('%s entry', '%s entries', c.audit_logs.item_count) % (c.audit_logs.item_count)}

+ ${_('Download as JSON')}