##// END OF EJS Templates
audit-logger: use raw JSON with empty data to control unicode decode warnings....
marcink -
r2184:c4cc6e2a default
parent child Browse files
Show More
@@ -1,262 +1,263 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2017-2017 RhodeCode GmbH
3 # Copyright (C) 2017-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import datetime
22 import datetime
23
23
24 from rhodecode.lib.jsonalchemy import JsonRaw
24 from rhodecode.model import meta
25 from rhodecode.model import meta
25 from rhodecode.model.db import User, UserLog, Repository
26 from rhodecode.model.db import User, UserLog, Repository
26
27
27
28
28 log = logging.getLogger(__name__)
29 log = logging.getLogger(__name__)
29
30
30 # action as key, and expected action_data as value
31 # action as key, and expected action_data as value
31 ACTIONS_V1 = {
32 ACTIONS_V1 = {
32 'user.login.success': {'user_agent': ''},
33 'user.login.success': {'user_agent': ''},
33 'user.login.failure': {'user_agent': ''},
34 'user.login.failure': {'user_agent': ''},
34 'user.logout': {'user_agent': ''},
35 'user.logout': {'user_agent': ''},
35 'user.password.reset_request': {},
36 'user.password.reset_request': {},
36 'user.push': {'user_agent': '', 'commit_ids': []},
37 'user.push': {'user_agent': '', 'commit_ids': []},
37 'user.pull': {'user_agent': ''},
38 'user.pull': {'user_agent': ''},
38
39
39 'user.create': {'data': {}},
40 'user.create': {'data': {}},
40 'user.delete': {'old_data': {}},
41 'user.delete': {'old_data': {}},
41 'user.edit': {'old_data': {}},
42 'user.edit': {'old_data': {}},
42 'user.edit.permissions': {},
43 'user.edit.permissions': {},
43 'user.edit.ip.add': {'ip': {}, 'user': {}},
44 'user.edit.ip.add': {'ip': {}, 'user': {}},
44 'user.edit.ip.delete': {'ip': {}, 'user': {}},
45 'user.edit.ip.delete': {'ip': {}, 'user': {}},
45 'user.edit.token.add': {'token': {}, 'user': {}},
46 'user.edit.token.add': {'token': {}, 'user': {}},
46 'user.edit.token.delete': {'token': {}, 'user': {}},
47 'user.edit.token.delete': {'token': {}, 'user': {}},
47 'user.edit.email.add': {'email': ''},
48 'user.edit.email.add': {'email': ''},
48 'user.edit.email.delete': {'email': ''},
49 'user.edit.email.delete': {'email': ''},
49 'user.edit.ssh_key.add': {'token': {}, 'user': {}},
50 'user.edit.ssh_key.add': {'token': {}, 'user': {}},
50 'user.edit.ssh_key.delete': {'token': {}, 'user': {}},
51 'user.edit.ssh_key.delete': {'token': {}, 'user': {}},
51 'user.edit.password_reset.enabled': {},
52 'user.edit.password_reset.enabled': {},
52 'user.edit.password_reset.disabled': {},
53 'user.edit.password_reset.disabled': {},
53
54
54 'user_group.create': {'data': {}},
55 'user_group.create': {'data': {}},
55 'user_group.delete': {'old_data': {}},
56 'user_group.delete': {'old_data': {}},
56 'user_group.edit': {'old_data': {}},
57 'user_group.edit': {'old_data': {}},
57 'user_group.edit.permissions': {},
58 'user_group.edit.permissions': {},
58 'user_group.edit.member.add': {'user': {}},
59 'user_group.edit.member.add': {'user': {}},
59 'user_group.edit.member.delete': {'user': {}},
60 'user_group.edit.member.delete': {'user': {}},
60
61
61 'repo.create': {'data': {}},
62 'repo.create': {'data': {}},
62 'repo.fork': {'data': {}},
63 'repo.fork': {'data': {}},
63 'repo.edit': {'old_data': {}},
64 'repo.edit': {'old_data': {}},
64 'repo.edit.permissions': {},
65 'repo.edit.permissions': {},
65 'repo.delete': {'old_data': {}},
66 'repo.delete': {'old_data': {}},
66 'repo.commit.strip': {'commit_id': ''},
67 'repo.commit.strip': {'commit_id': ''},
67 'repo.archive.download': {'user_agent': '', 'archive_name': '',
68 'repo.archive.download': {'user_agent': '', 'archive_name': '',
68 'archive_spec': '', 'archive_cached': ''},
69 'archive_spec': '', 'archive_cached': ''},
69 'repo.pull_request.create': '',
70 'repo.pull_request.create': '',
70 'repo.pull_request.edit': '',
71 'repo.pull_request.edit': '',
71 'repo.pull_request.delete': '',
72 'repo.pull_request.delete': '',
72 'repo.pull_request.close': '',
73 'repo.pull_request.close': '',
73 'repo.pull_request.merge': '',
74 'repo.pull_request.merge': '',
74 'repo.pull_request.vote': '',
75 'repo.pull_request.vote': '',
75 'repo.pull_request.comment.create': '',
76 'repo.pull_request.comment.create': '',
76 'repo.pull_request.comment.delete': '',
77 'repo.pull_request.comment.delete': '',
77
78
78 'repo.pull_request.reviewer.add': '',
79 'repo.pull_request.reviewer.add': '',
79 'repo.pull_request.reviewer.delete': '',
80 'repo.pull_request.reviewer.delete': '',
80
81
81 'repo.commit.comment.create': {'data': {}},
82 'repo.commit.comment.create': {'data': {}},
82 'repo.commit.comment.delete': {'data': {}},
83 'repo.commit.comment.delete': {'data': {}},
83 'repo.commit.vote': '',
84 'repo.commit.vote': '',
84
85
85 'repo_group.create': {'data': {}},
86 'repo_group.create': {'data': {}},
86 'repo_group.edit': {'old_data': {}},
87 'repo_group.edit': {'old_data': {}},
87 'repo_group.edit.permissions': {},
88 'repo_group.edit.permissions': {},
88 'repo_group.delete': {'old_data': {}},
89 'repo_group.delete': {'old_data': {}},
89 }
90 }
90 ACTIONS = ACTIONS_V1
91 ACTIONS = ACTIONS_V1
91
92
92 SOURCE_WEB = 'source_web'
93 SOURCE_WEB = 'source_web'
93 SOURCE_API = 'source_api'
94 SOURCE_API = 'source_api'
94
95
95
96
96 class UserWrap(object):
97 class UserWrap(object):
97 """
98 """
98 Fake object used to imitate AuthUser
99 Fake object used to imitate AuthUser
99 """
100 """
100
101
101 def __init__(self, user_id=None, username=None, ip_addr=None):
102 def __init__(self, user_id=None, username=None, ip_addr=None):
102 self.user_id = user_id
103 self.user_id = user_id
103 self.username = username
104 self.username = username
104 self.ip_addr = ip_addr
105 self.ip_addr = ip_addr
105
106
106
107
107 class RepoWrap(object):
108 class RepoWrap(object):
108 """
109 """
109 Fake object used to imitate RepoObject that audit logger requires
110 Fake object used to imitate RepoObject that audit logger requires
110 """
111 """
111
112
112 def __init__(self, repo_id=None, repo_name=None):
113 def __init__(self, repo_id=None, repo_name=None):
113 self.repo_id = repo_id
114 self.repo_id = repo_id
114 self.repo_name = repo_name
115 self.repo_name = repo_name
115
116
116
117
117 def _store_log(action_name, action_data, user_id, username, user_data,
118 def _store_log(action_name, action_data, user_id, username, user_data,
118 ip_address, repository_id, repository_name):
119 ip_address, repository_id, repository_name):
119 user_log = UserLog()
120 user_log = UserLog()
120 user_log.version = UserLog.VERSION_2
121 user_log.version = UserLog.VERSION_2
121
122
122 user_log.action = action_name
123 user_log.action = action_name
123 user_log.action_data = action_data
124 user_log.action_data = action_data or JsonRaw(u'{}')
124
125
125 user_log.user_ip = ip_address
126 user_log.user_ip = ip_address
126
127
127 user_log.user_id = user_id
128 user_log.user_id = user_id
128 user_log.username = username
129 user_log.username = username
129 user_log.user_data = user_data
130 user_log.user_data = user_data or JsonRaw(u'{}')
130
131
131 user_log.repository_id = repository_id
132 user_log.repository_id = repository_id
132 user_log.repository_name = repository_name
133 user_log.repository_name = repository_name
133
134
134 user_log.action_date = datetime.datetime.now()
135 user_log.action_date = datetime.datetime.now()
135
136
136 return user_log
137 return user_log
137
138
138
139
139 def store_web(*args, **kwargs):
140 def store_web(*args, **kwargs):
140 if 'action_data' not in kwargs:
141 if 'action_data' not in kwargs:
141 kwargs['action_data'] = {}
142 kwargs['action_data'] = {}
142 kwargs['action_data'].update({
143 kwargs['action_data'].update({
143 'source': SOURCE_WEB
144 'source': SOURCE_WEB
144 })
145 })
145 return store(*args, **kwargs)
146 return store(*args, **kwargs)
146
147
147
148
148 def store_api(*args, **kwargs):
149 def store_api(*args, **kwargs):
149 if 'action_data' not in kwargs:
150 if 'action_data' not in kwargs:
150 kwargs['action_data'] = {}
151 kwargs['action_data'] = {}
151 kwargs['action_data'].update({
152 kwargs['action_data'].update({
152 'source': SOURCE_API
153 'source': SOURCE_API
153 })
154 })
154 return store(*args, **kwargs)
155 return store(*args, **kwargs)
155
156
156
157
157 def store(action, user, action_data=None, user_data=None, ip_addr=None,
158 def store(action, user, action_data=None, user_data=None, ip_addr=None,
158 repo=None, sa_session=None, commit=False):
159 repo=None, sa_session=None, commit=False):
159 """
160 """
160 Audit logger for various actions made by users, typically this
161 Audit logger for various actions made by users, typically this
161 results in a call such::
162 results in a call such::
162
163
163 from rhodecode.lib import audit_logger
164 from rhodecode.lib import audit_logger
164
165
165 audit_logger.store(
166 audit_logger.store(
166 'repo.edit', user=self._rhodecode_user)
167 'repo.edit', user=self._rhodecode_user)
167 audit_logger.store(
168 audit_logger.store(
168 'repo.delete', action_data={'data': repo_data},
169 'repo.delete', action_data={'data': repo_data},
169 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'))
170 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'))
170
171
171 # repo action
172 # repo action
172 audit_logger.store(
173 audit_logger.store(
173 'repo.delete',
174 'repo.delete',
174 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'),
175 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'),
175 repo=audit_logger.RepoWrap(repo_name='some-repo'))
176 repo=audit_logger.RepoWrap(repo_name='some-repo'))
176
177
177 # repo action, when we know and have the repository object already
178 # repo action, when we know and have the repository object already
178 audit_logger.store(
179 audit_logger.store(
179 'repo.delete', action_data={'source': audit_logger.SOURCE_WEB, },
180 'repo.delete', action_data={'source': audit_logger.SOURCE_WEB, },
180 user=self._rhodecode_user,
181 user=self._rhodecode_user,
181 repo=repo_object)
182 repo=repo_object)
182
183
183 # alternative wrapper to the above
184 # alternative wrapper to the above
184 audit_logger.store_web(
185 audit_logger.store_web(
185 'repo.delete', action_data={},
186 'repo.delete', action_data={},
186 user=self._rhodecode_user,
187 user=self._rhodecode_user,
187 repo=repo_object)
188 repo=repo_object)
188
189
189 # without an user ?
190 # without an user ?
190 audit_logger.store(
191 audit_logger.store(
191 'user.login.failure',
192 'user.login.failure',
192 user=audit_logger.UserWrap(
193 user=audit_logger.UserWrap(
193 username=self.request.params.get('username'),
194 username=self.request.params.get('username'),
194 ip_addr=self.request.remote_addr))
195 ip_addr=self.request.remote_addr))
195
196
196 """
197 """
197 from rhodecode.lib.utils2 import safe_unicode
198 from rhodecode.lib.utils2 import safe_unicode
198 from rhodecode.lib.auth import AuthUser
199 from rhodecode.lib.auth import AuthUser
199
200
200 action_spec = ACTIONS.get(action, None)
201 action_spec = ACTIONS.get(action, None)
201 if action_spec is None:
202 if action_spec is None:
202 raise ValueError('Action `{}` is not supported'.format(action))
203 raise ValueError('Action `{}` is not supported'.format(action))
203
204
204 if not sa_session:
205 if not sa_session:
205 sa_session = meta.Session()
206 sa_session = meta.Session()
206
207
207 try:
208 try:
208 username = getattr(user, 'username', None)
209 username = getattr(user, 'username', None)
209 if not username:
210 if not username:
210 pass
211 pass
211
212
212 user_id = getattr(user, 'user_id', None)
213 user_id = getattr(user, 'user_id', None)
213 if not user_id:
214 if not user_id:
214 # maybe we have username ? Try to figure user_id from username
215 # maybe we have username ? Try to figure user_id from username
215 if username:
216 if username:
216 user_id = getattr(
217 user_id = getattr(
217 User.get_by_username(username), 'user_id', None)
218 User.get_by_username(username), 'user_id', None)
218
219
219 ip_addr = ip_addr or getattr(user, 'ip_addr', None)
220 ip_addr = ip_addr or getattr(user, 'ip_addr', None)
220 if not ip_addr:
221 if not ip_addr:
221 pass
222 pass
222
223
223 if not user_data:
224 if not user_data:
224 # try to get this from the auth user
225 # try to get this from the auth user
225 if isinstance(user, AuthUser):
226 if isinstance(user, AuthUser):
226 user_data = {
227 user_data = {
227 'username': user.username,
228 'username': user.username,
228 'email': user.email,
229 'email': user.email,
229 }
230 }
230
231
231 repository_name = getattr(repo, 'repo_name', None)
232 repository_name = getattr(repo, 'repo_name', None)
232 repository_id = getattr(repo, 'repo_id', None)
233 repository_id = getattr(repo, 'repo_id', None)
233 if not repository_id:
234 if not repository_id:
234 # maybe we have repo_name ? Try to figure repo_id from repo_name
235 # maybe we have repo_name ? Try to figure repo_id from repo_name
235 if repository_name:
236 if repository_name:
236 repository_id = getattr(
237 repository_id = getattr(
237 Repository.get_by_repo_name(repository_name), 'repo_id', None)
238 Repository.get_by_repo_name(repository_name), 'repo_id', None)
238
239
239 action_name = safe_unicode(action)
240 action_name = safe_unicode(action)
240 ip_address = safe_unicode(ip_addr)
241 ip_address = safe_unicode(ip_addr)
241
242
242 user_log = _store_log(
243 user_log = _store_log(
243 action_name=action_name,
244 action_name=action_name,
244 action_data=action_data or {},
245 action_data=action_data or {},
245 user_id=user_id,
246 user_id=user_id,
246 username=username,
247 username=username,
247 user_data=user_data or {},
248 user_data=user_data or {},
248 ip_address=ip_address,
249 ip_address=ip_address,
249 repository_id=repository_id,
250 repository_id=repository_id,
250 repository_name=repository_name
251 repository_name=repository_name
251 )
252 )
252
253
253 sa_session.add(user_log)
254 sa_session.add(user_log)
254 if commit:
255 if commit:
255 sa_session.commit()
256 sa_session.commit()
256
257
257 entry_id = user_log.entry_id or ''
258 entry_id = user_log.entry_id or ''
258 log.info('AUDIT[%s]: Logging action: `%s` by user:id:%s[%s] ip:%s',
259 log.info('AUDIT[%s]: Logging action: `%s` by user:id:%s[%s] ip:%s',
259 entry_id, action_name, user_id, username, ip_address)
260 entry_id, action_name, user_id, username, ip_address)
260
261
261 except Exception:
262 except Exception:
262 log.exception('AUDIT: failed to store audit log')
263 log.exception('AUDIT: failed to store audit log')
General Comments 0
You need to be logged in to leave comments. Login now