diff --git a/rhodecode/lib/audit_logger.py b/rhodecode/lib/audit_logger.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/audit_logger.py @@ -0,0 +1,138 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2017-2017 RhodeCode GmbH +# +# 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 . +# +# 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 + +from rhodecode.model import meta +from rhodecode.model.db import User, UserLog + + +log = logging.getLogger(__name__) + + +ACTIONS = { + 'repo.add': {}, + 'repo.edit': {}, +} + + +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 + + +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 + user_log.action_data = action_data + + user_log.user_ip = ip_address + + user_log.user_id = user_id + user_log.username = username + user_log.user_data = user_data + + user_log.repository_id = repository_id + user_log.repository_name = repository_name + + user_log.action_date = datetime.datetime.now() + + log.info('AUDIT: Logging action: `%s` by user:id:%s[%s] ip:%s', + action_name, user_id, username, ip_address) + + return user_log + + +def store( + action, user, action_data=None, user_data=None, ip_addr=None, + repo=None, sa_session=None, commit=False): + """ + Audit logger for various actions made by users, typically this results in a call such:: + + from rhodecode.lib import audit_logger + + audit_logger.store(action='repo.edit', user=self._rhodecode_user) + audit_logger.store(action='repo.delete', user=audit_logger.UserWrap(username='itried-to-login', ip_addr='8.8.8.8')) + + # without an user ? + audit_user = audit_logger.UserWrap( + username=self.request.params.get('username'), + ip_addr=self.request.remote_addr) + audit_logger.store(action='user.login.failure', user=audit_user) + """ + from rhodecode.lib.utils2 import safe_unicode + from rhodecode.lib.auth import AuthUser + + if action not in ACTIONS: + raise ValueError('Action `{}` not in valid actions'.format(action)) + + 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): + user_data = {} + + repository_id = getattr(repo, 'repo_id', None) + repository_name = getattr(repo, 'repo_name', None) + + user_log = _store_log( + action_name=safe_unicode(action), + action_data=action_data or {}, + user_id=user_id, + username=username, + user_data=user_data or {}, + ip_address=safe_unicode(ip_addr), + repository_id=repository_id, + repository_name=repository_name + ) + sa_session.add(user_log) + if commit: + sa_session.commit() + + except Exception: + log.exception('AUDIT: failed to store audit log') diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py --- a/rhodecode/model/db.py +++ b/rhodecode/model/db.py @@ -1098,6 +1098,10 @@ class UserLog(Base, BaseModel): {'extend_existing': True, 'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, ) + VERSION_1 = 'v1' + VERSION_2 = 'v2' + VERSIONS = [VERSION_1, VERSION_2] + user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) username = Column("username", String(255), nullable=True, unique=None, default=None) @@ -1107,7 +1111,7 @@ class UserLog(Base, BaseModel): action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None) action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None) - version = Column("version", String(255), nullable=True, default='v1') + version = Column("version", String(255), nullable=True, default=VERSION_1) user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384))))) action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))