##// END OF EJS Templates
audit-logs: expose tailoed audit logs in repository view
marcink -
r2156:ea1af41c default
parent child Browse files
Show More
@@ -0,0 +1,65 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2017-2017 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22 from pyramid.view import view_config
23
24 from rhodecode.apps._base import RepoAppView
25 from rhodecode.lib import helpers as h
26 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
27 from rhodecode.lib.utils2 import safe_int
28 from rhodecode.model.repo import RepoModel
29
30 log = logging.getLogger(__name__)
31
32
33 class AuditLogsView(RepoAppView):
34 def load_default_context(self):
35 c = self._get_local_tmpl_context()
36
37 self._register_global_c(c)
38 return c
39
40 @LoginRequired()
41 @HasRepoPermissionAnyDecorator('repository.admin')
42 @view_config(
43 route_name='edit_repo_audit_logs', request_method='GET',
44 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
45 def repo_audit_logs(self):
46 _ = self.request.translate
47 c = self.load_default_context()
48 c.db_repo = self.db_repo
49
50 c.active = 'audit'
51
52 p = safe_int(self.request.GET.get('page', 1), 1)
53
54 filter_term = self.request.GET.get('filter')
55 user_log = RepoModel().get_repo_log(c.db_repo, filter_term)
56
57 def url_generator(**kw):
58 if filter_term:
59 kw['filter'] = filter_term
60 return self.request.current_route_path(_query=kw)
61
62 c.audit_logs = h.Page(
63 user_log, page=p, items_per_page=10, url=url_generator)
64 c.filter_term = filter_term
65 return self._get_template_context(c)
@@ -0,0 +1,22 b''
1 ## -*- coding: utf-8 -*-
2 <%namespace name="base" file="/base/base.mako"/>
3
4
5 <div class="panel panel-default">
6 <div class="panel-heading">
7 <h3 class="panel-title">${_('Repository Audit Logs')} -
8 ${_ungettext('%s entry', '%s entries', c.audit_logs.item_count) % (c.audit_logs.item_count)}
9 </h3>
10 </div>
11 <div class="panel-body">
12
13 ${h.form(None, id_="filter_form", method="get")}
14 <input class="q_filter_box ${'' if c.filter_term else 'initial'}" id="j_filter" size="15" type="text" name="filter" value="${c.filter_term or ''}" placeholder="${_('audit filter...')}"/>
15 <input type='submit' value="${_('filter')}" class="btn" />
16 ${h.end_form()}
17 <p class="tooltip filterexample" style="position: inherit" title="${h.tooltip(h.journal_filter_help(c.pyramid_request))}">${_('Example Queries')}</p>
18
19 <%include file="/admin/admin_log_base.mako" />
20
21 </div>
22 </div>
@@ -1,450 +1,455 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-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 from rhodecode.apps._base import add_route_with_slash
21 21
22 22
23 23 def includeme(config):
24 24
25 25 # repo creating checks, special cases that aren't repo routes
26 26 config.add_route(
27 27 name='repo_creating',
28 28 pattern='/{repo_name:.*?[^/]}/repo_creating')
29 29
30 30 config.add_route(
31 31 name='repo_creating_check',
32 32 pattern='/{repo_name:.*?[^/]}/repo_creating_check')
33 33
34 34 # Summary
35 35 # NOTE(marcink): one additional route is defined in very bottom, catch
36 36 # all pattern
37 37 config.add_route(
38 38 name='repo_summary_explicit',
39 39 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
40 40 config.add_route(
41 41 name='repo_summary_commits',
42 42 pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True)
43 43
44 44 # Commits
45 45 config.add_route(
46 46 name='repo_commit',
47 47 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
48 48
49 49 config.add_route(
50 50 name='repo_commit_children',
51 51 pattern='/{repo_name:.*?[^/]}/changeset_children/{commit_id}', repo_route=True)
52 52
53 53 config.add_route(
54 54 name='repo_commit_parents',
55 55 pattern='/{repo_name:.*?[^/]}/changeset_parents/{commit_id}', repo_route=True)
56 56
57 57 config.add_route(
58 58 name='repo_commit_raw',
59 59 pattern='/{repo_name:.*?[^/]}/changeset-diff/{commit_id}', repo_route=True)
60 60
61 61 config.add_route(
62 62 name='repo_commit_patch',
63 63 pattern='/{repo_name:.*?[^/]}/changeset-patch/{commit_id}', repo_route=True)
64 64
65 65 config.add_route(
66 66 name='repo_commit_download',
67 67 pattern='/{repo_name:.*?[^/]}/changeset-download/{commit_id}', repo_route=True)
68 68
69 69 config.add_route(
70 70 name='repo_commit_data',
71 71 pattern='/{repo_name:.*?[^/]}/changeset-data/{commit_id}', repo_route=True)
72 72
73 73 config.add_route(
74 74 name='repo_commit_comment_create',
75 75 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/create', repo_route=True)
76 76
77 77 config.add_route(
78 78 name='repo_commit_comment_preview',
79 79 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True)
80 80
81 81 config.add_route(
82 82 name='repo_commit_comment_delete',
83 83 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/delete', repo_route=True)
84 84
85 85 # still working url for backward compat.
86 86 config.add_route(
87 87 name='repo_commit_raw_deprecated',
88 88 pattern='/{repo_name:.*?[^/]}/raw-changeset/{commit_id}', repo_route=True)
89 89
90 90 # Files
91 91 config.add_route(
92 92 name='repo_archivefile',
93 93 pattern='/{repo_name:.*?[^/]}/archive/{fname}', repo_route=True)
94 94
95 95 config.add_route(
96 96 name='repo_files_diff',
97 97 pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True)
98 98 config.add_route( # legacy route to make old links work
99 99 name='repo_files_diff_2way_redirect',
100 100 pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True)
101 101
102 102 config.add_route(
103 103 name='repo_files',
104 104 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True)
105 105 config.add_route(
106 106 name='repo_files:default_path',
107 107 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True)
108 108 config.add_route(
109 109 name='repo_files:default_commit',
110 110 pattern='/{repo_name:.*?[^/]}/files', repo_route=True)
111 111
112 112 config.add_route(
113 113 name='repo_files:rendered',
114 114 pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True)
115 115
116 116 config.add_route(
117 117 name='repo_files:annotated',
118 118 pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True)
119 119 config.add_route(
120 120 name='repo_files:annotated_previous',
121 121 pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True)
122 122
123 123 config.add_route(
124 124 name='repo_nodetree_full',
125 125 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True)
126 126 config.add_route(
127 127 name='repo_nodetree_full:default_path',
128 128 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True)
129 129
130 130 config.add_route(
131 131 name='repo_files_nodelist',
132 132 pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True)
133 133
134 134 config.add_route(
135 135 name='repo_file_raw',
136 136 pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True)
137 137
138 138 config.add_route(
139 139 name='repo_file_download',
140 140 pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True)
141 141 config.add_route( # backward compat to keep old links working
142 142 name='repo_file_download:legacy',
143 143 pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}',
144 144 repo_route=True)
145 145
146 146 config.add_route(
147 147 name='repo_file_history',
148 148 pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True)
149 149
150 150 config.add_route(
151 151 name='repo_file_authors',
152 152 pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
153 153
154 154 config.add_route(
155 155 name='repo_files_remove_file',
156 156 pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
157 157 repo_route=True)
158 158 config.add_route(
159 159 name='repo_files_delete_file',
160 160 pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}',
161 161 repo_route=True)
162 162 config.add_route(
163 163 name='repo_files_edit_file',
164 164 pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}',
165 165 repo_route=True)
166 166 config.add_route(
167 167 name='repo_files_update_file',
168 168 pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}',
169 169 repo_route=True)
170 170 config.add_route(
171 171 name='repo_files_add_file',
172 172 pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}',
173 173 repo_route=True)
174 174 config.add_route(
175 175 name='repo_files_create_file',
176 176 pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}',
177 177 repo_route=True)
178 178
179 179 # Refs data
180 180 config.add_route(
181 181 name='repo_refs_data',
182 182 pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True)
183 183
184 184 config.add_route(
185 185 name='repo_refs_changelog_data',
186 186 pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True)
187 187
188 188 config.add_route(
189 189 name='repo_stats',
190 190 pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True)
191 191
192 192 # Changelog
193 193 config.add_route(
194 194 name='repo_changelog',
195 195 pattern='/{repo_name:.*?[^/]}/changelog', repo_route=True)
196 196 config.add_route(
197 197 name='repo_changelog_file',
198 198 pattern='/{repo_name:.*?[^/]}/changelog/{commit_id}/{f_path:.*}', repo_route=True)
199 199 config.add_route(
200 200 name='repo_changelog_elements',
201 201 pattern='/{repo_name:.*?[^/]}/changelog_elements', repo_route=True)
202 202 config.add_route(
203 203 name='repo_changelog_elements_file',
204 204 pattern='/{repo_name:.*?[^/]}/changelog_elements/{commit_id}/{f_path:.*}', repo_route=True)
205 205
206 206 # Compare
207 207 config.add_route(
208 208 name='repo_compare_select',
209 209 pattern='/{repo_name:.*?[^/]}/compare', repo_route=True)
210 210
211 211 config.add_route(
212 212 name='repo_compare',
213 213 pattern='/{repo_name:.*?[^/]}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}', repo_route=True)
214 214
215 215 # Tags
216 216 config.add_route(
217 217 name='tags_home',
218 218 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
219 219
220 220 # Branches
221 221 config.add_route(
222 222 name='branches_home',
223 223 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
224 224
225 225 # Bookmarks
226 226 config.add_route(
227 227 name='bookmarks_home',
228 228 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
229 229
230 230 # Forks
231 231 config.add_route(
232 232 name='repo_fork_new',
233 233 pattern='/{repo_name:.*?[^/]}/fork', repo_route=True,
234 234 repo_accepted_types=['hg', 'git'])
235 235
236 236 config.add_route(
237 237 name='repo_fork_create',
238 238 pattern='/{repo_name:.*?[^/]}/fork/create', repo_route=True,
239 239 repo_accepted_types=['hg', 'git'])
240 240
241 241 config.add_route(
242 242 name='repo_forks_show_all',
243 243 pattern='/{repo_name:.*?[^/]}/forks', repo_route=True,
244 244 repo_accepted_types=['hg', 'git'])
245 245 config.add_route(
246 246 name='repo_forks_data',
247 247 pattern='/{repo_name:.*?[^/]}/forks/data', repo_route=True,
248 248 repo_accepted_types=['hg', 'git'])
249 249
250 250 # Pull Requests
251 251 config.add_route(
252 252 name='pullrequest_show',
253 253 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}',
254 254 repo_route=True)
255 255
256 256 config.add_route(
257 257 name='pullrequest_show_all',
258 258 pattern='/{repo_name:.*?[^/]}/pull-request',
259 259 repo_route=True, repo_accepted_types=['hg', 'git'])
260 260
261 261 config.add_route(
262 262 name='pullrequest_show_all_data',
263 263 pattern='/{repo_name:.*?[^/]}/pull-request-data',
264 264 repo_route=True, repo_accepted_types=['hg', 'git'])
265 265
266 266 config.add_route(
267 267 name='pullrequest_repo_refs',
268 268 pattern='/{repo_name:.*?[^/]}/pull-request/refs/{target_repo_name:.*?[^/]}',
269 269 repo_route=True)
270 270
271 271 config.add_route(
272 272 name='pullrequest_repo_destinations',
273 273 pattern='/{repo_name:.*?[^/]}/pull-request/repo-destinations',
274 274 repo_route=True)
275 275
276 276 config.add_route(
277 277 name='pullrequest_new',
278 278 pattern='/{repo_name:.*?[^/]}/pull-request/new',
279 279 repo_route=True, repo_accepted_types=['hg', 'git'])
280 280
281 281 config.add_route(
282 282 name='pullrequest_create',
283 283 pattern='/{repo_name:.*?[^/]}/pull-request/create',
284 284 repo_route=True, repo_accepted_types=['hg', 'git'])
285 285
286 286 config.add_route(
287 287 name='pullrequest_update',
288 288 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/update',
289 289 repo_route=True)
290 290
291 291 config.add_route(
292 292 name='pullrequest_merge',
293 293 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/merge',
294 294 repo_route=True)
295 295
296 296 config.add_route(
297 297 name='pullrequest_delete',
298 298 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/delete',
299 299 repo_route=True)
300 300
301 301 config.add_route(
302 302 name='pullrequest_comment_create',
303 303 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment',
304 304 repo_route=True)
305 305
306 306 config.add_route(
307 307 name='pullrequest_comment_delete',
308 308 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete',
309 309 repo_route=True, repo_accepted_types=['hg', 'git'])
310 310
311 311 # Settings
312 312 config.add_route(
313 313 name='edit_repo',
314 314 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
315 315
316 316 # Settings advanced
317 317 config.add_route(
318 318 name='edit_repo_advanced',
319 319 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
320 320 config.add_route(
321 321 name='edit_repo_advanced_delete',
322 322 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
323 323 config.add_route(
324 324 name='edit_repo_advanced_locking',
325 325 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
326 326 config.add_route(
327 327 name='edit_repo_advanced_journal',
328 328 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
329 329 config.add_route(
330 330 name='edit_repo_advanced_fork',
331 331 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
332 332
333 333 # Caches
334 334 config.add_route(
335 335 name='edit_repo_caches',
336 336 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
337 337
338 338 # Permissions
339 339 config.add_route(
340 340 name='edit_repo_perms',
341 341 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
342 342
343 343 # Maintenance
344 344 config.add_route(
345 345 name='edit_repo_maintenance',
346 346 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
347 347
348 348 config.add_route(
349 349 name='edit_repo_maintenance_execute',
350 350 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
351 351
352 352 # Fields
353 353 config.add_route(
354 354 name='edit_repo_fields',
355 355 pattern='/{repo_name:.*?[^/]}/settings/fields', repo_route=True)
356 356 config.add_route(
357 357 name='edit_repo_fields_create',
358 358 pattern='/{repo_name:.*?[^/]}/settings/fields/create', repo_route=True)
359 359 config.add_route(
360 360 name='edit_repo_fields_delete',
361 361 pattern='/{repo_name:.*?[^/]}/settings/fields/{field_id}/delete', repo_route=True)
362 362
363 363 # Locking
364 364 config.add_route(
365 365 name='repo_edit_toggle_locking',
366 366 pattern='/{repo_name:.*?[^/]}/settings/toggle_locking', repo_route=True)
367 367
368 368 # Remote
369 369 config.add_route(
370 370 name='edit_repo_remote',
371 371 pattern='/{repo_name:.*?[^/]}/settings/remote', repo_route=True)
372 372 config.add_route(
373 373 name='edit_repo_remote_pull',
374 374 pattern='/{repo_name:.*?[^/]}/settings/remote/pull', repo_route=True)
375 375
376 376
377 377 # Statistics
378 378 config.add_route(
379 379 name='edit_repo_statistics',
380 380 pattern='/{repo_name:.*?[^/]}/settings/statistics', repo_route=True)
381 381 config.add_route(
382 382 name='edit_repo_statistics_reset',
383 383 pattern='/{repo_name:.*?[^/]}/settings/statistics/update', repo_route=True)
384 384
385 385 # Issue trackers
386 386 config.add_route(
387 387 name='edit_repo_issuetracker',
388 388 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers', repo_route=True)
389 389 config.add_route(
390 390 name='edit_repo_issuetracker_test',
391 391 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/test', repo_route=True)
392 392 config.add_route(
393 393 name='edit_repo_issuetracker_delete',
394 394 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/delete', repo_route=True)
395 395 config.add_route(
396 396 name='edit_repo_issuetracker_update',
397 397 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/update', repo_route=True)
398 398
399 399 # VCS Settings
400 400 config.add_route(
401 401 name='edit_repo_vcs',
402 402 pattern='/{repo_name:.*?[^/]}/settings/vcs', repo_route=True)
403 403 config.add_route(
404 404 name='edit_repo_vcs_update',
405 405 pattern='/{repo_name:.*?[^/]}/settings/vcs/update', repo_route=True)
406 406
407 407 # svn pattern
408 408 config.add_route(
409 409 name='edit_repo_vcs_svn_pattern_delete',
410 410 pattern='/{repo_name:.*?[^/]}/settings/vcs/svn_pattern/delete', repo_route=True)
411 411
412 412 # Repo Review Rules (EE feature)
413 413 config.add_route(
414 414 name='repo_reviewers',
415 415 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
416 416
417 417 config.add_route(
418 418 name='repo_default_reviewers_data',
419 419 pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True)
420 420
421 421 # Strip
422 422 config.add_route(
423 423 name='edit_repo_strip',
424 424 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
425 425
426 426 config.add_route(
427 427 name='strip_check',
428 428 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
429 429
430 430 config.add_route(
431 431 name='strip_execute',
432 432 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
433 433
434 # Audit logs
435 config.add_route(
436 name='edit_repo_audit_logs',
437 pattern='/{repo_name:.*?[^/]}/settings/audit_logs', repo_route=True)
438
434 439 # ATOM/RSS Feed
435 440 config.add_route(
436 441 name='rss_feed_home',
437 442 pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True)
438 443
439 444 config.add_route(
440 445 name='atom_feed_home',
441 446 pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True)
442 447
443 448 # NOTE(marcink): needs to be at the end for catch-all
444 449 add_route_with_slash(
445 450 config,
446 451 name='repo_summary',
447 452 pattern='/{repo_name:.*?[^/]}', repo_route=True)
448 453
449 454 # Scan module for configuration decorators.
450 455 config.scan('.views', ignore='.tests')
@@ -1,1020 +1,1032 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-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 import logging
22 21 import os
23 22 import re
24 23 import shutil
25 24 import time
25 import logging
26 26 import traceback
27 27 import datetime
28 28
29 29 from pyramid.threadlocal import get_current_request
30 30 from zope.cachedescriptors.property import Lazy as LazyProperty
31 31
32 32 from rhodecode import events
33 from rhodecode.lib import helpers as h
34 33 from rhodecode.lib.auth import HasUserGroupPermissionAny
35 34 from rhodecode.lib.caching_query import FromCache
36 35 from rhodecode.lib.exceptions import AttachedForksError
37 36 from rhodecode.lib.hooks_base import log_delete_repository
37 from rhodecode.lib.user_log_filter import user_log_filter
38 38 from rhodecode.lib.utils import make_db_config
39 39 from rhodecode.lib.utils2 import (
40 40 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
41 get_current_rhodecode_user, safe_int, datetime_to_time, action_logger_generic)
41 get_current_rhodecode_user, safe_int, datetime_to_time,
42 action_logger_generic)
42 43 from rhodecode.lib.vcs.backends import get_backend
43 44 from rhodecode.model import BaseModel
44 from rhodecode.model.db import (_hash_key,
45 Repository, UserRepoToPerm, UserGroupRepoToPerm, UserRepoGroupToPerm,
46 UserGroupRepoGroupToPerm, User, Permission, Statistics, UserGroup,
47 RepoGroup, RepositoryField)
45 from rhodecode.model.db import (
46 _hash_key, joinedload, or_, Repository, UserRepoToPerm, UserGroupRepoToPerm,
47 UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission,
48 Statistics, UserGroup, RepoGroup, RepositoryField, UserLog)
48 49
49 50 from rhodecode.model.settings import VcsSettingsModel
50 51
51 52
52 53 log = logging.getLogger(__name__)
53 54
54 55
55 56 class RepoModel(BaseModel):
56 57
57 58 cls = Repository
58 59
59 60 def _get_user_group(self, users_group):
60 61 return self._get_instance(UserGroup, users_group,
61 62 callback=UserGroup.get_by_group_name)
62 63
63 64 def _get_repo_group(self, repo_group):
64 65 return self._get_instance(RepoGroup, repo_group,
65 66 callback=RepoGroup.get_by_group_name)
66 67
67 68 def _create_default_perms(self, repository, private):
68 69 # create default permission
69 70 default = 'repository.read'
70 71 def_user = User.get_default_user()
71 72 for p in def_user.user_perms:
72 73 if p.permission.permission_name.startswith('repository.'):
73 74 default = p.permission.permission_name
74 75 break
75 76
76 77 default_perm = 'repository.none' if private else default
77 78
78 79 repo_to_perm = UserRepoToPerm()
79 80 repo_to_perm.permission = Permission.get_by_key(default_perm)
80 81
81 82 repo_to_perm.repository = repository
82 83 repo_to_perm.user_id = def_user.user_id
83 84
84 85 return repo_to_perm
85 86
86 87 @LazyProperty
87 88 def repos_path(self):
88 89 """
89 90 Gets the repositories root path from database
90 91 """
91 92 settings_model = VcsSettingsModel(sa=self.sa)
92 93 return settings_model.get_repos_location()
93 94
94 95 def get(self, repo_id, cache=False):
95 96 repo = self.sa.query(Repository) \
96 97 .filter(Repository.repo_id == repo_id)
97 98
98 99 if cache:
99 100 repo = repo.options(
100 101 FromCache("sql_cache_short", "get_repo_%s" % repo_id))
101 102 return repo.scalar()
102 103
103 104 def get_repo(self, repository):
104 105 return self._get_repo(repository)
105 106
106 107 def get_by_repo_name(self, repo_name, cache=False):
107 108 repo = self.sa.query(Repository) \
108 109 .filter(Repository.repo_name == repo_name)
109 110
110 111 if cache:
111 112 name_key = _hash_key(repo_name)
112 113 repo = repo.options(
113 114 FromCache("sql_cache_short", "get_repo_%s" % name_key))
114 115 return repo.scalar()
115 116
116 117 def _extract_id_from_repo_name(self, repo_name):
117 118 if repo_name.startswith('/'):
118 119 repo_name = repo_name.lstrip('/')
119 120 by_id_match = re.match(r'^_(\d{1,})', repo_name)
120 121 if by_id_match:
121 122 return by_id_match.groups()[0]
122 123
123 124 def get_repo_by_id(self, repo_name):
124 125 """
125 126 Extracts repo_name by id from special urls.
126 127 Example url is _11/repo_name
127 128
128 129 :param repo_name:
129 130 :return: repo object if matched else None
130 131 """
131 132
132 133 try:
133 134 _repo_id = self._extract_id_from_repo_name(repo_name)
134 135 if _repo_id:
135 136 return self.get(_repo_id)
136 137 except Exception:
137 138 log.exception('Failed to extract repo_name from URL')
138 139
139 140 return None
140 141
141 142 def get_repos_for_root(self, root, traverse=False):
142 143 if traverse:
143 144 like_expression = u'{}%'.format(safe_unicode(root))
144 145 repos = Repository.query().filter(
145 146 Repository.repo_name.like(like_expression)).all()
146 147 else:
147 148 if root and not isinstance(root, RepoGroup):
148 149 raise ValueError(
149 150 'Root must be an instance '
150 151 'of RepoGroup, got:{} instead'.format(type(root)))
151 152 repos = Repository.query().filter(Repository.group == root).all()
152 153 return repos
153 154
154 155 def get_url(self, repo, request=None, permalink=False):
155 156 if not request:
156 157 request = get_current_request()
157 158
158 159 if not request:
159 160 return
160 161
161 162 if permalink:
162 163 return request.route_url(
163 164 'repo_summary', repo_name=safe_str(repo.repo_id))
164 165 else:
165 166 return request.route_url(
166 167 'repo_summary', repo_name=safe_str(repo.repo_name))
167 168
168 169 def get_commit_url(self, repo, commit_id, request=None, permalink=False):
169 170 if not request:
170 171 request = get_current_request()
171 172
172 173 if not request:
173 174 return
174 175
175 176 if permalink:
176 177 return request.route_url(
177 178 'repo_commit', repo_name=safe_str(repo.repo_id),
178 179 commit_id=commit_id)
179 180
180 181 else:
181 182 return request.route_url(
182 183 'repo_commit', repo_name=safe_str(repo.repo_name),
183 184 commit_id=commit_id)
184 185
186 def get_repo_log(self, repo, filter_term):
187 repo_log = UserLog.query()\
188 .filter(or_(UserLog.repository_id == repo.repo_id,
189 UserLog.repository_name == repo.repo_name))\
190 .options(joinedload(UserLog.user))\
191 .options(joinedload(UserLog.repository))\
192 .order_by(UserLog.action_date.desc())
193
194 repo_log = user_log_filter(repo_log, filter_term)
195 return repo_log
196
185 197 @classmethod
186 198 def update_repoinfo(cls, repositories=None):
187 199 if not repositories:
188 200 repositories = Repository.getAll()
189 201 for repo in repositories:
190 202 repo.update_commit_cache()
191 203
192 204 def get_repos_as_dict(self, repo_list=None, admin=False,
193 205 super_user_actions=False):
194 206 _render = get_current_request().get_partial_renderer(
195 207 'data_table/_dt_elements.mako')
196 208 c = _render.get_call_context()
197 209
198 210 def quick_menu(repo_name):
199 211 return _render('quick_menu', repo_name)
200 212
201 213 def repo_lnk(name, rtype, rstate, private, fork_of):
202 214 return _render('repo_name', name, rtype, rstate, private, fork_of,
203 215 short_name=not admin, admin=False)
204 216
205 217 def last_change(last_change):
206 218 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
207 219 last_change = last_change + datetime.timedelta(seconds=
208 220 (datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
209 221 return _render("last_change", last_change)
210 222
211 223 def rss_lnk(repo_name):
212 224 return _render("rss", repo_name)
213 225
214 226 def atom_lnk(repo_name):
215 227 return _render("atom", repo_name)
216 228
217 229 def last_rev(repo_name, cs_cache):
218 230 return _render('revision', repo_name, cs_cache.get('revision'),
219 231 cs_cache.get('raw_id'), cs_cache.get('author'),
220 232 cs_cache.get('message'))
221 233
222 234 def desc(desc):
223 235 return _render('repo_desc', desc, c.visual.stylify_metatags)
224 236
225 237 def state(repo_state):
226 238 return _render("repo_state", repo_state)
227 239
228 240 def repo_actions(repo_name):
229 241 return _render('repo_actions', repo_name, super_user_actions)
230 242
231 243 def user_profile(username):
232 244 return _render('user_profile', username)
233 245
234 246 repos_data = []
235 247 for repo in repo_list:
236 248 cs_cache = repo.changeset_cache
237 249 row = {
238 250 "menu": quick_menu(repo.repo_name),
239 251
240 252 "name": repo_lnk(repo.repo_name, repo.repo_type,
241 253 repo.repo_state, repo.private, repo.fork),
242 254 "name_raw": repo.repo_name.lower(),
243 255
244 256 "last_change": last_change(repo.last_db_change),
245 257 "last_change_raw": datetime_to_time(repo.last_db_change),
246 258
247 259 "last_changeset": last_rev(repo.repo_name, cs_cache),
248 260 "last_changeset_raw": cs_cache.get('revision'),
249 261
250 262 "desc": desc(repo.description_safe),
251 263 "owner": user_profile(repo.user.username),
252 264
253 265 "state": state(repo.repo_state),
254 266 "rss": rss_lnk(repo.repo_name),
255 267
256 268 "atom": atom_lnk(repo.repo_name),
257 269 }
258 270 if admin:
259 271 row.update({
260 272 "action": repo_actions(repo.repo_name),
261 273 })
262 274 repos_data.append(row)
263 275
264 276 return repos_data
265 277
266 278 def _get_defaults(self, repo_name):
267 279 """
268 280 Gets information about repository, and returns a dict for
269 281 usage in forms
270 282
271 283 :param repo_name:
272 284 """
273 285
274 286 repo_info = Repository.get_by_repo_name(repo_name)
275 287
276 288 if repo_info is None:
277 289 return None
278 290
279 291 defaults = repo_info.get_dict()
280 292 defaults['repo_name'] = repo_info.just_name
281 293
282 294 groups = repo_info.groups_with_parents
283 295 parent_group = groups[-1] if groups else None
284 296
285 297 # we use -1 as this is how in HTML, we mark an empty group
286 298 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
287 299
288 300 keys_to_process = (
289 301 {'k': 'repo_type', 'strip': False},
290 302 {'k': 'repo_enable_downloads', 'strip': True},
291 303 {'k': 'repo_description', 'strip': True},
292 304 {'k': 'repo_enable_locking', 'strip': True},
293 305 {'k': 'repo_landing_rev', 'strip': True},
294 306 {'k': 'clone_uri', 'strip': False},
295 307 {'k': 'repo_private', 'strip': True},
296 308 {'k': 'repo_enable_statistics', 'strip': True}
297 309 )
298 310
299 311 for item in keys_to_process:
300 312 attr = item['k']
301 313 if item['strip']:
302 314 attr = remove_prefix(item['k'], 'repo_')
303 315
304 316 val = defaults[attr]
305 317 if item['k'] == 'repo_landing_rev':
306 318 val = ':'.join(defaults[attr])
307 319 defaults[item['k']] = val
308 320 if item['k'] == 'clone_uri':
309 321 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
310 322
311 323 # fill owner
312 324 if repo_info.user:
313 325 defaults.update({'user': repo_info.user.username})
314 326 else:
315 327 replacement_user = User.get_first_super_admin().username
316 328 defaults.update({'user': replacement_user})
317 329
318 330 return defaults
319 331
320 332 def update(self, repo, **kwargs):
321 333 try:
322 334 cur_repo = self._get_repo(repo)
323 335 source_repo_name = cur_repo.repo_name
324 336 if 'user' in kwargs:
325 337 cur_repo.user = User.get_by_username(kwargs['user'])
326 338
327 339 if 'repo_group' in kwargs:
328 340 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
329 341 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
330 342
331 343 update_keys = [
332 344 (1, 'repo_description'),
333 345 (1, 'repo_landing_rev'),
334 346 (1, 'repo_private'),
335 347 (1, 'repo_enable_downloads'),
336 348 (1, 'repo_enable_locking'),
337 349 (1, 'repo_enable_statistics'),
338 350 (0, 'clone_uri'),
339 351 (0, 'fork_id')
340 352 ]
341 353 for strip, k in update_keys:
342 354 if k in kwargs:
343 355 val = kwargs[k]
344 356 if strip:
345 357 k = remove_prefix(k, 'repo_')
346 358
347 359 setattr(cur_repo, k, val)
348 360
349 361 new_name = cur_repo.get_new_name(kwargs['repo_name'])
350 362 cur_repo.repo_name = new_name
351 363
352 364 # if private flag is set, reset default permission to NONE
353 365 if kwargs.get('repo_private'):
354 366 EMPTY_PERM = 'repository.none'
355 367 RepoModel().grant_user_permission(
356 368 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
357 369 )
358 370
359 371 # handle extra fields
360 372 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
361 373 kwargs):
362 374 k = RepositoryField.un_prefix_key(field)
363 375 ex_field = RepositoryField.get_by_key_name(
364 376 key=k, repo=cur_repo)
365 377 if ex_field:
366 378 ex_field.field_value = kwargs[field]
367 379 self.sa.add(ex_field)
368 380 cur_repo.updated_on = datetime.datetime.now()
369 381 self.sa.add(cur_repo)
370 382
371 383 if source_repo_name != new_name:
372 384 # rename repository
373 385 self._rename_filesystem_repo(
374 386 old=source_repo_name, new=new_name)
375 387
376 388 return cur_repo
377 389 except Exception:
378 390 log.error(traceback.format_exc())
379 391 raise
380 392
381 393 def _create_repo(self, repo_name, repo_type, description, owner,
382 394 private=False, clone_uri=None, repo_group=None,
383 395 landing_rev='rev:tip', fork_of=None,
384 396 copy_fork_permissions=False, enable_statistics=False,
385 397 enable_locking=False, enable_downloads=False,
386 398 copy_group_permissions=False,
387 399 state=Repository.STATE_PENDING):
388 400 """
389 401 Create repository inside database with PENDING state, this should be
390 402 only executed by create() repo. With exception of importing existing
391 403 repos
392 404 """
393 405 from rhodecode.model.scm import ScmModel
394 406
395 407 owner = self._get_user(owner)
396 408 fork_of = self._get_repo(fork_of)
397 409 repo_group = self._get_repo_group(safe_int(repo_group))
398 410
399 411 try:
400 412 repo_name = safe_unicode(repo_name)
401 413 description = safe_unicode(description)
402 414 # repo name is just a name of repository
403 415 # while repo_name_full is a full qualified name that is combined
404 416 # with name and path of group
405 417 repo_name_full = repo_name
406 418 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
407 419
408 420 new_repo = Repository()
409 421 new_repo.repo_state = state
410 422 new_repo.enable_statistics = False
411 423 new_repo.repo_name = repo_name_full
412 424 new_repo.repo_type = repo_type
413 425 new_repo.user = owner
414 426 new_repo.group = repo_group
415 427 new_repo.description = description or repo_name
416 428 new_repo.private = private
417 429 new_repo.clone_uri = clone_uri
418 430 new_repo.landing_rev = landing_rev
419 431
420 432 new_repo.enable_statistics = enable_statistics
421 433 new_repo.enable_locking = enable_locking
422 434 new_repo.enable_downloads = enable_downloads
423 435
424 436 if repo_group:
425 437 new_repo.enable_locking = repo_group.enable_locking
426 438
427 439 if fork_of:
428 440 parent_repo = fork_of
429 441 new_repo.fork = parent_repo
430 442
431 443 events.trigger(events.RepoPreCreateEvent(new_repo))
432 444
433 445 self.sa.add(new_repo)
434 446
435 447 EMPTY_PERM = 'repository.none'
436 448 if fork_of and copy_fork_permissions:
437 449 repo = fork_of
438 450 user_perms = UserRepoToPerm.query() \
439 451 .filter(UserRepoToPerm.repository == repo).all()
440 452 group_perms = UserGroupRepoToPerm.query() \
441 453 .filter(UserGroupRepoToPerm.repository == repo).all()
442 454
443 455 for perm in user_perms:
444 456 UserRepoToPerm.create(
445 457 perm.user, new_repo, perm.permission)
446 458
447 459 for perm in group_perms:
448 460 UserGroupRepoToPerm.create(
449 461 perm.users_group, new_repo, perm.permission)
450 462 # in case we copy permissions and also set this repo to private
451 463 # override the default user permission to make it a private
452 464 # repo
453 465 if private:
454 466 RepoModel(self.sa).grant_user_permission(
455 467 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
456 468
457 469 elif repo_group and copy_group_permissions:
458 470 user_perms = UserRepoGroupToPerm.query() \
459 471 .filter(UserRepoGroupToPerm.group == repo_group).all()
460 472
461 473 group_perms = UserGroupRepoGroupToPerm.query() \
462 474 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
463 475
464 476 for perm in user_perms:
465 477 perm_name = perm.permission.permission_name.replace(
466 478 'group.', 'repository.')
467 479 perm_obj = Permission.get_by_key(perm_name)
468 480 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
469 481
470 482 for perm in group_perms:
471 483 perm_name = perm.permission.permission_name.replace(
472 484 'group.', 'repository.')
473 485 perm_obj = Permission.get_by_key(perm_name)
474 486 UserGroupRepoToPerm.create(
475 487 perm.users_group, new_repo, perm_obj)
476 488
477 489 if private:
478 490 RepoModel(self.sa).grant_user_permission(
479 491 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
480 492
481 493 else:
482 494 perm_obj = self._create_default_perms(new_repo, private)
483 495 self.sa.add(perm_obj)
484 496
485 497 # now automatically start following this repository as owner
486 498 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
487 499 owner.user_id)
488 500
489 501 # we need to flush here, in order to check if database won't
490 502 # throw any exceptions, create filesystem dirs at the very end
491 503 self.sa.flush()
492 504 events.trigger(events.RepoCreateEvent(new_repo))
493 505 return new_repo
494 506
495 507 except Exception:
496 508 log.error(traceback.format_exc())
497 509 raise
498 510
499 511 def create(self, form_data, cur_user):
500 512 """
501 513 Create repository using celery tasks
502 514
503 515 :param form_data:
504 516 :param cur_user:
505 517 """
506 518 from rhodecode.lib.celerylib import tasks, run_task
507 519 return run_task(tasks.create_repo, form_data, cur_user)
508 520
509 521 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
510 522 perm_deletions=None, check_perms=True,
511 523 cur_user=None):
512 524 if not perm_additions:
513 525 perm_additions = []
514 526 if not perm_updates:
515 527 perm_updates = []
516 528 if not perm_deletions:
517 529 perm_deletions = []
518 530
519 531 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
520 532
521 533 changes = {
522 534 'added': [],
523 535 'updated': [],
524 536 'deleted': []
525 537 }
526 538 # update permissions
527 539 for member_id, perm, member_type in perm_updates:
528 540 member_id = int(member_id)
529 541 if member_type == 'user':
530 542 member_name = User.get(member_id).username
531 543 # this updates also current one if found
532 544 self.grant_user_permission(
533 545 repo=repo, user=member_id, perm=perm)
534 546 else: # set for user group
535 547 # check if we have permissions to alter this usergroup
536 548 member_name = UserGroup.get(member_id).users_group_name
537 549 if not check_perms or HasUserGroupPermissionAny(
538 550 *req_perms)(member_name, user=cur_user):
539 551 self.grant_user_group_permission(
540 552 repo=repo, group_name=member_id, perm=perm)
541 553
542 554 changes['updated'].append({'type': member_type, 'id': member_id,
543 555 'name': member_name, 'new_perm': perm})
544 556
545 557 # set new permissions
546 558 for member_id, perm, member_type in perm_additions:
547 559 member_id = int(member_id)
548 560 if member_type == 'user':
549 561 member_name = User.get(member_id).username
550 562 self.grant_user_permission(
551 563 repo=repo, user=member_id, perm=perm)
552 564 else: # set for user group
553 565 # check if we have permissions to alter this usergroup
554 566 member_name = UserGroup.get(member_id).users_group_name
555 567 if not check_perms or HasUserGroupPermissionAny(
556 568 *req_perms)(member_name, user=cur_user):
557 569 self.grant_user_group_permission(
558 570 repo=repo, group_name=member_id, perm=perm)
559 571 changes['added'].append({'type': member_type, 'id': member_id,
560 572 'name': member_name, 'new_perm': perm})
561 573 # delete permissions
562 574 for member_id, perm, member_type in perm_deletions:
563 575 member_id = int(member_id)
564 576 if member_type == 'user':
565 577 member_name = User.get(member_id).username
566 578 self.revoke_user_permission(repo=repo, user=member_id)
567 579 else: # set for user group
568 580 # check if we have permissions to alter this usergroup
569 581 member_name = UserGroup.get(member_id).users_group_name
570 582 if not check_perms or HasUserGroupPermissionAny(
571 583 *req_perms)(member_name, user=cur_user):
572 584 self.revoke_user_group_permission(
573 585 repo=repo, group_name=member_id)
574 586
575 587 changes['deleted'].append({'type': member_type, 'id': member_id,
576 588 'name': member_name, 'new_perm': perm})
577 589 return changes
578 590
579 591 def create_fork(self, form_data, cur_user):
580 592 """
581 593 Simple wrapper into executing celery task for fork creation
582 594
583 595 :param form_data:
584 596 :param cur_user:
585 597 """
586 598 from rhodecode.lib.celerylib import tasks, run_task
587 599 return run_task(tasks.create_repo_fork, form_data, cur_user)
588 600
589 601 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
590 602 """
591 603 Delete given repository, forks parameter defines what do do with
592 604 attached forks. Throws AttachedForksError if deleted repo has attached
593 605 forks
594 606
595 607 :param repo:
596 608 :param forks: str 'delete' or 'detach'
597 609 :param fs_remove: remove(archive) repo from filesystem
598 610 """
599 611 if not cur_user:
600 612 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
601 613 repo = self._get_repo(repo)
602 614 if repo:
603 615 if forks == 'detach':
604 616 for r in repo.forks:
605 617 r.fork = None
606 618 self.sa.add(r)
607 619 elif forks == 'delete':
608 620 for r in repo.forks:
609 621 self.delete(r, forks='delete')
610 622 elif [f for f in repo.forks]:
611 623 raise AttachedForksError()
612 624
613 625 old_repo_dict = repo.get_dict()
614 626 events.trigger(events.RepoPreDeleteEvent(repo))
615 627 try:
616 628 self.sa.delete(repo)
617 629 if fs_remove:
618 630 self._delete_filesystem_repo(repo)
619 631 else:
620 632 log.debug('skipping removal from filesystem')
621 633 old_repo_dict.update({
622 634 'deleted_by': cur_user,
623 635 'deleted_on': time.time(),
624 636 })
625 637 log_delete_repository(**old_repo_dict)
626 638 events.trigger(events.RepoDeleteEvent(repo))
627 639 except Exception:
628 640 log.error(traceback.format_exc())
629 641 raise
630 642
631 643 def grant_user_permission(self, repo, user, perm):
632 644 """
633 645 Grant permission for user on given repository, or update existing one
634 646 if found
635 647
636 648 :param repo: Instance of Repository, repository_id, or repository name
637 649 :param user: Instance of User, user_id or username
638 650 :param perm: Instance of Permission, or permission_name
639 651 """
640 652 user = self._get_user(user)
641 653 repo = self._get_repo(repo)
642 654 permission = self._get_perm(perm)
643 655
644 656 # check if we have that permission already
645 657 obj = self.sa.query(UserRepoToPerm) \
646 658 .filter(UserRepoToPerm.user == user) \
647 659 .filter(UserRepoToPerm.repository == repo) \
648 660 .scalar()
649 661 if obj is None:
650 662 # create new !
651 663 obj = UserRepoToPerm()
652 664 obj.repository = repo
653 665 obj.user = user
654 666 obj.permission = permission
655 667 self.sa.add(obj)
656 668 log.debug('Granted perm %s to %s on %s', perm, user, repo)
657 669 action_logger_generic(
658 670 'granted permission: {} to user: {} on repo: {}'.format(
659 671 perm, user, repo), namespace='security.repo')
660 672 return obj
661 673
662 674 def revoke_user_permission(self, repo, user):
663 675 """
664 676 Revoke permission for user on given repository
665 677
666 678 :param repo: Instance of Repository, repository_id, or repository name
667 679 :param user: Instance of User, user_id or username
668 680 """
669 681
670 682 user = self._get_user(user)
671 683 repo = self._get_repo(repo)
672 684
673 685 obj = self.sa.query(UserRepoToPerm) \
674 686 .filter(UserRepoToPerm.repository == repo) \
675 687 .filter(UserRepoToPerm.user == user) \
676 688 .scalar()
677 689 if obj:
678 690 self.sa.delete(obj)
679 691 log.debug('Revoked perm on %s on %s', repo, user)
680 692 action_logger_generic(
681 693 'revoked permission from user: {} on repo: {}'.format(
682 694 user, repo), namespace='security.repo')
683 695
684 696 def grant_user_group_permission(self, repo, group_name, perm):
685 697 """
686 698 Grant permission for user group on given repository, or update
687 699 existing one if found
688 700
689 701 :param repo: Instance of Repository, repository_id, or repository name
690 702 :param group_name: Instance of UserGroup, users_group_id,
691 703 or user group name
692 704 :param perm: Instance of Permission, or permission_name
693 705 """
694 706 repo = self._get_repo(repo)
695 707 group_name = self._get_user_group(group_name)
696 708 permission = self._get_perm(perm)
697 709
698 710 # check if we have that permission already
699 711 obj = self.sa.query(UserGroupRepoToPerm) \
700 712 .filter(UserGroupRepoToPerm.users_group == group_name) \
701 713 .filter(UserGroupRepoToPerm.repository == repo) \
702 714 .scalar()
703 715
704 716 if obj is None:
705 717 # create new
706 718 obj = UserGroupRepoToPerm()
707 719
708 720 obj.repository = repo
709 721 obj.users_group = group_name
710 722 obj.permission = permission
711 723 self.sa.add(obj)
712 724 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
713 725 action_logger_generic(
714 726 'granted permission: {} to usergroup: {} on repo: {}'.format(
715 727 perm, group_name, repo), namespace='security.repo')
716 728
717 729 return obj
718 730
719 731 def revoke_user_group_permission(self, repo, group_name):
720 732 """
721 733 Revoke permission for user group on given repository
722 734
723 735 :param repo: Instance of Repository, repository_id, or repository name
724 736 :param group_name: Instance of UserGroup, users_group_id,
725 737 or user group name
726 738 """
727 739 repo = self._get_repo(repo)
728 740 group_name = self._get_user_group(group_name)
729 741
730 742 obj = self.sa.query(UserGroupRepoToPerm) \
731 743 .filter(UserGroupRepoToPerm.repository == repo) \
732 744 .filter(UserGroupRepoToPerm.users_group == group_name) \
733 745 .scalar()
734 746 if obj:
735 747 self.sa.delete(obj)
736 748 log.debug('Revoked perm to %s on %s', repo, group_name)
737 749 action_logger_generic(
738 750 'revoked permission from usergroup: {} on repo: {}'.format(
739 751 group_name, repo), namespace='security.repo')
740 752
741 753 def delete_stats(self, repo_name):
742 754 """
743 755 removes stats for given repo
744 756
745 757 :param repo_name:
746 758 """
747 759 repo = self._get_repo(repo_name)
748 760 try:
749 761 obj = self.sa.query(Statistics) \
750 762 .filter(Statistics.repository == repo).scalar()
751 763 if obj:
752 764 self.sa.delete(obj)
753 765 except Exception:
754 766 log.error(traceback.format_exc())
755 767 raise
756 768
757 769 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
758 770 field_type='str', field_desc=''):
759 771
760 772 repo = self._get_repo(repo_name)
761 773
762 774 new_field = RepositoryField()
763 775 new_field.repository = repo
764 776 new_field.field_key = field_key
765 777 new_field.field_type = field_type # python type
766 778 new_field.field_value = field_value
767 779 new_field.field_desc = field_desc
768 780 new_field.field_label = field_label
769 781 self.sa.add(new_field)
770 782 return new_field
771 783
772 784 def delete_repo_field(self, repo_name, field_key):
773 785 repo = self._get_repo(repo_name)
774 786 field = RepositoryField.get_by_key_name(field_key, repo)
775 787 if field:
776 788 self.sa.delete(field)
777 789
778 790 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
779 791 clone_uri=None, repo_store_location=None,
780 792 use_global_config=False):
781 793 """
782 794 makes repository on filesystem. It's group aware means it'll create
783 795 a repository within a group, and alter the paths accordingly of
784 796 group location
785 797
786 798 :param repo_name:
787 799 :param alias:
788 800 :param parent:
789 801 :param clone_uri:
790 802 :param repo_store_location:
791 803 """
792 804 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
793 805 from rhodecode.model.scm import ScmModel
794 806
795 807 if Repository.NAME_SEP in repo_name:
796 808 raise ValueError(
797 809 'repo_name must not contain groups got `%s`' % repo_name)
798 810
799 811 if isinstance(repo_group, RepoGroup):
800 812 new_parent_path = os.sep.join(repo_group.full_path_splitted)
801 813 else:
802 814 new_parent_path = repo_group or ''
803 815
804 816 if repo_store_location:
805 817 _paths = [repo_store_location]
806 818 else:
807 819 _paths = [self.repos_path, new_parent_path, repo_name]
808 820 # we need to make it str for mercurial
809 821 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
810 822
811 823 # check if this path is not a repository
812 824 if is_valid_repo(repo_path, self.repos_path):
813 825 raise Exception('This path %s is a valid repository' % repo_path)
814 826
815 827 # check if this path is a group
816 828 if is_valid_repo_group(repo_path, self.repos_path):
817 829 raise Exception('This path %s is a valid group' % repo_path)
818 830
819 831 log.info('creating repo %s in %s from url: `%s`',
820 832 repo_name, safe_unicode(repo_path),
821 833 obfuscate_url_pw(clone_uri))
822 834
823 835 backend = get_backend(repo_type)
824 836
825 837 config_repo = None if use_global_config else repo_name
826 838 if config_repo and new_parent_path:
827 839 config_repo = Repository.NAME_SEP.join(
828 840 (new_parent_path, config_repo))
829 841 config = make_db_config(clear_session=False, repo=config_repo)
830 842 config.set('extensions', 'largefiles', '')
831 843
832 844 # patch and reset hooks section of UI config to not run any
833 845 # hooks on creating remote repo
834 846 config.clear_section('hooks')
835 847
836 848 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
837 849 if repo_type == 'git':
838 850 repo = backend(
839 851 repo_path, config=config, create=True, src_url=clone_uri,
840 852 bare=True)
841 853 else:
842 854 repo = backend(
843 855 repo_path, config=config, create=True, src_url=clone_uri)
844 856
845 857 ScmModel().install_hooks(repo, repo_type=repo_type)
846 858
847 859 log.debug('Created repo %s with %s backend',
848 860 safe_unicode(repo_name), safe_unicode(repo_type))
849 861 return repo
850 862
851 863 def _rename_filesystem_repo(self, old, new):
852 864 """
853 865 renames repository on filesystem
854 866
855 867 :param old: old name
856 868 :param new: new name
857 869 """
858 870 log.info('renaming repo from %s to %s', old, new)
859 871
860 872 old_path = os.path.join(self.repos_path, old)
861 873 new_path = os.path.join(self.repos_path, new)
862 874 if os.path.isdir(new_path):
863 875 raise Exception(
864 876 'Was trying to rename to already existing dir %s' % new_path
865 877 )
866 878 shutil.move(old_path, new_path)
867 879
868 880 def _delete_filesystem_repo(self, repo):
869 881 """
870 882 removes repo from filesystem, the removal is acctually made by
871 883 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
872 884 repository is no longer valid for rhodecode, can be undeleted later on
873 885 by reverting the renames on this repository
874 886
875 887 :param repo: repo object
876 888 """
877 889 rm_path = os.path.join(self.repos_path, repo.repo_name)
878 890 repo_group = repo.group
879 891 log.info("Removing repository %s", rm_path)
880 892 # disable hg/git internal that it doesn't get detected as repo
881 893 alias = repo.repo_type
882 894
883 895 config = make_db_config(clear_session=False)
884 896 config.set('extensions', 'largefiles', '')
885 897 bare = getattr(repo.scm_instance(config=config), 'bare', False)
886 898
887 899 # skip this for bare git repos
888 900 if not bare:
889 901 # disable VCS repo
890 902 vcs_path = os.path.join(rm_path, '.%s' % alias)
891 903 if os.path.exists(vcs_path):
892 904 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
893 905
894 906 _now = datetime.datetime.now()
895 907 _ms = str(_now.microsecond).rjust(6, '0')
896 908 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
897 909 repo.just_name)
898 910 if repo_group:
899 911 # if repository is in group, prefix the removal path with the group
900 912 args = repo_group.full_path_splitted + [_d]
901 913 _d = os.path.join(*args)
902 914
903 915 if os.path.isdir(rm_path):
904 916 shutil.move(rm_path, os.path.join(self.repos_path, _d))
905 917
906 918
907 919 class ReadmeFinder:
908 920 """
909 921 Utility which knows how to find a readme for a specific commit.
910 922
911 923 The main idea is that this is a configurable algorithm. When creating an
912 924 instance you can define parameters, currently only the `default_renderer`.
913 925 Based on this configuration the method :meth:`search` behaves slightly
914 926 different.
915 927 """
916 928
917 929 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
918 930 path_re = re.compile(r'^docs?', re.IGNORECASE)
919 931
920 932 default_priorities = {
921 933 None: 0,
922 934 '.text': 2,
923 935 '.txt': 3,
924 936 '.rst': 1,
925 937 '.rest': 2,
926 938 '.md': 1,
927 939 '.mkdn': 2,
928 940 '.mdown': 3,
929 941 '.markdown': 4,
930 942 }
931 943
932 944 path_priority = {
933 945 'doc': 0,
934 946 'docs': 1,
935 947 }
936 948
937 949 FALLBACK_PRIORITY = 99
938 950
939 951 RENDERER_TO_EXTENSION = {
940 952 'rst': ['.rst', '.rest'],
941 953 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
942 954 }
943 955
944 956 def __init__(self, default_renderer=None):
945 957 self._default_renderer = default_renderer
946 958 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
947 959 default_renderer, [])
948 960
949 961 def search(self, commit, path='/'):
950 962 """
951 963 Find a readme in the given `commit`.
952 964 """
953 965 nodes = commit.get_nodes(path)
954 966 matches = self._match_readmes(nodes)
955 967 matches = self._sort_according_to_priority(matches)
956 968 if matches:
957 969 return matches[0].node
958 970
959 971 paths = self._match_paths(nodes)
960 972 paths = self._sort_paths_according_to_priority(paths)
961 973 for path in paths:
962 974 match = self.search(commit, path=path)
963 975 if match:
964 976 return match
965 977
966 978 return None
967 979
968 980 def _match_readmes(self, nodes):
969 981 for node in nodes:
970 982 if not node.is_file():
971 983 continue
972 984 path = node.path.rsplit('/', 1)[-1]
973 985 match = self.readme_re.match(path)
974 986 if match:
975 987 extension = match.group(1)
976 988 yield ReadmeMatch(node, match, self._priority(extension))
977 989
978 990 def _match_paths(self, nodes):
979 991 for node in nodes:
980 992 if not node.is_dir():
981 993 continue
982 994 match = self.path_re.match(node.path)
983 995 if match:
984 996 yield node.path
985 997
986 998 def _priority(self, extension):
987 999 renderer_priority = (
988 1000 0 if extension in self._renderer_extensions else 1)
989 1001 extension_priority = self.default_priorities.get(
990 1002 extension, self.FALLBACK_PRIORITY)
991 1003 return (renderer_priority, extension_priority)
992 1004
993 1005 def _sort_according_to_priority(self, matches):
994 1006
995 1007 def priority_and_path(match):
996 1008 return (match.priority, match.path)
997 1009
998 1010 return sorted(matches, key=priority_and_path)
999 1011
1000 1012 def _sort_paths_according_to_priority(self, paths):
1001 1013
1002 1014 def priority_and_path(path):
1003 1015 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1004 1016
1005 1017 return sorted(paths, key=priority_and_path)
1006 1018
1007 1019
1008 1020 class ReadmeMatch:
1009 1021
1010 1022 def __init__(self, node, match, priority):
1011 1023 self.node = node
1012 1024 self._match = match
1013 1025 self.priority = priority
1014 1026
1015 1027 @property
1016 1028 def path(self):
1017 1029 return self.node.path
1018 1030
1019 1031 def __repr__(self):
1020 1032 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
@@ -1,99 +1,102 b''
1 1 ## -*- coding: utf-8 -*-
2 2 ##
3 3 ## See also repo_settings.html
4 4 ##
5 5 <%inherit file="/base/base.mako"/>
6 6
7 7 <%def name="title()">
8 8 ${_('%s repository settings') % c.rhodecode_db_repo.repo_name}
9 9 %if c.rhodecode_name:
10 10 &middot; ${h.branding(c.rhodecode_name)}
11 11 %endif
12 12 </%def>
13 13
14 14 <%def name="breadcrumbs_links()">
15 15 ${_('Settings')}
16 16 </%def>
17 17
18 18 <%def name="menu_bar_nav()">
19 19 ${self.menu_items(active='repositories')}
20 20 </%def>
21 21
22 22 <%def name="menu_bar_subnav()">
23 23 ${self.repo_menu(active='options')}
24 24 </%def>
25 25
26 26 <%def name="main_content()">
27 27 % if hasattr(c, 'repo_edit_template'):
28 28 <%include file="${c.repo_edit_template}"/>
29 29 % else:
30 30 <%include file="/admin/repos/repo_edit_${c.active}.mako"/>
31 31 % endif
32 32 </%def>
33 33
34 34
35 35 <%def name="main()">
36 36 <div class="box">
37 37 <div class="title">
38 38 ${self.repo_page_title(c.rhodecode_db_repo)}
39 39 ${self.breadcrumbs()}
40 40 </div>
41 41
42 42 <div class="sidebar-col-wrapper scw-small">
43 43 <div class="sidebar">
44 44 <ul class="nav nav-pills nav-stacked">
45 45 <li class="${'active' if c.active=='settings' else ''}">
46 46 <a href="${h.route_path('edit_repo', repo_name=c.repo_name)}">${_('Settings')}</a>
47 47 </li>
48 48 <li class="${'active' if c.active=='permissions' else ''}">
49 49 <a href="${h.route_path('edit_repo_perms', repo_name=c.repo_name)}">${_('Permissions')}</a>
50 50 </li>
51 51 <li class="${'active' if c.active=='advanced' else ''}">
52 52 <a href="${h.route_path('edit_repo_advanced', repo_name=c.repo_name)}">${_('Advanced')}</a>
53 53 </li>
54 54 <li class="${'active' if c.active=='vcs' else ''}">
55 55 <a href="${h.route_path('edit_repo_vcs', repo_name=c.repo_name)}">${_('VCS')}</a>
56 56 </li>
57 57 <li class="${'active' if c.active=='fields' else ''}">
58 58 <a href="${h.route_path('edit_repo_fields', repo_name=c.repo_name)}">${_('Extra Fields')}</a>
59 59 </li>
60 60 <li class="${'active' if c.active=='issuetracker' else ''}">
61 61 <a href="${h.route_path('edit_repo_issuetracker', repo_name=c.repo_name)}">${_('Issue Tracker')}</a>
62 62 </li>
63 63 <li class="${'active' if c.active=='caches' else ''}">
64 64 <a href="${h.route_path('edit_repo_caches', repo_name=c.repo_name)}">${_('Caches')}</a>
65 65 </li>
66 66 %if c.rhodecode_db_repo.repo_type != 'svn':
67 67 <li class="${'active' if c.active=='remote' else ''}">
68 68 <a href="${h.route_path('edit_repo_remote', repo_name=c.repo_name)}">${_('Remote')}</a>
69 69 </li>
70 70 %endif
71 71 <li class="${'active' if c.active=='statistics' else ''}">
72 72 <a href="${h.route_path('edit_repo_statistics', repo_name=c.repo_name)}">${_('Statistics')}</a>
73 73 </li>
74 74 <li class="${'active' if c.active=='integrations' else ''}">
75 75 <a href="${h.route_path('repo_integrations_home', repo_name=c.repo_name)}">${_('Integrations')}</a>
76 76 </li>
77 77 %if c.rhodecode_db_repo.repo_type != 'svn':
78 78 <li class="${'active' if c.active=='reviewers' else ''}">
79 79 <a href="${h.route_path('repo_reviewers', repo_name=c.repo_name)}">${_('Reviewer Rules')}</a>
80 80 </li>
81 81 %endif
82 82 <li class="${'active' if c.active=='maintenance' else ''}">
83 83 <a href="${h.route_path('edit_repo_maintenance', repo_name=c.repo_name)}">${_('Maintenance')}</a>
84 84 </li>
85 85 <li class="${'active' if c.active=='strip' else ''}">
86 86 <a href="${h.route_path('edit_repo_strip', repo_name=c.repo_name)}">${_('Strip')}</a>
87 87 </li>
88 <li class="${'active' if c.active=='audit' else ''}">
89 <a href="${h.route_path('edit_repo_audit_logs', repo_name=c.repo_name)}">${_('Audit logs')}</a>
90 </li>
88 91
89 92 </ul>
90 93 </div>
91 94
92 95 <div class="main-content-full-width">
93 96 ${self.main_content()}
94 97 </div>
95 98
96 99 </div>
97 100 </div>
98 101
99 102 </%def> No newline at end of file
@@ -1,57 +1,57 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('%s user settings') % c.user.username}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 13 &raquo;
14 14 ${h.link_to(_('Users'),h.route_path('users'))}
15 15 &raquo;
16 16 % if c.user.active:
17 17 ${c.user.username}
18 18 % else:
19 19 <strike title="${_('This user is set as disabled')}">${c.user.username}</strike>
20 20 % endif
21 21
22 22 </%def>
23 23
24 24 <%def name="menu_bar_nav()">
25 25 ${self.menu_items(active='admin')}
26 26 </%def>
27 27
28 28 <%def name="main()">
29 29 <div class="box user_settings">
30 30 <div class="title">
31 31 ${self.breadcrumbs()}
32 32 </div>
33 33
34 34 ##main
35 35 <div class="sidebar-col-wrapper">
36 36 <div class="sidebar">
37 37 <ul class="nav nav-pills nav-stacked">
38 38 <li class="${'active' if c.active=='profile' else ''}"><a href="${h.route_path('user_edit', user_id=c.user.user_id)}">${_('User Profile')}</a></li>
39 39 <li class="${'active' if c.active=='auth_tokens' else ''}"><a href="${h.route_path('edit_user_auth_tokens', user_id=c.user.user_id)}">${_('Auth tokens')}</a></li>
40 40 <li class="${'active' if c.active in ['ssh_keys','ssh_keys_generate'] else ''}"><a href="${h.route_path('edit_user_ssh_keys', user_id=c.user.user_id)}">${_('SSH Keys')}</a></li>
41 41 <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.route_path('user_edit_advanced', user_id=c.user.user_id)}">${_('Advanced')}</a></li>
42 42 <li class="${'active' if c.active=='global_perms' else ''}"><a href="${h.route_path('user_edit_global_perms', user_id=c.user.user_id)}">${_('Global permissions')}</a></li>
43 43 <li class="${'active' if c.active=='perms_summary' else ''}"><a href="${h.route_path('edit_user_perms_summary', user_id=c.user.user_id)}">${_('Permissions summary')}</a></li>
44 44 <li class="${'active' if c.active=='emails' else ''}"><a href="${h.route_path('edit_user_emails', user_id=c.user.user_id)}">${_('Emails')}</a></li>
45 45 <li class="${'active' if c.active=='ips' else ''}"><a href="${h.route_path('edit_user_ips', user_id=c.user.user_id)}">${_('Ip Whitelist')}</a></li>
46 46 <li class="${'active' if c.active=='groups' else ''}"><a href="${h.route_path('edit_user_groups_management', user_id=c.user.user_id)}">${_('User Groups Management')}</a></li>
47 <li class="${'active' if c.active=='audit' else ''}"><a href="${h.route_path('edit_user_audit_logs', user_id=c.user.user_id)}">${_('User audit')}</a></li>
47 <li class="${'active' if c.active=='audit' else ''}"><a href="${h.route_path('edit_user_audit_logs', user_id=c.user.user_id)}">${_('Audit logs')}</a></li>
48 48 </ul>
49 49 </div>
50 50
51 51 <div class="main-content-full-width">
52 52 <%include file="/admin/users/user_edit_${c.active}.mako"/>
53 53 </div>
54 54 </div>
55 55 </div>
56 56
57 57 </%def>
General Comments 0
You need to be logged in to leave comments. Login now