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