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