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