##// END OF EJS Templates
audit-logs: store repository name on pull action.
marcink -
r1737:c2645bad default
parent child Browse files
Show More
@@ -1,420 +1,421 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2013-2017 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
22 22 """
23 23 Set of hooks run by RhodeCode Enterprise
24 24 """
25 25
26 26 import os
27 27 import collections
28 28 import logging
29 29
30 30 import rhodecode
31 31 from rhodecode import events
32 32 from rhodecode.lib import helpers as h
33 33 from rhodecode.lib import audit_logger
34 34 from rhodecode.lib.utils import action_logger
35 35 from rhodecode.lib.utils2 import safe_str
36 36 from rhodecode.lib.exceptions import HTTPLockedRC, UserCreationError
37 37 from rhodecode.model.db import Repository, User
38 38
39 39 log = logging.getLogger(__name__)
40 40
41 41
42 42 HookResponse = collections.namedtuple('HookResponse', ('status', 'output'))
43 43
44 44
45 45 def is_shadow_repo(extras):
46 46 """
47 47 Returns ``True`` if this is an action executed against a shadow repository.
48 48 """
49 49 return extras['is_shadow_repo']
50 50
51 51
52 52 def _get_scm_size(alias, root_path):
53 53
54 54 if not alias.startswith('.'):
55 55 alias += '.'
56 56
57 57 size_scm, size_root = 0, 0
58 58 for path, unused_dirs, files in os.walk(safe_str(root_path)):
59 59 if path.find(alias) != -1:
60 60 for f in files:
61 61 try:
62 62 size_scm += os.path.getsize(os.path.join(path, f))
63 63 except OSError:
64 64 pass
65 65 else:
66 66 for f in files:
67 67 try:
68 68 size_root += os.path.getsize(os.path.join(path, f))
69 69 except OSError:
70 70 pass
71 71
72 72 size_scm_f = h.format_byte_size_binary(size_scm)
73 73 size_root_f = h.format_byte_size_binary(size_root)
74 74 size_total_f = h.format_byte_size_binary(size_root + size_scm)
75 75
76 76 return size_scm_f, size_root_f, size_total_f
77 77
78 78
79 79 # actual hooks called by Mercurial internally, and GIT by our Python Hooks
80 80 def repo_size(extras):
81 81 """Present size of repository after push."""
82 82 repo = Repository.get_by_repo_name(extras.repository)
83 83 vcs_part = safe_str(u'.%s' % repo.repo_type)
84 84 size_vcs, size_root, size_total = _get_scm_size(vcs_part,
85 85 repo.repo_full_path)
86 86 msg = ('Repository `%s` size summary %s:%s repo:%s total:%s\n'
87 87 % (repo.repo_name, vcs_part, size_vcs, size_root, size_total))
88 88 return HookResponse(0, msg)
89 89
90 90
91 91 def pre_push(extras):
92 92 """
93 93 Hook executed before pushing code.
94 94
95 95 It bans pushing when the repository is locked.
96 96 """
97 97
98 98 usr = User.get_by_username(extras.username)
99 99 output = ''
100 100 if extras.locked_by[0] and usr.user_id != int(extras.locked_by[0]):
101 101 locked_by = User.get(extras.locked_by[0]).username
102 102 reason = extras.locked_by[2]
103 103 # this exception is interpreted in git/hg middlewares and based
104 104 # on that proper return code is server to client
105 105 _http_ret = HTTPLockedRC(
106 106 _locked_by_explanation(extras.repository, locked_by, reason))
107 107 if str(_http_ret.code).startswith('2'):
108 108 # 2xx Codes don't raise exceptions
109 109 output = _http_ret.title
110 110 else:
111 111 raise _http_ret
112 112
113 113 # Propagate to external components. This is done after checking the
114 114 # lock, for consistent behavior.
115 115 if not is_shadow_repo(extras):
116 116 pre_push_extension(repo_store_path=Repository.base_path(), **extras)
117 117 events.trigger(events.RepoPrePushEvent(
118 118 repo_name=extras.repository, extras=extras))
119 119
120 120 return HookResponse(0, output)
121 121
122 122
123 123 def pre_pull(extras):
124 124 """
125 125 Hook executed before pulling the code.
126 126
127 127 It bans pulling when the repository is locked.
128 128 """
129 129
130 130 output = ''
131 131 if extras.locked_by[0]:
132 132 locked_by = User.get(extras.locked_by[0]).username
133 133 reason = extras.locked_by[2]
134 134 # this exception is interpreted in git/hg middlewares and based
135 135 # on that proper return code is server to client
136 136 _http_ret = HTTPLockedRC(
137 137 _locked_by_explanation(extras.repository, locked_by, reason))
138 138 if str(_http_ret.code).startswith('2'):
139 139 # 2xx Codes don't raise exceptions
140 140 output = _http_ret.title
141 141 else:
142 142 raise _http_ret
143 143
144 144 # Propagate to external components. This is done after checking the
145 145 # lock, for consistent behavior.
146 146 if not is_shadow_repo(extras):
147 147 pre_pull_extension(**extras)
148 148 events.trigger(events.RepoPrePullEvent(
149 149 repo_name=extras.repository, extras=extras))
150 150
151 151 return HookResponse(0, output)
152 152
153 153
154 154 def post_pull(extras):
155 155 """Hook executed after client pulls the code."""
156 156 user = User.get_by_username(extras.username)
157 157 action = 'pull'
158 158 action_logger(user, action, extras.repository, extras.ip, commit=True)
159 159
160 160 audit_user = audit_logger.UserWrap(
161 161 username=extras.username,
162 162 ip_addr=extras.ip)
163 repo = audit_logger.RepoWrap(repo_name=extras.repository)
163 164 audit_logger.store(
164 165 action='user.pull', action_data={
165 166 'user_agent': extras.user_agent},
166 user=audit_user, commit=True)
167 user=audit_user, repo=repo, commit=True)
167 168
168 169 # Propagate to external components.
169 170 if not is_shadow_repo(extras):
170 171 post_pull_extension(**extras)
171 172 events.trigger(events.RepoPullEvent(
172 173 repo_name=extras.repository, extras=extras))
173 174
174 175 output = ''
175 176 # make lock is a tri state False, True, None. We only make lock on True
176 177 if extras.make_lock is True and not is_shadow_repo(extras):
177 178 Repository.lock(Repository.get_by_repo_name(extras.repository),
178 179 user.user_id,
179 180 lock_reason=Repository.LOCK_PULL)
180 181 msg = 'Made lock on repo `%s`' % (extras.repository,)
181 182 output += msg
182 183
183 184 if extras.locked_by[0]:
184 185 locked_by = User.get(extras.locked_by[0]).username
185 186 reason = extras.locked_by[2]
186 187 _http_ret = HTTPLockedRC(
187 188 _locked_by_explanation(extras.repository, locked_by, reason))
188 189 if str(_http_ret.code).startswith('2'):
189 190 # 2xx Codes don't raise exceptions
190 191 output += _http_ret.title
191 192
192 193 return HookResponse(0, output)
193 194
194 195
195 196 def post_push(extras):
196 197 """Hook executed after user pushes to the repository."""
197 198 action_tmpl = extras.action + ':%s'
198 199 commit_ids = extras.commit_ids[:29000]
199 200
200 201 action = action_tmpl % ','.join(commit_ids)
201 202 action_logger(
202 203 extras.username, action, extras.repository, extras.ip, commit=True)
203 204
204 205 audit_user = audit_logger.UserWrap(
205 206 username=extras.username,
206 207 ip_addr=extras.ip)
207 208 repo = audit_logger.RepoWrap(repo_name=extras.repository)
208 209 audit_logger.store(
209 210 action='user.push', action_data={
210 211 'user_agent': extras.user_agent,
211 212 'commit_ids': commit_ids[:10000]},
212 213 user=audit_user, repo=repo, commit=True)
213 214
214 215 # Propagate to external components.
215 216 if not is_shadow_repo(extras):
216 217 post_push_extension(
217 218 repo_store_path=Repository.base_path(),
218 219 pushed_revs=commit_ids,
219 220 **extras)
220 221 events.trigger(events.RepoPushEvent(
221 222 repo_name=extras.repository,
222 223 pushed_commit_ids=commit_ids,
223 224 extras=extras))
224 225
225 226 output = ''
226 227 # make lock is a tri state False, True, None. We only release lock on False
227 228 if extras.make_lock is False and not is_shadow_repo(extras):
228 229 Repository.unlock(Repository.get_by_repo_name(extras.repository))
229 230 msg = 'Released lock on repo `%s`\n' % extras.repository
230 231 output += msg
231 232
232 233 if extras.locked_by[0]:
233 234 locked_by = User.get(extras.locked_by[0]).username
234 235 reason = extras.locked_by[2]
235 236 _http_ret = HTTPLockedRC(
236 237 _locked_by_explanation(extras.repository, locked_by, reason))
237 238 # TODO: johbo: if not?
238 239 if str(_http_ret.code).startswith('2'):
239 240 # 2xx Codes don't raise exceptions
240 241 output += _http_ret.title
241 242
242 243 output += 'RhodeCode: push completed\n'
243 244
244 245 return HookResponse(0, output)
245 246
246 247
247 248 def _locked_by_explanation(repo_name, user_name, reason):
248 249 message = (
249 250 'Repository `%s` locked by user `%s`. Reason:`%s`'
250 251 % (repo_name, user_name, reason))
251 252 return message
252 253
253 254
254 255 def check_allowed_create_user(user_dict, created_by, **kwargs):
255 256 # pre create hooks
256 257 if pre_create_user.is_active():
257 258 allowed, reason = pre_create_user(created_by=created_by, **user_dict)
258 259 if not allowed:
259 260 raise UserCreationError(reason)
260 261
261 262
262 263 class ExtensionCallback(object):
263 264 """
264 265 Forwards a given call to rcextensions, sanitizes keyword arguments.
265 266
266 267 Does check if there is an extension active for that hook. If it is
267 268 there, it will forward all `kwargs_keys` keyword arguments to the
268 269 extension callback.
269 270 """
270 271
271 272 def __init__(self, hook_name, kwargs_keys):
272 273 self._hook_name = hook_name
273 274 self._kwargs_keys = set(kwargs_keys)
274 275
275 276 def __call__(self, *args, **kwargs):
276 277 log.debug('Calling extension callback for %s', self._hook_name)
277 278
278 279 kwargs_to_pass = dict((key, kwargs[key]) for key in self._kwargs_keys)
279 280 # backward compat for removed api_key for old hooks. THis was it works
280 281 # with older rcextensions that require api_key present
281 282 if self._hook_name in ['CREATE_USER_HOOK', 'DELETE_USER_HOOK']:
282 283 kwargs_to_pass['api_key'] = '_DEPRECATED_'
283 284
284 285 callback = self._get_callback()
285 286 if callback:
286 287 return callback(**kwargs_to_pass)
287 288 else:
288 289 log.debug('extensions callback not found skipping...')
289 290
290 291 def is_active(self):
291 292 return hasattr(rhodecode.EXTENSIONS, self._hook_name)
292 293
293 294 def _get_callback(self):
294 295 return getattr(rhodecode.EXTENSIONS, self._hook_name, None)
295 296
296 297
297 298 pre_pull_extension = ExtensionCallback(
298 299 hook_name='PRE_PULL_HOOK',
299 300 kwargs_keys=(
300 301 'server_url', 'config', 'scm', 'username', 'ip', 'action',
301 302 'repository'))
302 303
303 304
304 305 post_pull_extension = ExtensionCallback(
305 306 hook_name='PULL_HOOK',
306 307 kwargs_keys=(
307 308 'server_url', 'config', 'scm', 'username', 'ip', 'action',
308 309 'repository'))
309 310
310 311
311 312 pre_push_extension = ExtensionCallback(
312 313 hook_name='PRE_PUSH_HOOK',
313 314 kwargs_keys=(
314 315 'server_url', 'config', 'scm', 'username', 'ip', 'action',
315 316 'repository', 'repo_store_path', 'commit_ids'))
316 317
317 318
318 319 post_push_extension = ExtensionCallback(
319 320 hook_name='PUSH_HOOK',
320 321 kwargs_keys=(
321 322 'server_url', 'config', 'scm', 'username', 'ip', 'action',
322 323 'repository', 'repo_store_path', 'pushed_revs'))
323 324
324 325
325 326 pre_create_user = ExtensionCallback(
326 327 hook_name='PRE_CREATE_USER_HOOK',
327 328 kwargs_keys=(
328 329 'username', 'password', 'email', 'firstname', 'lastname', 'active',
329 330 'admin', 'created_by'))
330 331
331 332
332 333 log_create_pull_request = ExtensionCallback(
333 334 hook_name='CREATE_PULL_REQUEST',
334 335 kwargs_keys=(
335 336 'server_url', 'config', 'scm', 'username', 'ip', 'action',
336 337 'repository', 'pull_request_id', 'url', 'title', 'description',
337 338 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
338 339 'mergeable', 'source', 'target', 'author', 'reviewers'))
339 340
340 341
341 342 log_merge_pull_request = ExtensionCallback(
342 343 hook_name='MERGE_PULL_REQUEST',
343 344 kwargs_keys=(
344 345 'server_url', 'config', 'scm', 'username', 'ip', 'action',
345 346 'repository', 'pull_request_id', 'url', 'title', 'description',
346 347 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
347 348 'mergeable', 'source', 'target', 'author', 'reviewers'))
348 349
349 350
350 351 log_close_pull_request = ExtensionCallback(
351 352 hook_name='CLOSE_PULL_REQUEST',
352 353 kwargs_keys=(
353 354 'server_url', 'config', 'scm', 'username', 'ip', 'action',
354 355 'repository', 'pull_request_id', 'url', 'title', 'description',
355 356 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
356 357 'mergeable', 'source', 'target', 'author', 'reviewers'))
357 358
358 359
359 360 log_review_pull_request = ExtensionCallback(
360 361 hook_name='REVIEW_PULL_REQUEST',
361 362 kwargs_keys=(
362 363 'server_url', 'config', 'scm', 'username', 'ip', 'action',
363 364 'repository', 'pull_request_id', 'url', 'title', 'description',
364 365 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
365 366 'mergeable', 'source', 'target', 'author', 'reviewers'))
366 367
367 368
368 369 log_update_pull_request = ExtensionCallback(
369 370 hook_name='UPDATE_PULL_REQUEST',
370 371 kwargs_keys=(
371 372 'server_url', 'config', 'scm', 'username', 'ip', 'action',
372 373 'repository', 'pull_request_id', 'url', 'title', 'description',
373 374 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
374 375 'mergeable', 'source', 'target', 'author', 'reviewers'))
375 376
376 377
377 378 log_create_user = ExtensionCallback(
378 379 hook_name='CREATE_USER_HOOK',
379 380 kwargs_keys=(
380 381 'username', 'full_name_or_username', 'full_contact', 'user_id',
381 382 'name', 'firstname', 'short_contact', 'admin', 'lastname',
382 383 'ip_addresses', 'extern_type', 'extern_name',
383 384 'email', 'api_keys', 'last_login',
384 385 'full_name', 'active', 'password', 'emails',
385 386 'inherit_default_permissions', 'created_by', 'created_on'))
386 387
387 388
388 389 log_delete_user = ExtensionCallback(
389 390 hook_name='DELETE_USER_HOOK',
390 391 kwargs_keys=(
391 392 'username', 'full_name_or_username', 'full_contact', 'user_id',
392 393 'name', 'firstname', 'short_contact', 'admin', 'lastname',
393 394 'ip_addresses',
394 395 'email', 'last_login',
395 396 'full_name', 'active', 'password', 'emails',
396 397 'inherit_default_permissions', 'deleted_by'))
397 398
398 399
399 400 log_create_repository = ExtensionCallback(
400 401 hook_name='CREATE_REPO_HOOK',
401 402 kwargs_keys=(
402 403 'repo_name', 'repo_type', 'description', 'private', 'created_on',
403 404 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
404 405 'clone_uri', 'fork_id', 'group_id', 'created_by'))
405 406
406 407
407 408 log_delete_repository = ExtensionCallback(
408 409 hook_name='DELETE_REPO_HOOK',
409 410 kwargs_keys=(
410 411 'repo_name', 'repo_type', 'description', 'private', 'created_on',
411 412 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
412 413 'clone_uri', 'fork_id', 'group_id', 'deleted_by', 'deleted_on'))
413 414
414 415
415 416 log_create_repository_group = ExtensionCallback(
416 417 hook_name='CREATE_REPO_GROUP_HOOK',
417 418 kwargs_keys=(
418 419 'group_name', 'group_parent_id', 'group_description',
419 420 'group_id', 'user_id', 'created_by', 'created_on',
420 421 'enable_locking'))
General Comments 0
You need to be logged in to leave comments. Login now