##// END OF EJS Templates
fixed import
fixed import

File last commit:

r5608:6d33e504 default
r5659:822bcfab default
Show More
audit_logger.py
303 lines | 9.7 KiB | text/x-python | PythonLexer
core: updated copyright to 2024
r5608 # Copyright (C) 2017-2024 RhodeCode GmbH
audit-logs: introducing new audit logger for actions....
r1694 #
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
import logging
import datetime
audit-logger: use raw JSON with empty data to control unicode decode warnings....
r2184 from rhodecode.lib.jsonalchemy import JsonRaw
audit-logs: introducing new audit logger for actions....
r1694 from rhodecode.model import meta
audit-logs: add push/pull actions to audit logs.
r1736 from rhodecode.model.db import User, UserLog, Repository
core: multiple fixes to unicode vs str usage...
r5065 from rhodecode.lib.str_utils import safe_str
audit-logs: introducing new audit logger for actions....
r1694
log = logging.getLogger(__name__)
audit-logs: added audit-logs on user actions....
r1801 # action as key, and expected action_data as value
audit-logs: implemented pull request and comment events.
r1807 ACTIONS_V1 = {
audit-logs: fill in some default values for the expected action data.
r1802 'user.login.success': {'user_agent': ''},
'user.login.failure': {'user_agent': ''},
'user.logout': {'user_agent': ''},
audit-logs: added user.register audit log entry.
r2384 'user.register': {},
audit-logs: use new v2 api on login/logout/password reset views.
r1697 'user.password.reset_request': {},
audit-logs: fill in some default values for the expected action data.
r1802 'user.push': {'user_agent': '', 'commit_ids': []},
'user.pull': {'user_agent': ''},
audit-logs: use new v2 api on login/logout/password reset views.
r1697
audit-logs: added audit-logs on user actions....
r1801 'user.create': {'data': {}},
'user.delete': {'old_data': {}},
'user.edit': {'old_data': {}},
'user.edit.permissions': {},
audit-logs: updated action data attrbiutes.
r1823 'user.edit.ip.add': {'ip': {}, 'user': {}},
'user.edit.ip.delete': {'ip': {}, 'user': {}},
'user.edit.token.add': {'token': {}, 'user': {}},
'user.edit.token.delete': {'token': {}, 'user': {}},
'user.edit.email.add': {'email': ''},
'user.edit.email.delete': {'email': ''},
users: added SSH key management for user admin pages
r1993 'user.edit.ssh_key.add': {'token': {}, 'user': {}},
'user.edit.ssh_key.delete': {'token': {}, 'user': {}},
audit-logs: added audit-logs on user actions....
r1801 'user.edit.password_reset.enabled': {},
'user.edit.password_reset.disabled': {},
audit-logs: added audit logs on user groups admin page.
r1805 'user_group.create': {'data': {}},
'user_group.delete': {'old_data': {}},
'user_group.edit': {'old_data': {}},
'user_group.edit.permissions': {},
audit-logs: implemented full audit logs across application....
r1829 'user_group.edit.member.add': {'user': {}},
'user_group.edit.member.delete': {'user': {}},
audit-logs: added audit logs on user groups admin page.
r1805
audit-logs: fill in some default values for the expected action data.
r1802 'repo.create': {'data': {}},
audit-logs: moved async tasks from old deprecated action_logger.
r1803 'repo.fork': {'data': {}},
audit-logs: fill in some default values for the expected action data.
r1802 'repo.edit': {'old_data': {}},
repo-permissions: moved permissions into pyramid....
r1734 'repo.edit.permissions': {},
audit-logs: added entries for branch permissions
r3025 'repo.edit.permissions.branch': {},
repositories: added option to archive repositories instead of deleting them....
r3090 'repo.archive': {'old_data': {}},
audit-logs: fill in some default values for the expected action data.
r1802 'repo.delete': {'old_data': {}},
audit-logs: added entries for branch permissions
r3025
audit-logs: updated action data attrbiutes.
r1823 'repo.archive.download': {'user_agent': '', 'archive_name': '',
'archive_spec': '', 'archive_cached': ''},
audit-logs: added entries for branch permissions
r3025
'repo.permissions.branch_rule.create': {},
'repo.permissions.branch_rule.edit': {},
'repo.permissions.branch_rule.delete': {},
audit-logs: implemented pull request and comment events.
r1807 'repo.pull_request.create': '',
'repo.pull_request.edit': '',
'repo.pull_request.delete': '',
'repo.pull_request.close': '',
'repo.pull_request.merge': '',
'repo.pull_request.vote': '',
'repo.pull_request.comment.create': '',
comments: edit functionality added
r4401 'repo.pull_request.comment.edit': '',
audit-logs: implemented pull request and comment events.
r1807 'repo.pull_request.comment.delete': '',
'repo.pull_request.reviewer.add': '',
'repo.pull_request.reviewer.delete': '',
reviewers: added observers as another way to define reviewers....
r4500 'repo.pull_request.observer.add': '',
'repo.pull_request.observer.delete': '',
audit-logs: added entries for branch permissions
r3025 'repo.commit.strip': {'commit_id': ''},
audit-logs: implemented full audit logs across application....
r1829 'repo.commit.comment.create': {'data': {}},
'repo.commit.comment.delete': {'data': {}},
comments: edit functionality added
r4401 'repo.commit.comment.edit': {'data': {}},
audit-logs: implemented pull request and comment events.
r1807 'repo.commit.vote': '',
artifacts: added reading of metadata and define basic audit-log entries for add/delete artifact
r3679 'repo.artifact.add': '',
'repo.artifact.delete': '',
audit-logs: fill in some default values for the expected action data.
r1802 'repo_group.create': {'data': {}},
'repo_group.edit': {'old_data': {}},
audit-logs: added action logs for repository groups.
r1799 'repo_group.edit.permissions': {},
audit-logs: fill in some default values for the expected action data.
r1802 'repo_group.delete': {'old_data': {}},
audit-logs: introducing new audit logger for actions....
r1694 }
audit-logs: added entries for branch permissions
r3025
audit-logs: implemented pull request and comment events.
r1807 ACTIONS = ACTIONS_V1
audit-logs: introducing new audit logger for actions....
r1694
audit-logs: implement enum sources that should be re-used.
r1798 SOURCE_WEB = 'source_web'
SOURCE_API = 'source_api'
audit-logs: introducing new audit logger for actions....
r1694
class UserWrap(object):
"""
Fake object used to imitate AuthUser
"""
def __init__(self, user_id=None, username=None, ip_addr=None):
self.user_id = user_id
self.username = username
self.ip_addr = ip_addr
audit-logs: add push/pull actions to audit logs.
r1736 class RepoWrap(object):
"""
Fake object used to imitate RepoObject that audit logger requires
"""
def __init__(self, repo_id=None, repo_name=None):
self.repo_id = repo_id
self.repo_name = repo_name
audit-logs: introducing new audit logger for actions....
r1694 def _store_log(action_name, action_data, user_id, username, user_data,
ip_address, repository_id, repository_name):
user_log = UserLog()
user_log.version = UserLog.VERSION_2
user_log.action = action_name
python3: fixed various code issues...
r4973 user_log.action_data = action_data or JsonRaw('{}')
audit-logs: introducing new audit logger for actions....
r1694
user_log.user_ip = ip_address
user_log.user_id = user_id
user_log.username = username
python3: fixed various code issues...
r4973 user_log.user_data = user_data or JsonRaw('{}')
audit-logs: introducing new audit logger for actions....
r1694
user_log.repository_id = repository_id
user_log.repository_name = repository_name
user_log.action_date = datetime.datetime.now()
return user_log
audit-logger: added convinience wrappers to store web or api action.
r1800 def store_web(*args, **kwargs):
audit-logger: use copy of params we later modify to prevent from modification by the store function of parameters that we only use for reading.
r4195 action_data = {}
org_action_data = kwargs.pop('action_data', {})
action_data.update(org_action_data)
action_data['source'] = SOURCE_WEB
kwargs['action_data'] = action_data
audit-logger: added convinience wrappers to store web or api action.
r1800 return store(*args, **kwargs)
def store_api(*args, **kwargs):
audit-logger: use copy of params we later modify to prevent from modification by the store function of parameters that we only use for reading.
r4195 action_data = {}
org_action_data = kwargs.pop('action_data', {})
action_data.update(org_action_data)
action_data['source'] = SOURCE_API
kwargs['action_data'] = action_data
audit-logger: added convinience wrappers to store web or api action.
r1800 return store(*args, **kwargs)
audit-logs: added audit-logs on user actions....
r1801 def store(action, user, action_data=None, user_data=None, ip_addr=None,
repo=None, sa_session=None, commit=False):
audit-logs: introducing new audit logger for actions....
r1694 """
audit-logger: added convinience wrappers to store web or api action.
r1800 Audit logger for various actions made by users, typically this
results in a call such::
audit-logs: introducing new audit logger for actions....
r1694
from rhodecode.lib import audit_logger
audit-logger: added entry for archive download.
r1743 audit_logger.store(
audit-logs: implemented full audit logs across application....
r1829 'repo.edit', user=self._rhodecode_user)
audit-logger: added entry for archive download.
r1743 audit_logger.store(
audit-logs: implemented full audit logs across application....
r1829 'repo.delete', action_data={'data': repo_data},
audit-logger: added entry for archive download.
r1743 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'))
# repo action
audit_logger.store(
audit-logs: implemented full audit logs across application....
r1829 'repo.delete',
audit-logger: added entry for archive download.
r1743 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'),
repo=audit_logger.RepoWrap(repo_name='some-repo'))
# repo action, when we know and have the repository object already
audit_logger.store(
audit-logs: implemented full audit logs across application....
r1829 'repo.delete', action_data={'source': audit_logger.SOURCE_WEB, },
audit-logs: implement enum sources that should be re-used.
r1798 user=self._rhodecode_user,
audit-logger: added entry for archive download.
r1743 repo=repo_object)
audit-logs: introducing new audit logger for actions....
r1694
audit-logger: added convinience wrappers to store web or api action.
r1800 # alternative wrapper to the above
audit_logger.store_web(
audit-logs: implemented full audit logs across application....
r1829 'repo.delete', action_data={},
audit-logger: added convinience wrappers to store web or api action.
r1800 user=self._rhodecode_user,
repo=repo_object)
audit-logs: introducing new audit logger for actions....
r1694 # without an user ?
audit-logger: added entry for archive download.
r1743 audit_logger.store(
audit-logs: implemented full audit logs across application....
r1829 'user.login.failure',
audit-logger: added entry for archive download.
r1743 user=audit_logger.UserWrap(
username=self.request.params.get('username'),
ip_addr=self.request.remote_addr))
audit-logs: introducing new audit logger for actions....
r1694 """
from rhodecode.lib.auth import AuthUser
audit-logs: fill in some default values for the expected action data.
r1802 action_spec = ACTIONS.get(action, None)
if action_spec is None:
modernize: updates for python3
r5095 raise ValueError(f'Action `{action}` is not supported')
audit-logs: introducing new audit logger for actions....
r1694
if not sa_session:
sa_session = meta.Session()
try:
username = getattr(user, 'username', None)
if not username:
pass
user_id = getattr(user, 'user_id', None)
if not user_id:
# maybe we have username ? Try to figure user_id from username
if username:
user_id = getattr(
User.get_by_username(username), 'user_id', None)
ip_addr = ip_addr or getattr(user, 'ip_addr', None)
if not ip_addr:
pass
if not user_data:
# try to get this from the auth user
if isinstance(user, AuthUser):
audit-logger: store some user details on we have proper object of auth-user.
r1701 user_data = {
'username': user.username,
'email': user.email,
}
audit-logs: introducing new audit logger for actions....
r1694
audit-logs: add push/pull actions to audit logs.
r1736 repository_name = getattr(repo, 'repo_name', None)
audit-logs: introducing new audit logger for actions....
r1694 repository_id = getattr(repo, 'repo_id', None)
audit-logs: add push/pull actions to audit logs.
r1736 if not repository_id:
# maybe we have repo_name ? Try to figure repo_id from repo_name
if repository_name:
repository_id = getattr(
Repository.get_by_repo_name(repository_name), 'repo_id', None)
audit-logs: introducing new audit logger for actions....
r1694
core: multiple fixes to unicode vs str usage...
r5065 action_name = safe_str(action)
ip_address = safe_str(ip_addr)
audit-logs: allow showing individual entries for audit log.
r2110
security: update lastactivity when on audit logs....
r2930 with sa_session.no_autoflush:
audit-logs: allow showing individual entries for audit log.
r2110
security: update lastactivity when on audit logs....
r2930 user_log = _store_log(
action_name=action_name,
action_data=action_data or {},
user_id=user_id,
username=username,
user_data=user_data or {},
ip_address=ip_address,
repository_id=repository_id,
repository_name=repository_name
)
sa_session.add(user_log)
pull-requests: optimize db transaction logic....
r4712 if commit:
sa_session.commit()
entry_id = user_log.entry_id or ''
update_user_last_activity(sa_session, user_id)
security: update lastactivity when on audit logs....
r2930
if commit:
sa_session.commit()
audit-logs: introducing new audit logger for actions....
r1694
audit-logs: allow showing individual entries for audit log.
r2110 log.info('AUDIT[%s]: Logging action: `%s` by user:id:%s[%s] ip:%s',
logging: expose extra metadata to various important logs for loki
r4816 entry_id, action_name, user_id, username, ip_address,
extra={"entry_id": entry_id, "action": action_name,
"user_id": user_id, "ip": ip_address})
audit-logs: allow showing individual entries for audit log.
r2110
audit-logs: introducing new audit logger for actions....
r1694 except Exception:
log.exception('AUDIT: failed to store audit log')
security: update lastactivity when on audit logs....
r2930
def update_user_last_activity(sa_session, user_id):
_last_activity = datetime.datetime.now()
try:
sa_session.query(User).filter(User.user_id == user_id).update(
{"last_activity": _last_activity})
log.debug(
'updated user `%s` last activity to:%s', user_id, _last_activity)
except Exception:
pull-requests: optimize db transaction logic....
r4712 log.exception("Failed last activity update for user_id: %s", user_id)
sa_session.rollback()