##// END OF EJS Templates
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.
marcink -
r4195:4c53d118 stable
parent child Browse files
Show More
@@ -1,291 +1,293 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2017-2019 RhodeCode GmbH
3 # Copyright (C) 2017-2019 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.lib.jsonalchemy import JsonRaw
25 from rhodecode.model import meta
25 from rhodecode.model import meta
26 from rhodecode.model.db import User, UserLog, Repository
26 from rhodecode.model.db import User, UserLog, Repository
27
27
28
28
29 log = logging.getLogger(__name__)
29 log = logging.getLogger(__name__)
30
30
31 # action as key, and expected action_data as value
31 # action as key, and expected action_data as value
32 ACTIONS_V1 = {
32 ACTIONS_V1 = {
33 'user.login.success': {'user_agent': ''},
33 'user.login.success': {'user_agent': ''},
34 'user.login.failure': {'user_agent': ''},
34 'user.login.failure': {'user_agent': ''},
35 'user.logout': {'user_agent': ''},
35 'user.logout': {'user_agent': ''},
36 'user.register': {},
36 'user.register': {},
37 'user.password.reset_request': {},
37 'user.password.reset_request': {},
38 'user.push': {'user_agent': '', 'commit_ids': []},
38 'user.push': {'user_agent': '', 'commit_ids': []},
39 'user.pull': {'user_agent': ''},
39 'user.pull': {'user_agent': ''},
40
40
41 'user.create': {'data': {}},
41 'user.create': {'data': {}},
42 'user.delete': {'old_data': {}},
42 'user.delete': {'old_data': {}},
43 'user.edit': {'old_data': {}},
43 'user.edit': {'old_data': {}},
44 'user.edit.permissions': {},
44 'user.edit.permissions': {},
45 'user.edit.ip.add': {'ip': {}, 'user': {}},
45 'user.edit.ip.add': {'ip': {}, 'user': {}},
46 'user.edit.ip.delete': {'ip': {}, 'user': {}},
46 'user.edit.ip.delete': {'ip': {}, 'user': {}},
47 'user.edit.token.add': {'token': {}, 'user': {}},
47 'user.edit.token.add': {'token': {}, 'user': {}},
48 'user.edit.token.delete': {'token': {}, 'user': {}},
48 'user.edit.token.delete': {'token': {}, 'user': {}},
49 'user.edit.email.add': {'email': ''},
49 'user.edit.email.add': {'email': ''},
50 'user.edit.email.delete': {'email': ''},
50 'user.edit.email.delete': {'email': ''},
51 'user.edit.ssh_key.add': {'token': {}, 'user': {}},
51 'user.edit.ssh_key.add': {'token': {}, 'user': {}},
52 'user.edit.ssh_key.delete': {'token': {}, 'user': {}},
52 'user.edit.ssh_key.delete': {'token': {}, 'user': {}},
53 'user.edit.password_reset.enabled': {},
53 'user.edit.password_reset.enabled': {},
54 'user.edit.password_reset.disabled': {},
54 'user.edit.password_reset.disabled': {},
55
55
56 'user_group.create': {'data': {}},
56 'user_group.create': {'data': {}},
57 'user_group.delete': {'old_data': {}},
57 'user_group.delete': {'old_data': {}},
58 'user_group.edit': {'old_data': {}},
58 'user_group.edit': {'old_data': {}},
59 'user_group.edit.permissions': {},
59 'user_group.edit.permissions': {},
60 'user_group.edit.member.add': {'user': {}},
60 'user_group.edit.member.add': {'user': {}},
61 'user_group.edit.member.delete': {'user': {}},
61 'user_group.edit.member.delete': {'user': {}},
62
62
63 'repo.create': {'data': {}},
63 'repo.create': {'data': {}},
64 'repo.fork': {'data': {}},
64 'repo.fork': {'data': {}},
65 'repo.edit': {'old_data': {}},
65 'repo.edit': {'old_data': {}},
66 'repo.edit.permissions': {},
66 'repo.edit.permissions': {},
67 'repo.edit.permissions.branch': {},
67 'repo.edit.permissions.branch': {},
68 'repo.archive': {'old_data': {}},
68 'repo.archive': {'old_data': {}},
69 'repo.delete': {'old_data': {}},
69 'repo.delete': {'old_data': {}},
70
70
71 'repo.archive.download': {'user_agent': '', 'archive_name': '',
71 'repo.archive.download': {'user_agent': '', 'archive_name': '',
72 'archive_spec': '', 'archive_cached': ''},
72 'archive_spec': '', 'archive_cached': ''},
73
73
74 'repo.permissions.branch_rule.create': {},
74 'repo.permissions.branch_rule.create': {},
75 'repo.permissions.branch_rule.edit': {},
75 'repo.permissions.branch_rule.edit': {},
76 'repo.permissions.branch_rule.delete': {},
76 'repo.permissions.branch_rule.delete': {},
77
77
78 'repo.pull_request.create': '',
78 'repo.pull_request.create': '',
79 'repo.pull_request.edit': '',
79 'repo.pull_request.edit': '',
80 'repo.pull_request.delete': '',
80 'repo.pull_request.delete': '',
81 'repo.pull_request.close': '',
81 'repo.pull_request.close': '',
82 'repo.pull_request.merge': '',
82 'repo.pull_request.merge': '',
83 'repo.pull_request.vote': '',
83 'repo.pull_request.vote': '',
84 'repo.pull_request.comment.create': '',
84 'repo.pull_request.comment.create': '',
85 'repo.pull_request.comment.delete': '',
85 'repo.pull_request.comment.delete': '',
86
86
87 'repo.pull_request.reviewer.add': '',
87 'repo.pull_request.reviewer.add': '',
88 'repo.pull_request.reviewer.delete': '',
88 'repo.pull_request.reviewer.delete': '',
89
89
90 'repo.commit.strip': {'commit_id': ''},
90 'repo.commit.strip': {'commit_id': ''},
91 'repo.commit.comment.create': {'data': {}},
91 'repo.commit.comment.create': {'data': {}},
92 'repo.commit.comment.delete': {'data': {}},
92 'repo.commit.comment.delete': {'data': {}},
93 'repo.commit.vote': '',
93 'repo.commit.vote': '',
94
94
95 'repo.artifact.add': '',
95 'repo.artifact.add': '',
96 'repo.artifact.delete': '',
96 'repo.artifact.delete': '',
97
97
98 'repo_group.create': {'data': {}},
98 'repo_group.create': {'data': {}},
99 'repo_group.edit': {'old_data': {}},
99 'repo_group.edit': {'old_data': {}},
100 'repo_group.edit.permissions': {},
100 'repo_group.edit.permissions': {},
101 'repo_group.delete': {'old_data': {}},
101 'repo_group.delete': {'old_data': {}},
102 }
102 }
103
103
104 ACTIONS = ACTIONS_V1
104 ACTIONS = ACTIONS_V1
105
105
106 SOURCE_WEB = 'source_web'
106 SOURCE_WEB = 'source_web'
107 SOURCE_API = 'source_api'
107 SOURCE_API = 'source_api'
108
108
109
109
110 class UserWrap(object):
110 class UserWrap(object):
111 """
111 """
112 Fake object used to imitate AuthUser
112 Fake object used to imitate AuthUser
113 """
113 """
114
114
115 def __init__(self, user_id=None, username=None, ip_addr=None):
115 def __init__(self, user_id=None, username=None, ip_addr=None):
116 self.user_id = user_id
116 self.user_id = user_id
117 self.username = username
117 self.username = username
118 self.ip_addr = ip_addr
118 self.ip_addr = ip_addr
119
119
120
120
121 class RepoWrap(object):
121 class RepoWrap(object):
122 """
122 """
123 Fake object used to imitate RepoObject that audit logger requires
123 Fake object used to imitate RepoObject that audit logger requires
124 """
124 """
125
125
126 def __init__(self, repo_id=None, repo_name=None):
126 def __init__(self, repo_id=None, repo_name=None):
127 self.repo_id = repo_id
127 self.repo_id = repo_id
128 self.repo_name = repo_name
128 self.repo_name = repo_name
129
129
130
130
131 def _store_log(action_name, action_data, user_id, username, user_data,
131 def _store_log(action_name, action_data, user_id, username, user_data,
132 ip_address, repository_id, repository_name):
132 ip_address, repository_id, repository_name):
133 user_log = UserLog()
133 user_log = UserLog()
134 user_log.version = UserLog.VERSION_2
134 user_log.version = UserLog.VERSION_2
135
135
136 user_log.action = action_name
136 user_log.action = action_name
137 user_log.action_data = action_data or JsonRaw(u'{}')
137 user_log.action_data = action_data or JsonRaw(u'{}')
138
138
139 user_log.user_ip = ip_address
139 user_log.user_ip = ip_address
140
140
141 user_log.user_id = user_id
141 user_log.user_id = user_id
142 user_log.username = username
142 user_log.username = username
143 user_log.user_data = user_data or JsonRaw(u'{}')
143 user_log.user_data = user_data or JsonRaw(u'{}')
144
144
145 user_log.repository_id = repository_id
145 user_log.repository_id = repository_id
146 user_log.repository_name = repository_name
146 user_log.repository_name = repository_name
147
147
148 user_log.action_date = datetime.datetime.now()
148 user_log.action_date = datetime.datetime.now()
149
149
150 return user_log
150 return user_log
151
151
152
152
153 def store_web(*args, **kwargs):
153 def store_web(*args, **kwargs):
154 if 'action_data' not in kwargs:
154 action_data = {}
155 kwargs['action_data'] = {}
155 org_action_data = kwargs.pop('action_data', {})
156 kwargs['action_data'].update({
156 action_data.update(org_action_data)
157 'source': SOURCE_WEB
157 action_data['source'] = SOURCE_WEB
158 })
158 kwargs['action_data'] = action_data
159
159 return store(*args, **kwargs)
160 return store(*args, **kwargs)
160
161
161
162
162 def store_api(*args, **kwargs):
163 def store_api(*args, **kwargs):
163 if 'action_data' not in kwargs:
164 action_data = {}
164 kwargs['action_data'] = {}
165 org_action_data = kwargs.pop('action_data', {})
165 kwargs['action_data'].update({
166 action_data.update(org_action_data)
166 'source': SOURCE_API
167 action_data['source'] = SOURCE_API
167 })
168 kwargs['action_data'] = action_data
169
168 return store(*args, **kwargs)
170 return store(*args, **kwargs)
169
171
170
172
171 def store(action, user, action_data=None, user_data=None, ip_addr=None,
173 def store(action, user, action_data=None, user_data=None, ip_addr=None,
172 repo=None, sa_session=None, commit=False):
174 repo=None, sa_session=None, commit=False):
173 """
175 """
174 Audit logger for various actions made by users, typically this
176 Audit logger for various actions made by users, typically this
175 results in a call such::
177 results in a call such::
176
178
177 from rhodecode.lib import audit_logger
179 from rhodecode.lib import audit_logger
178
180
179 audit_logger.store(
181 audit_logger.store(
180 'repo.edit', user=self._rhodecode_user)
182 'repo.edit', user=self._rhodecode_user)
181 audit_logger.store(
183 audit_logger.store(
182 'repo.delete', action_data={'data': repo_data},
184 'repo.delete', action_data={'data': repo_data},
183 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'))
185 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'))
184
186
185 # repo action
187 # repo action
186 audit_logger.store(
188 audit_logger.store(
187 'repo.delete',
189 'repo.delete',
188 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'),
190 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'),
189 repo=audit_logger.RepoWrap(repo_name='some-repo'))
191 repo=audit_logger.RepoWrap(repo_name='some-repo'))
190
192
191 # repo action, when we know and have the repository object already
193 # repo action, when we know and have the repository object already
192 audit_logger.store(
194 audit_logger.store(
193 'repo.delete', action_data={'source': audit_logger.SOURCE_WEB, },
195 'repo.delete', action_data={'source': audit_logger.SOURCE_WEB, },
194 user=self._rhodecode_user,
196 user=self._rhodecode_user,
195 repo=repo_object)
197 repo=repo_object)
196
198
197 # alternative wrapper to the above
199 # alternative wrapper to the above
198 audit_logger.store_web(
200 audit_logger.store_web(
199 'repo.delete', action_data={},
201 'repo.delete', action_data={},
200 user=self._rhodecode_user,
202 user=self._rhodecode_user,
201 repo=repo_object)
203 repo=repo_object)
202
204
203 # without an user ?
205 # without an user ?
204 audit_logger.store(
206 audit_logger.store(
205 'user.login.failure',
207 'user.login.failure',
206 user=audit_logger.UserWrap(
208 user=audit_logger.UserWrap(
207 username=self.request.params.get('username'),
209 username=self.request.params.get('username'),
208 ip_addr=self.request.remote_addr))
210 ip_addr=self.request.remote_addr))
209
211
210 """
212 """
211 from rhodecode.lib.utils2 import safe_unicode
213 from rhodecode.lib.utils2 import safe_unicode
212 from rhodecode.lib.auth import AuthUser
214 from rhodecode.lib.auth import AuthUser
213
215
214 action_spec = ACTIONS.get(action, None)
216 action_spec = ACTIONS.get(action, None)
215 if action_spec is None:
217 if action_spec is None:
216 raise ValueError('Action `{}` is not supported'.format(action))
218 raise ValueError('Action `{}` is not supported'.format(action))
217
219
218 if not sa_session:
220 if not sa_session:
219 sa_session = meta.Session()
221 sa_session = meta.Session()
220
222
221 try:
223 try:
222 username = getattr(user, 'username', None)
224 username = getattr(user, 'username', None)
223 if not username:
225 if not username:
224 pass
226 pass
225
227
226 user_id = getattr(user, 'user_id', None)
228 user_id = getattr(user, 'user_id', None)
227 if not user_id:
229 if not user_id:
228 # maybe we have username ? Try to figure user_id from username
230 # maybe we have username ? Try to figure user_id from username
229 if username:
231 if username:
230 user_id = getattr(
232 user_id = getattr(
231 User.get_by_username(username), 'user_id', None)
233 User.get_by_username(username), 'user_id', None)
232
234
233 ip_addr = ip_addr or getattr(user, 'ip_addr', None)
235 ip_addr = ip_addr or getattr(user, 'ip_addr', None)
234 if not ip_addr:
236 if not ip_addr:
235 pass
237 pass
236
238
237 if not user_data:
239 if not user_data:
238 # try to get this from the auth user
240 # try to get this from the auth user
239 if isinstance(user, AuthUser):
241 if isinstance(user, AuthUser):
240 user_data = {
242 user_data = {
241 'username': user.username,
243 'username': user.username,
242 'email': user.email,
244 'email': user.email,
243 }
245 }
244
246
245 repository_name = getattr(repo, 'repo_name', None)
247 repository_name = getattr(repo, 'repo_name', None)
246 repository_id = getattr(repo, 'repo_id', None)
248 repository_id = getattr(repo, 'repo_id', None)
247 if not repository_id:
249 if not repository_id:
248 # maybe we have repo_name ? Try to figure repo_id from repo_name
250 # maybe we have repo_name ? Try to figure repo_id from repo_name
249 if repository_name:
251 if repository_name:
250 repository_id = getattr(
252 repository_id = getattr(
251 Repository.get_by_repo_name(repository_name), 'repo_id', None)
253 Repository.get_by_repo_name(repository_name), 'repo_id', None)
252
254
253 action_name = safe_unicode(action)
255 action_name = safe_unicode(action)
254 ip_address = safe_unicode(ip_addr)
256 ip_address = safe_unicode(ip_addr)
255
257
256 with sa_session.no_autoflush:
258 with sa_session.no_autoflush:
257 update_user_last_activity(sa_session, user_id)
259 update_user_last_activity(sa_session, user_id)
258
260
259 user_log = _store_log(
261 user_log = _store_log(
260 action_name=action_name,
262 action_name=action_name,
261 action_data=action_data or {},
263 action_data=action_data or {},
262 user_id=user_id,
264 user_id=user_id,
263 username=username,
265 username=username,
264 user_data=user_data or {},
266 user_data=user_data or {},
265 ip_address=ip_address,
267 ip_address=ip_address,
266 repository_id=repository_id,
268 repository_id=repository_id,
267 repository_name=repository_name
269 repository_name=repository_name
268 )
270 )
269
271
270 sa_session.add(user_log)
272 sa_session.add(user_log)
271
273
272 if commit:
274 if commit:
273 sa_session.commit()
275 sa_session.commit()
274
276
275 entry_id = user_log.entry_id or ''
277 entry_id = user_log.entry_id or ''
276 log.info('AUDIT[%s]: Logging action: `%s` by user:id:%s[%s] ip:%s',
278 log.info('AUDIT[%s]: Logging action: `%s` by user:id:%s[%s] ip:%s',
277 entry_id, action_name, user_id, username, ip_address)
279 entry_id, action_name, user_id, username, ip_address)
278
280
279 except Exception:
281 except Exception:
280 log.exception('AUDIT: failed to store audit log')
282 log.exception('AUDIT: failed to store audit log')
281
283
282
284
283 def update_user_last_activity(sa_session, user_id):
285 def update_user_last_activity(sa_session, user_id):
284 _last_activity = datetime.datetime.now()
286 _last_activity = datetime.datetime.now()
285 try:
287 try:
286 sa_session.query(User).filter(User.user_id == user_id).update(
288 sa_session.query(User).filter(User.user_id == user_id).update(
287 {"last_activity": _last_activity})
289 {"last_activity": _last_activity})
288 log.debug(
290 log.debug(
289 'updated user `%s` last activity to:%s', user_id, _last_activity)
291 'updated user `%s` last activity to:%s', user_id, _last_activity)
290 except Exception:
292 except Exception:
291 log.exception("Failed last activity update")
293 log.exception("Failed last activity update")
General Comments 0
You need to be logged in to leave comments. Login now