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