##// END OF EJS Templates
routing: improve bad character detection on repo names
super-admin -
r4843:05c94e8b default
parent child Browse files
Show More
@@ -1,829 +1,839 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import time
22 22 import logging
23 23 import operator
24 24
25 25 from pyramid import compat
26 26 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
27 27
28 28 from rhodecode.lib import helpers as h, diffs, rc_cache
29 29 from rhodecode.lib.utils import repo_name_slug
30 30 from rhodecode.lib.utils2 import (
31 31 StrictAttributeDict, str2bool, safe_int, datetime_to_time, safe_unicode)
32 32 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
33 33 from rhodecode.lib.vcs.backends.base import EmptyCommit
34 34 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
35 35 from rhodecode.model import repo
36 36 from rhodecode.model import repo_group
37 37 from rhodecode.model import user_group
38 38 from rhodecode.model import user
39 39 from rhodecode.model.db import User
40 40 from rhodecode.model.scm import ScmModel
41 41 from rhodecode.model.settings import VcsSettingsModel, IssueTrackerSettingsModel
42 42 from rhodecode.model.repo import ReadmeFinder
43 43
44 44 log = logging.getLogger(__name__)
45 45
46 46
47 47 ADMIN_PREFIX = '/_admin'
48 48 STATIC_FILE_PREFIX = '/_static'
49 49
50 50 URL_NAME_REQUIREMENTS = {
51 51 # group name can have a slash in them, but they must not end with a slash
52 52 'group_name': r'.*?[^/]',
53 53 'repo_group_name': r'.*?[^/]',
54 54 # repo names can have a slash in them, but they must not end with a slash
55 55 'repo_name': r'.*?[^/]',
56 56 # file path eats up everything at the end
57 57 'f_path': r'.*',
58 58 # reference types
59 59 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
60 60 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
61 61 }
62 62
63 63
64 64 def add_route_with_slash(config,name, pattern, **kw):
65 65 config.add_route(name, pattern, **kw)
66 66 if not pattern.endswith('/'):
67 67 config.add_route(name + '_slash', pattern + '/', **kw)
68 68
69 69
70 70 def add_route_requirements(route_path, requirements=None):
71 71 """
72 72 Adds regex requirements to pyramid routes using a mapping dict
73 73 e.g::
74 74 add_route_requirements('{repo_name}/settings')
75 75 """
76 76 requirements = requirements or URL_NAME_REQUIREMENTS
77 77 for key, regex in requirements.items():
78 78 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
79 79 return route_path
80 80
81 81
82 82 def get_format_ref_id(repo):
83 83 """Returns a `repo` specific reference formatter function"""
84 84 if h.is_svn(repo):
85 85 return _format_ref_id_svn
86 86 else:
87 87 return _format_ref_id
88 88
89 89
90 90 def _format_ref_id(name, raw_id):
91 91 """Default formatting of a given reference `name`"""
92 92 return name
93 93
94 94
95 95 def _format_ref_id_svn(name, raw_id):
96 96 """Special way of formatting a reference for Subversion including path"""
97 97 return '%s@%s' % (name, raw_id)
98 98
99 99
100 100 class TemplateArgs(StrictAttributeDict):
101 101 pass
102 102
103 103
104 104 class BaseAppView(object):
105 105
106 106 def __init__(self, context, request):
107 107 self.request = request
108 108 self.context = context
109 109 self.session = request.session
110 110 if not hasattr(request, 'user'):
111 111 # NOTE(marcink): edge case, we ended up in matched route
112 112 # but probably of web-app context, e.g API CALL/VCS CALL
113 113 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
114 114 log.warning('Unable to process request `%s` in this scope', request)
115 115 raise HTTPBadRequest()
116 116
117 117 self._rhodecode_user = request.user # auth user
118 118 self._rhodecode_db_user = self._rhodecode_user.get_instance()
119 119 self._maybe_needs_password_change(
120 120 request.matched_route.name, self._rhodecode_db_user)
121 121
122 122 def _maybe_needs_password_change(self, view_name, user_obj):
123 123
124 124 dont_check_views = [
125 125 'channelstream_connect'
126 126 ]
127 127 if view_name in dont_check_views:
128 128 return
129 129
130 130 log.debug('Checking if user %s needs password change on view %s',
131 131 user_obj, view_name)
132 132
133 133 skip_user_views = [
134 134 'logout', 'login',
135 135 'my_account_password', 'my_account_password_update'
136 136 ]
137 137
138 138 if not user_obj:
139 139 return
140 140
141 141 if user_obj.username == User.DEFAULT_USER:
142 142 return
143 143
144 144 now = time.time()
145 145 should_change = user_obj.user_data.get('force_password_change')
146 146 change_after = safe_int(should_change) or 0
147 147 if should_change and now > change_after:
148 148 log.debug('User %s requires password change', user_obj)
149 149 h.flash('You are required to change your password', 'warning',
150 150 ignore_duplicate=True)
151 151
152 152 if view_name not in skip_user_views:
153 153 raise HTTPFound(
154 154 self.request.route_path('my_account_password'))
155 155
156 156 def _log_creation_exception(self, e, repo_name):
157 157 _ = self.request.translate
158 158 reason = None
159 159 if len(e.args) == 2:
160 160 reason = e.args[1]
161 161
162 162 if reason == 'INVALID_CERTIFICATE':
163 163 log.exception(
164 164 'Exception creating a repository: invalid certificate')
165 165 msg = (_('Error creating repository %s: invalid certificate')
166 166 % repo_name)
167 167 else:
168 168 log.exception("Exception creating a repository")
169 169 msg = (_('Error creating repository %s')
170 170 % repo_name)
171 171 return msg
172 172
173 173 def _get_local_tmpl_context(self, include_app_defaults=True):
174 174 c = TemplateArgs()
175 175 c.auth_user = self.request.user
176 176 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
177 177 c.rhodecode_user = self.request.user
178 178
179 179 if include_app_defaults:
180 180 from rhodecode.lib.base import attach_context_attributes
181 181 attach_context_attributes(c, self.request, self.request.user.user_id)
182 182
183 183 c.is_super_admin = c.auth_user.is_admin
184 184
185 185 c.can_create_repo = c.is_super_admin
186 186 c.can_create_repo_group = c.is_super_admin
187 187 c.can_create_user_group = c.is_super_admin
188 188
189 189 c.is_delegated_admin = False
190 190
191 191 if not c.auth_user.is_default and not c.is_super_admin:
192 192 c.can_create_repo = h.HasPermissionAny('hg.create.repository')(
193 193 user=self.request.user)
194 194 repositories = c.auth_user.repositories_admin or c.can_create_repo
195 195
196 196 c.can_create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')(
197 197 user=self.request.user)
198 198 repository_groups = c.auth_user.repository_groups_admin or c.can_create_repo_group
199 199
200 200 c.can_create_user_group = h.HasPermissionAny('hg.usergroup.create.true')(
201 201 user=self.request.user)
202 202 user_groups = c.auth_user.user_groups_admin or c.can_create_user_group
203 203 # delegated admin can create, or manage some objects
204 204 c.is_delegated_admin = repositories or repository_groups or user_groups
205 205 return c
206 206
207 207 def _get_template_context(self, tmpl_args, **kwargs):
208 208
209 209 local_tmpl_args = {
210 210 'defaults': {},
211 211 'errors': {},
212 212 'c': tmpl_args
213 213 }
214 214 local_tmpl_args.update(kwargs)
215 215 return local_tmpl_args
216 216
217 217 def load_default_context(self):
218 218 """
219 219 example:
220 220
221 221 def load_default_context(self):
222 222 c = self._get_local_tmpl_context()
223 223 c.custom_var = 'foobar'
224 224
225 225 return c
226 226 """
227 227 raise NotImplementedError('Needs implementation in view class')
228 228
229 229
230 230 class RepoAppView(BaseAppView):
231 231
232 232 def __init__(self, context, request):
233 233 super(RepoAppView, self).__init__(context, request)
234 234 self.db_repo = request.db_repo
235 235 self.db_repo_name = self.db_repo.repo_name
236 236 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
237 237 self.db_repo_artifacts = ScmModel().get_artifacts(self.db_repo)
238 238 self.db_repo_patterns = IssueTrackerSettingsModel(repo=self.db_repo)
239 239
240 240 def _handle_missing_requirements(self, error):
241 241 log.error(
242 242 'Requirements are missing for repository %s: %s',
243 243 self.db_repo_name, safe_unicode(error))
244 244
245 245 def _get_local_tmpl_context(self, include_app_defaults=True):
246 246 _ = self.request.translate
247 247 c = super(RepoAppView, self)._get_local_tmpl_context(
248 248 include_app_defaults=include_app_defaults)
249 249
250 250 # register common vars for this type of view
251 251 c.rhodecode_db_repo = self.db_repo
252 252 c.repo_name = self.db_repo_name
253 253 c.repository_pull_requests = self.db_repo_pull_requests
254 254 c.repository_artifacts = self.db_repo_artifacts
255 255 c.repository_is_user_following = ScmModel().is_following_repo(
256 256 self.db_repo_name, self._rhodecode_user.user_id)
257 257 self.path_filter = PathFilter(None)
258 258
259 259 c.repository_requirements_missing = {}
260 260 try:
261 261 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
262 262 # NOTE(marcink):
263 263 # comparison to None since if it's an object __bool__ is expensive to
264 264 # calculate
265 265 if self.rhodecode_vcs_repo is not None:
266 266 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
267 267 c.auth_user.username)
268 268 self.path_filter = PathFilter(path_perms)
269 269 except RepositoryRequirementError as e:
270 270 c.repository_requirements_missing = {'error': str(e)}
271 271 self._handle_missing_requirements(e)
272 272 self.rhodecode_vcs_repo = None
273 273
274 274 c.path_filter = self.path_filter # used by atom_feed_entry.mako
275 275
276 276 if self.rhodecode_vcs_repo is None:
277 277 # unable to fetch this repo as vcs instance, report back to user
278 278 h.flash(_(
279 279 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
280 280 "Please check if it exist, or is not damaged.") %
281 281 {'repo_name': c.repo_name},
282 282 category='error', ignore_duplicate=True)
283 283 if c.repository_requirements_missing:
284 284 route = self.request.matched_route.name
285 285 if route.startswith(('edit_repo', 'repo_summary')):
286 286 # allow summary and edit repo on missing requirements
287 287 return c
288 288
289 289 raise HTTPFound(
290 290 h.route_path('repo_summary', repo_name=self.db_repo_name))
291 291
292 292 else: # redirect if we don't show missing requirements
293 293 raise HTTPFound(h.route_path('home'))
294 294
295 295 c.has_origin_repo_read_perm = False
296 296 if self.db_repo.fork:
297 297 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
298 298 'repository.write', 'repository.read', 'repository.admin')(
299 299 self.db_repo.fork.repo_name, 'summary fork link')
300 300
301 301 return c
302 302
303 303 def _get_f_path_unchecked(self, matchdict, default=None):
304 304 """
305 305 Should only be used by redirects, everything else should call _get_f_path
306 306 """
307 307 f_path = matchdict.get('f_path')
308 308 if f_path:
309 309 # fix for multiple initial slashes that causes errors for GIT
310 310 return f_path.lstrip('/')
311 311
312 312 return default
313 313
314 314 def _get_f_path(self, matchdict, default=None):
315 315 f_path_match = self._get_f_path_unchecked(matchdict, default)
316 316 return self.path_filter.assert_path_permissions(f_path_match)
317 317
318 318 def _get_general_setting(self, target_repo, settings_key, default=False):
319 319 settings_model = VcsSettingsModel(repo=target_repo)
320 320 settings = settings_model.get_general_settings()
321 321 return settings.get(settings_key, default)
322 322
323 323 def _get_repo_setting(self, target_repo, settings_key, default=False):
324 324 settings_model = VcsSettingsModel(repo=target_repo)
325 325 settings = settings_model.get_repo_settings_inherited()
326 326 return settings.get(settings_key, default)
327 327
328 328 def _get_readme_data(self, db_repo, renderer_type, commit_id=None, path='/'):
329 329 log.debug('Looking for README file at path %s', path)
330 330 if commit_id:
331 331 landing_commit_id = commit_id
332 332 else:
333 333 landing_commit = db_repo.get_landing_commit()
334 334 if isinstance(landing_commit, EmptyCommit):
335 335 return None, None
336 336 landing_commit_id = landing_commit.raw_id
337 337
338 338 cache_namespace_uid = 'cache_repo.{}'.format(db_repo.repo_id)
339 339 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
340 340 start = time.time()
341 341
342 342 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
343 343 def generate_repo_readme(repo_id, _commit_id, _repo_name, _readme_search_path, _renderer_type):
344 344 readme_data = None
345 345 readme_filename = None
346 346
347 347 commit = db_repo.get_commit(_commit_id)
348 348 log.debug("Searching for a README file at commit %s.", _commit_id)
349 349 readme_node = ReadmeFinder(_renderer_type).search(commit, path=_readme_search_path)
350 350
351 351 if readme_node:
352 352 log.debug('Found README node: %s', readme_node)
353 353 relative_urls = {
354 354 'raw': h.route_path(
355 355 'repo_file_raw', repo_name=_repo_name,
356 356 commit_id=commit.raw_id, f_path=readme_node.path),
357 357 'standard': h.route_path(
358 358 'repo_files', repo_name=_repo_name,
359 359 commit_id=commit.raw_id, f_path=readme_node.path),
360 360 }
361 361 readme_data = self._render_readme_or_none(commit, readme_node, relative_urls)
362 362 readme_filename = readme_node.unicode_path
363 363
364 364 return readme_data, readme_filename
365 365
366 366 readme_data, readme_filename = generate_repo_readme(
367 367 db_repo.repo_id, landing_commit_id, db_repo.repo_name, path, renderer_type,)
368 368 compute_time = time.time() - start
369 369 log.debug('Repo README for path %s generated and computed in %.4fs',
370 370 path, compute_time)
371 371 return readme_data, readme_filename
372 372
373 373 def _render_readme_or_none(self, commit, readme_node, relative_urls):
374 374 log.debug('Found README file `%s` rendering...', readme_node.path)
375 375 renderer = MarkupRenderer()
376 376 try:
377 377 html_source = renderer.render(
378 378 readme_node.content, filename=readme_node.path)
379 379 if relative_urls:
380 380 return relative_links(html_source, relative_urls)
381 381 return html_source
382 382 except Exception:
383 383 log.exception(
384 384 "Exception while trying to render the README")
385 385
386 386 def get_recache_flag(self):
387 387 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
388 388 flag_val = self.request.GET.get(flag_name)
389 389 if str2bool(flag_val):
390 390 return True
391 391 return False
392 392
393 393 def get_commit_preload_attrs(cls):
394 394 pre_load = ['author', 'branch', 'date', 'message', 'parents',
395 395 'obsolete', 'phase', 'hidden']
396 396 return pre_load
397 397
398 398
399 399 class PathFilter(object):
400 400
401 401 # Expects and instance of BasePathPermissionChecker or None
402 402 def __init__(self, permission_checker):
403 403 self.permission_checker = permission_checker
404 404
405 405 def assert_path_permissions(self, path):
406 406 if self.path_access_allowed(path):
407 407 return path
408 408 raise HTTPForbidden()
409 409
410 410 def path_access_allowed(self, path):
411 411 log.debug('Checking ACL permissions for PathFilter for `%s`', path)
412 412 if self.permission_checker:
413 413 has_access = path and self.permission_checker.has_access(path)
414 414 log.debug('ACL Permissions checker enabled, ACL Check has_access: %s', has_access)
415 415 return has_access
416 416
417 417 log.debug('ACL permissions checker not enabled, skipping...')
418 418 return True
419 419
420 420 def filter_patchset(self, patchset):
421 421 if not self.permission_checker or not patchset:
422 422 return patchset, False
423 423 had_filtered = False
424 424 filtered_patchset = []
425 425 for patch in patchset:
426 426 filename = patch.get('filename', None)
427 427 if not filename or self.permission_checker.has_access(filename):
428 428 filtered_patchset.append(patch)
429 429 else:
430 430 had_filtered = True
431 431 if had_filtered:
432 432 if isinstance(patchset, diffs.LimitedDiffContainer):
433 433 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
434 434 return filtered_patchset, True
435 435 else:
436 436 return patchset, False
437 437
438 438 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
439 439 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
440 440 result = diffset.render_patchset(
441 441 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
442 442 result.has_hidden_changes = has_hidden_changes
443 443 return result
444 444
445 445 def get_raw_patch(self, diff_processor):
446 446 if self.permission_checker is None:
447 447 return diff_processor.as_raw()
448 448 elif self.permission_checker.has_full_access:
449 449 return diff_processor.as_raw()
450 450 else:
451 451 return '# Repository has user-specific filters, raw patch generation is disabled.'
452 452
453 453 @property
454 454 def is_enabled(self):
455 455 return self.permission_checker is not None
456 456
457 457
458 458 class RepoGroupAppView(BaseAppView):
459 459 def __init__(self, context, request):
460 460 super(RepoGroupAppView, self).__init__(context, request)
461 461 self.db_repo_group = request.db_repo_group
462 462 self.db_repo_group_name = self.db_repo_group.group_name
463 463
464 464 def _get_local_tmpl_context(self, include_app_defaults=True):
465 465 _ = self.request.translate
466 466 c = super(RepoGroupAppView, self)._get_local_tmpl_context(
467 467 include_app_defaults=include_app_defaults)
468 468 c.repo_group = self.db_repo_group
469 469 return c
470 470
471 471 def _revoke_perms_on_yourself(self, form_result):
472 472 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
473 473 form_result['perm_updates'])
474 474 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
475 475 form_result['perm_additions'])
476 476 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
477 477 form_result['perm_deletions'])
478 478 admin_perm = 'group.admin'
479 479 if _updates and _updates[0][1] != admin_perm or \
480 480 _additions and _additions[0][1] != admin_perm or \
481 481 _deletions and _deletions[0][1] != admin_perm:
482 482 return True
483 483 return False
484 484
485 485
486 486 class UserGroupAppView(BaseAppView):
487 487 def __init__(self, context, request):
488 488 super(UserGroupAppView, self).__init__(context, request)
489 489 self.db_user_group = request.db_user_group
490 490 self.db_user_group_name = self.db_user_group.users_group_name
491 491
492 492
493 493 class UserAppView(BaseAppView):
494 494 def __init__(self, context, request):
495 495 super(UserAppView, self).__init__(context, request)
496 496 self.db_user = request.db_user
497 497 self.db_user_id = self.db_user.user_id
498 498
499 499 _ = self.request.translate
500 500 if not request.db_user_supports_default:
501 501 if self.db_user.username == User.DEFAULT_USER:
502 502 h.flash(_("Editing user `{}` is disabled.".format(
503 503 User.DEFAULT_USER)), category='warning')
504 504 raise HTTPFound(h.route_path('users'))
505 505
506 506
507 507 class DataGridAppView(object):
508 508 """
509 509 Common class to have re-usable grid rendering components
510 510 """
511 511
512 512 def _extract_ordering(self, request, column_map=None):
513 513 column_map = column_map or {}
514 514 column_index = safe_int(request.GET.get('order[0][column]'))
515 515 order_dir = request.GET.get(
516 516 'order[0][dir]', 'desc')
517 517 order_by = request.GET.get(
518 518 'columns[%s][data][sort]' % column_index, 'name_raw')
519 519
520 520 # translate datatable to DB columns
521 521 order_by = column_map.get(order_by) or order_by
522 522
523 523 search_q = request.GET.get('search[value]')
524 524 return search_q, order_by, order_dir
525 525
526 526 def _extract_chunk(self, request):
527 527 start = safe_int(request.GET.get('start'), 0)
528 528 length = safe_int(request.GET.get('length'), 25)
529 529 draw = safe_int(request.GET.get('draw'))
530 530 return draw, start, length
531 531
532 532 def _get_order_col(self, order_by, model):
533 533 if isinstance(order_by, compat.string_types):
534 534 try:
535 535 return operator.attrgetter(order_by)(model)
536 536 except AttributeError:
537 537 return None
538 538 else:
539 539 return order_by
540 540
541 541
542 542 class BaseReferencesView(RepoAppView):
543 543 """
544 544 Base for reference view for branches, tags and bookmarks.
545 545 """
546 546 def load_default_context(self):
547 547 c = self._get_local_tmpl_context()
548 548 return c
549 549
550 550 def load_refs_context(self, ref_items, partials_template):
551 551 _render = self.request.get_partial_renderer(partials_template)
552 552 pre_load = ["author", "date", "message", "parents"]
553 553
554 554 is_svn = h.is_svn(self.rhodecode_vcs_repo)
555 555 is_hg = h.is_hg(self.rhodecode_vcs_repo)
556 556
557 557 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
558 558
559 559 closed_refs = {}
560 560 if is_hg:
561 561 closed_refs = self.rhodecode_vcs_repo.branches_closed
562 562
563 563 data = []
564 564 for ref_name, commit_id in ref_items:
565 565 commit = self.rhodecode_vcs_repo.get_commit(
566 566 commit_id=commit_id, pre_load=pre_load)
567 567 closed = ref_name in closed_refs
568 568
569 569 # TODO: johbo: Unify generation of reference links
570 570 use_commit_id = '/' in ref_name or is_svn
571 571
572 572 if use_commit_id:
573 573 files_url = h.route_path(
574 574 'repo_files',
575 575 repo_name=self.db_repo_name,
576 576 f_path=ref_name if is_svn else '',
577 577 commit_id=commit_id,
578 578 _query=dict(at=ref_name)
579 579 )
580 580
581 581 else:
582 582 files_url = h.route_path(
583 583 'repo_files',
584 584 repo_name=self.db_repo_name,
585 585 f_path=ref_name if is_svn else '',
586 586 commit_id=ref_name,
587 587 _query=dict(at=ref_name)
588 588 )
589 589
590 590 data.append({
591 591 "name": _render('name', ref_name, files_url, closed),
592 592 "name_raw": ref_name,
593 593 "date": _render('date', commit.date),
594 594 "date_raw": datetime_to_time(commit.date),
595 595 "author": _render('author', commit.author),
596 596 "commit": _render(
597 597 'commit', commit.message, commit.raw_id, commit.idx),
598 598 "commit_raw": commit.idx,
599 599 "compare": _render(
600 600 'compare', format_ref_id(ref_name, commit.raw_id)),
601 601 })
602 602
603 603 return data
604 604
605 605
606 606 class RepoRoutePredicate(object):
607 607 def __init__(self, val, config):
608 608 self.val = val
609 609
610 610 def text(self):
611 611 return 'repo_route = %s' % self.val
612 612
613 613 phash = text
614 614
615 615 def __call__(self, info, request):
616 616 if hasattr(request, 'vcs_call'):
617 617 # skip vcs calls
618 618 return
619 619
620 620 repo_name = info['match']['repo_name']
621 if repo_name != repo_name_slug(repo_name):
621
622 repo_name_parts = repo_name.split('/')
623 repo_slugs = [x for x in map(lambda x: repo_name_slug(x), repo_name_parts)]
624
625 if repo_name_parts != repo_slugs:
622 626 # short-skip if the repo-name doesn't follow slug rule
627 log.warning('repo_name: %s is different than slug %s', repo_name_parts, repo_slugs)
623 628 return False
624 629
625 630 repo_model = repo.RepoModel()
626 631
627 632 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
628 633
629 634 def redirect_if_creating(route_info, db_repo):
630 635 skip_views = ['edit_repo_advanced_delete']
631 636 route = route_info['route']
632 637 # we should skip delete view so we can actually "remove" repositories
633 638 # if they get stuck in creating state.
634 639 if route.name in skip_views:
635 640 return
636 641
637 642 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
638 643 repo_creating_url = request.route_path(
639 644 'repo_creating', repo_name=db_repo.repo_name)
640 645 raise HTTPFound(repo_creating_url)
641 646
642 647 if by_name_match:
643 648 # register this as request object we can re-use later
644 649 request.db_repo = by_name_match
645 650 redirect_if_creating(info, by_name_match)
646 651 return True
647 652
648 653 by_id_match = repo_model.get_repo_by_id(repo_name)
649 654 if by_id_match:
650 655 request.db_repo = by_id_match
651 656 redirect_if_creating(info, by_id_match)
652 657 return True
653 658
654 659 return False
655 660
656 661
657 662 class RepoForbidArchivedRoutePredicate(object):
658 663 def __init__(self, val, config):
659 664 self.val = val
660 665
661 666 def text(self):
662 667 return 'repo_forbid_archived = %s' % self.val
663 668
664 669 phash = text
665 670
666 671 def __call__(self, info, request):
667 672 _ = request.translate
668 673 rhodecode_db_repo = request.db_repo
669 674
670 675 log.debug(
671 676 '%s checking if archived flag for repo for %s',
672 677 self.__class__.__name__, rhodecode_db_repo.repo_name)
673 678
674 679 if rhodecode_db_repo.archived:
675 680 log.warning('Current view is not supported for archived repo:%s',
676 681 rhodecode_db_repo.repo_name)
677 682
678 683 h.flash(
679 684 h.literal(_('Action not supported for archived repository.')),
680 685 category='warning')
681 686 summary_url = request.route_path(
682 687 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
683 688 raise HTTPFound(summary_url)
684 689 return True
685 690
686 691
687 692 class RepoTypeRoutePredicate(object):
688 693 def __init__(self, val, config):
689 694 self.val = val or ['hg', 'git', 'svn']
690 695
691 696 def text(self):
692 697 return 'repo_accepted_type = %s' % self.val
693 698
694 699 phash = text
695 700
696 701 def __call__(self, info, request):
697 702 if hasattr(request, 'vcs_call'):
698 703 # skip vcs calls
699 704 return
700 705
701 706 rhodecode_db_repo = request.db_repo
702 707
703 708 log.debug(
704 709 '%s checking repo type for %s in %s',
705 710 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
706 711
707 712 if rhodecode_db_repo.repo_type in self.val:
708 713 return True
709 714 else:
710 715 log.warning('Current view is not supported for repo type:%s',
711 716 rhodecode_db_repo.repo_type)
712 717 return False
713 718
714 719
715 720 class RepoGroupRoutePredicate(object):
716 721 def __init__(self, val, config):
717 722 self.val = val
718 723
719 724 def text(self):
720 725 return 'repo_group_route = %s' % self.val
721 726
722 727 phash = text
723 728
724 729 def __call__(self, info, request):
725 730 if hasattr(request, 'vcs_call'):
726 731 # skip vcs calls
727 732 return
728 733
729 734 repo_group_name = info['match']['repo_group_name']
730 if repo_group_name != repo_name_slug(repo_group_name):
735
736 repo_group_name_parts = repo_group_name.split('/')
737 repo_group_slugs = [x for x in map(lambda x: repo_name_slug(x), repo_group_name_parts)]
738 if repo_group_name_parts != repo_group_slugs:
739 # short-skip if the repo-name doesn't follow slug rule
740 log.warning('repo_group_name: %s is different than slug %s', repo_group_name_parts, repo_group_slugs)
731 741 return False
732 742
733 743 repo_group_model = repo_group.RepoGroupModel()
734 744 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
735 745
736 746 if by_name_match:
737 747 # register this as request object we can re-use later
738 748 request.db_repo_group = by_name_match
739 749 return True
740 750
741 751 return False
742 752
743 753
744 754 class UserGroupRoutePredicate(object):
745 755 def __init__(self, val, config):
746 756 self.val = val
747 757
748 758 def text(self):
749 759 return 'user_group_route = %s' % self.val
750 760
751 761 phash = text
752 762
753 763 def __call__(self, info, request):
754 764 if hasattr(request, 'vcs_call'):
755 765 # skip vcs calls
756 766 return
757 767
758 768 user_group_id = info['match']['user_group_id']
759 769 user_group_model = user_group.UserGroup()
760 770 by_id_match = user_group_model.get(user_group_id, cache=False)
761 771
762 772 if by_id_match:
763 773 # register this as request object we can re-use later
764 774 request.db_user_group = by_id_match
765 775 return True
766 776
767 777 return False
768 778
769 779
770 780 class UserRoutePredicateBase(object):
771 781 supports_default = None
772 782
773 783 def __init__(self, val, config):
774 784 self.val = val
775 785
776 786 def text(self):
777 787 raise NotImplementedError()
778 788
779 789 def __call__(self, info, request):
780 790 if hasattr(request, 'vcs_call'):
781 791 # skip vcs calls
782 792 return
783 793
784 794 user_id = info['match']['user_id']
785 795 user_model = user.User()
786 796 by_id_match = user_model.get(user_id, cache=False)
787 797
788 798 if by_id_match:
789 799 # register this as request object we can re-use later
790 800 request.db_user = by_id_match
791 801 request.db_user_supports_default = self.supports_default
792 802 return True
793 803
794 804 return False
795 805
796 806
797 807 class UserRoutePredicate(UserRoutePredicateBase):
798 808 supports_default = False
799 809
800 810 def text(self):
801 811 return 'user_route = %s' % self.val
802 812
803 813 phash = text
804 814
805 815
806 816 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
807 817 supports_default = True
808 818
809 819 def text(self):
810 820 return 'user_with_default_route = %s' % self.val
811 821
812 822 phash = text
813 823
814 824
815 825 def includeme(config):
816 826 config.add_route_predicate(
817 827 'repo_route', RepoRoutePredicate)
818 828 config.add_route_predicate(
819 829 'repo_accepted_types', RepoTypeRoutePredicate)
820 830 config.add_route_predicate(
821 831 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
822 832 config.add_route_predicate(
823 833 'repo_group_route', RepoGroupRoutePredicate)
824 834 config.add_route_predicate(
825 835 'user_group_route', UserGroupRoutePredicate)
826 836 config.add_route_predicate(
827 837 'user_route_with_default', UserRouteWithDefaultPredicate)
828 838 config.add_route_predicate(
829 839 'user_route', UserRoutePredicate)
@@ -1,800 +1,800 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Utilities library for RhodeCode
23 23 """
24 24
25 25 import datetime
26 26 import decorator
27 27 import json
28 28 import logging
29 29 import os
30 30 import re
31 31 import sys
32 32 import shutil
33 33 import socket
34 34 import tempfile
35 35 import traceback
36 36 import tarfile
37 37 import warnings
38 38 import hashlib
39 39 from os.path import join as jn
40 40
41 41 import paste
42 42 import pkg_resources
43 43 from webhelpers2.text import collapse, remove_formatting
44 44 from mako import exceptions
45 45 from pyramid.threadlocal import get_current_registry
46 46 from rhodecode.lib.request import Request
47 47
48 48 from rhodecode.lib.vcs.backends.base import Config
49 49 from rhodecode.lib.vcs.exceptions import VCSError
50 50 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
51 51 from rhodecode.lib.utils2 import (
52 52 safe_str, safe_unicode, get_current_rhodecode_user, md5, sha1)
53 53 from rhodecode.model import meta
54 54 from rhodecode.model.db import (
55 55 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
56 56 from rhodecode.model.meta import Session
57 57
58 58
59 59 log = logging.getLogger(__name__)
60 60
61 61 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
62 62
63 63 # String which contains characters that are not allowed in slug names for
64 64 # repositories or repository groups. It is properly escaped to use it in
65 65 # regular expressions.
66 66 SLUG_BAD_CHARS = re.escape('`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
67 67
68 68 # Regex that matches forbidden characters in repo/group slugs.
69 SLUG_BAD_CHAR_RE = re.compile('[{}]'.format(SLUG_BAD_CHARS))
69 SLUG_BAD_CHAR_RE = re.compile('[{}\x00-\x08\x0b-\x0c\x0e-\x1f]'.format(SLUG_BAD_CHARS))
70 70
71 71 # Regex that matches allowed characters in repo/group slugs.
72 72 SLUG_GOOD_CHAR_RE = re.compile('[^{}]'.format(SLUG_BAD_CHARS))
73 73
74 74 # Regex that matches whole repo/group slugs.
75 75 SLUG_RE = re.compile('[^{}]+'.format(SLUG_BAD_CHARS))
76 76
77 77 _license_cache = None
78 78
79 79
80 80 def repo_name_slug(value):
81 81 """
82 82 Return slug of name of repository
83 83 This function is called on each creation/modification
84 84 of repository to prevent bad names in repo
85 85 """
86 86 replacement_char = '-'
87 87
88 88 slug = remove_formatting(value)
89 89 slug = SLUG_BAD_CHAR_RE.sub('', slug)
90 90 slug = re.sub('[\s]+', '-', slug)
91 91 slug = collapse(slug, replacement_char)
92 92 return slug
93 93
94 94
95 95 #==============================================================================
96 96 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
97 97 #==============================================================================
98 98 def get_repo_slug(request):
99 99 _repo = ''
100 100
101 101 if hasattr(request, 'db_repo'):
102 102 # if our requests has set db reference use it for name, this
103 103 # translates the example.com/_<id> into proper repo names
104 104 _repo = request.db_repo.repo_name
105 105 elif getattr(request, 'matchdict', None):
106 106 # pyramid
107 107 _repo = request.matchdict.get('repo_name')
108 108
109 109 if _repo:
110 110 _repo = _repo.rstrip('/')
111 111 return _repo
112 112
113 113
114 114 def get_repo_group_slug(request):
115 115 _group = ''
116 116 if hasattr(request, 'db_repo_group'):
117 117 # if our requests has set db reference use it for name, this
118 118 # translates the example.com/_<id> into proper repo group names
119 119 _group = request.db_repo_group.group_name
120 120 elif getattr(request, 'matchdict', None):
121 121 # pyramid
122 122 _group = request.matchdict.get('repo_group_name')
123 123
124 124 if _group:
125 125 _group = _group.rstrip('/')
126 126 return _group
127 127
128 128
129 129 def get_user_group_slug(request):
130 130 _user_group = ''
131 131
132 132 if hasattr(request, 'db_user_group'):
133 133 _user_group = request.db_user_group.users_group_name
134 134 elif getattr(request, 'matchdict', None):
135 135 # pyramid
136 136 _user_group = request.matchdict.get('user_group_id')
137 137 _user_group_name = request.matchdict.get('user_group_name')
138 138 try:
139 139 if _user_group:
140 140 _user_group = UserGroup.get(_user_group)
141 141 elif _user_group_name:
142 142 _user_group = UserGroup.get_by_group_name(_user_group_name)
143 143
144 144 if _user_group:
145 145 _user_group = _user_group.users_group_name
146 146 except Exception:
147 147 log.exception('Failed to get user group by id and name')
148 148 # catch all failures here
149 149 return None
150 150
151 151 return _user_group
152 152
153 153
154 154 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
155 155 """
156 156 Scans given path for repos and return (name,(type,path)) tuple
157 157
158 158 :param path: path to scan for repositories
159 159 :param recursive: recursive search and return names with subdirs in front
160 160 """
161 161
162 162 # remove ending slash for better results
163 163 path = path.rstrip(os.sep)
164 164 log.debug('now scanning in %s location recursive:%s...', path, recursive)
165 165
166 166 def _get_repos(p):
167 167 dirpaths = _get_dirpaths(p)
168 168 if not _is_dir_writable(p):
169 169 log.warning('repo path without write access: %s', p)
170 170
171 171 for dirpath in dirpaths:
172 172 if os.path.isfile(os.path.join(p, dirpath)):
173 173 continue
174 174 cur_path = os.path.join(p, dirpath)
175 175
176 176 # skip removed repos
177 177 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
178 178 continue
179 179
180 180 #skip .<somethin> dirs
181 181 if dirpath.startswith('.'):
182 182 continue
183 183
184 184 try:
185 185 scm_info = get_scm(cur_path)
186 186 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
187 187 except VCSError:
188 188 if not recursive:
189 189 continue
190 190 #check if this dir containts other repos for recursive scan
191 191 rec_path = os.path.join(p, dirpath)
192 192 if os.path.isdir(rec_path):
193 193 for inner_scm in _get_repos(rec_path):
194 194 yield inner_scm
195 195
196 196 return _get_repos(path)
197 197
198 198
199 199 def _get_dirpaths(p):
200 200 try:
201 201 # OS-independable way of checking if we have at least read-only
202 202 # access or not.
203 203 dirpaths = os.listdir(p)
204 204 except OSError:
205 205 log.warning('ignoring repo path without read access: %s', p)
206 206 return []
207 207
208 208 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
209 209 # decode paths and suddenly returns unicode objects itself. The items it
210 210 # cannot decode are returned as strings and cause issues.
211 211 #
212 212 # Those paths are ignored here until a solid solution for path handling has
213 213 # been built.
214 214 expected_type = type(p)
215 215
216 216 def _has_correct_type(item):
217 217 if type(item) is not expected_type:
218 218 log.error(
219 219 u"Ignoring path %s since it cannot be decoded into unicode.",
220 220 # Using "repr" to make sure that we see the byte value in case
221 221 # of support.
222 222 repr(item))
223 223 return False
224 224 return True
225 225
226 226 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
227 227
228 228 return dirpaths
229 229
230 230
231 231 def _is_dir_writable(path):
232 232 """
233 233 Probe if `path` is writable.
234 234
235 235 Due to trouble on Cygwin / Windows, this is actually probing if it is
236 236 possible to create a file inside of `path`, stat does not produce reliable
237 237 results in this case.
238 238 """
239 239 try:
240 240 with tempfile.TemporaryFile(dir=path):
241 241 pass
242 242 except OSError:
243 243 return False
244 244 return True
245 245
246 246
247 247 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None, config=None):
248 248 """
249 249 Returns True if given path is a valid repository False otherwise.
250 250 If expect_scm param is given also, compare if given scm is the same
251 251 as expected from scm parameter. If explicit_scm is given don't try to
252 252 detect the scm, just use the given one to check if repo is valid
253 253
254 254 :param repo_name:
255 255 :param base_path:
256 256 :param expect_scm:
257 257 :param explicit_scm:
258 258 :param config:
259 259
260 260 :return True: if given path is a valid repository
261 261 """
262 262 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
263 263 log.debug('Checking if `%s` is a valid path for repository. '
264 264 'Explicit type: %s', repo_name, explicit_scm)
265 265
266 266 try:
267 267 if explicit_scm:
268 268 detected_scms = [get_scm_backend(explicit_scm)(
269 269 full_path, config=config).alias]
270 270 else:
271 271 detected_scms = get_scm(full_path)
272 272
273 273 if expect_scm:
274 274 return detected_scms[0] == expect_scm
275 275 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
276 276 return True
277 277 except VCSError:
278 278 log.debug('path: %s is not a valid repo !', full_path)
279 279 return False
280 280
281 281
282 282 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
283 283 """
284 284 Returns True if given path is a repository group, False otherwise
285 285
286 286 :param repo_name:
287 287 :param base_path:
288 288 """
289 289 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
290 290 log.debug('Checking if `%s` is a valid path for repository group',
291 291 repo_group_name)
292 292
293 293 # check if it's not a repo
294 294 if is_valid_repo(repo_group_name, base_path):
295 295 log.debug('Repo called %s exist, it is not a valid repo group', repo_group_name)
296 296 return False
297 297
298 298 try:
299 299 # we need to check bare git repos at higher level
300 300 # since we might match branches/hooks/info/objects or possible
301 301 # other things inside bare git repo
302 302 maybe_repo = os.path.dirname(full_path)
303 303 if maybe_repo == base_path:
304 304 # skip root level repo check, we know root location CANNOT BE a repo group
305 305 return False
306 306
307 307 scm_ = get_scm(maybe_repo)
308 308 log.debug('path: %s is a vcs object:%s, not valid repo group', full_path, scm_)
309 309 return False
310 310 except VCSError:
311 311 pass
312 312
313 313 # check if it's a valid path
314 314 if skip_path_check or os.path.isdir(full_path):
315 315 log.debug('path: %s is a valid repo group !', full_path)
316 316 return True
317 317
318 318 log.debug('path: %s is not a valid repo group !', full_path)
319 319 return False
320 320
321 321
322 322 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
323 323 while True:
324 324 ok = raw_input(prompt)
325 325 if ok.lower() in ('y', 'ye', 'yes'):
326 326 return True
327 327 if ok.lower() in ('n', 'no', 'nop', 'nope'):
328 328 return False
329 329 retries = retries - 1
330 330 if retries < 0:
331 331 raise IOError
332 332 print(complaint)
333 333
334 334 # propagated from mercurial documentation
335 335 ui_sections = [
336 336 'alias', 'auth',
337 337 'decode/encode', 'defaults',
338 338 'diff', 'email',
339 339 'extensions', 'format',
340 340 'merge-patterns', 'merge-tools',
341 341 'hooks', 'http_proxy',
342 342 'smtp', 'patch',
343 343 'paths', 'profiling',
344 344 'server', 'trusted',
345 345 'ui', 'web', ]
346 346
347 347
348 348 def config_data_from_db(clear_session=True, repo=None):
349 349 """
350 350 Read the configuration data from the database and return configuration
351 351 tuples.
352 352 """
353 353 from rhodecode.model.settings import VcsSettingsModel
354 354
355 355 config = []
356 356
357 357 sa = meta.Session()
358 358 settings_model = VcsSettingsModel(repo=repo, sa=sa)
359 359
360 360 ui_settings = settings_model.get_ui_settings()
361 361
362 362 ui_data = []
363 363 for setting in ui_settings:
364 364 if setting.active:
365 365 ui_data.append((setting.section, setting.key, setting.value))
366 366 config.append((
367 367 safe_str(setting.section), safe_str(setting.key),
368 368 safe_str(setting.value)))
369 369 if setting.key == 'push_ssl':
370 370 # force set push_ssl requirement to False, rhodecode
371 371 # handles that
372 372 config.append((
373 373 safe_str(setting.section), safe_str(setting.key), False))
374 374 log.debug(
375 375 'settings ui from db@repo[%s]: %s',
376 376 repo,
377 377 ','.join(map(lambda s: '[{}] {}={}'.format(*s), ui_data)))
378 378 if clear_session:
379 379 meta.Session.remove()
380 380
381 381 # TODO: mikhail: probably it makes no sense to re-read hooks information.
382 382 # It's already there and activated/deactivated
383 383 skip_entries = []
384 384 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
385 385 if 'pull' not in enabled_hook_classes:
386 386 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
387 387 if 'push' not in enabled_hook_classes:
388 388 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
389 389 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
390 390 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PUSH_KEY))
391 391
392 392 config = [entry for entry in config if entry[:2] not in skip_entries]
393 393
394 394 return config
395 395
396 396
397 397 def make_db_config(clear_session=True, repo=None):
398 398 """
399 399 Create a :class:`Config` instance based on the values in the database.
400 400 """
401 401 config = Config()
402 402 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
403 403 for section, option, value in config_data:
404 404 config.set(section, option, value)
405 405 return config
406 406
407 407
408 408 def get_enabled_hook_classes(ui_settings):
409 409 """
410 410 Return the enabled hook classes.
411 411
412 412 :param ui_settings: List of ui_settings as returned
413 413 by :meth:`VcsSettingsModel.get_ui_settings`
414 414
415 415 :return: a list with the enabled hook classes. The order is not guaranteed.
416 416 :rtype: list
417 417 """
418 418 enabled_hooks = []
419 419 active_hook_keys = [
420 420 key for section, key, value, active in ui_settings
421 421 if section == 'hooks' and active]
422 422
423 423 hook_names = {
424 424 RhodeCodeUi.HOOK_PUSH: 'push',
425 425 RhodeCodeUi.HOOK_PULL: 'pull',
426 426 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
427 427 }
428 428
429 429 for key in active_hook_keys:
430 430 hook = hook_names.get(key)
431 431 if hook:
432 432 enabled_hooks.append(hook)
433 433
434 434 return enabled_hooks
435 435
436 436
437 437 def set_rhodecode_config(config):
438 438 """
439 439 Updates pyramid config with new settings from database
440 440
441 441 :param config:
442 442 """
443 443 from rhodecode.model.settings import SettingsModel
444 444 app_settings = SettingsModel().get_all_settings()
445 445
446 446 for k, v in app_settings.items():
447 447 config[k] = v
448 448
449 449
450 450 def get_rhodecode_realm():
451 451 """
452 452 Return the rhodecode realm from database.
453 453 """
454 454 from rhodecode.model.settings import SettingsModel
455 455 realm = SettingsModel().get_setting_by_name('realm')
456 456 return safe_str(realm.app_settings_value)
457 457
458 458
459 459 def get_rhodecode_base_path():
460 460 """
461 461 Returns the base path. The base path is the filesystem path which points
462 462 to the repository store.
463 463 """
464 464 from rhodecode.model.settings import SettingsModel
465 465 paths_ui = SettingsModel().get_ui_by_section_and_key('paths', '/')
466 466 return safe_str(paths_ui.ui_value)
467 467
468 468
469 469 def map_groups(path):
470 470 """
471 471 Given a full path to a repository, create all nested groups that this
472 472 repo is inside. This function creates parent-child relationships between
473 473 groups and creates default perms for all new groups.
474 474
475 475 :param paths: full path to repository
476 476 """
477 477 from rhodecode.model.repo_group import RepoGroupModel
478 478 sa = meta.Session()
479 479 groups = path.split(Repository.NAME_SEP)
480 480 parent = None
481 481 group = None
482 482
483 483 # last element is repo in nested groups structure
484 484 groups = groups[:-1]
485 485 rgm = RepoGroupModel(sa)
486 486 owner = User.get_first_super_admin()
487 487 for lvl, group_name in enumerate(groups):
488 488 group_name = '/'.join(groups[:lvl] + [group_name])
489 489 group = RepoGroup.get_by_group_name(group_name)
490 490 desc = '%s group' % group_name
491 491
492 492 # skip folders that are now removed repos
493 493 if REMOVED_REPO_PAT.match(group_name):
494 494 break
495 495
496 496 if group is None:
497 497 log.debug('creating group level: %s group_name: %s',
498 498 lvl, group_name)
499 499 group = RepoGroup(group_name, parent)
500 500 group.group_description = desc
501 501 group.user = owner
502 502 sa.add(group)
503 503 perm_obj = rgm._create_default_perms(group)
504 504 sa.add(perm_obj)
505 505 sa.flush()
506 506
507 507 parent = group
508 508 return group
509 509
510 510
511 511 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
512 512 """
513 513 maps all repos given in initial_repo_list, non existing repositories
514 514 are created, if remove_obsolete is True it also checks for db entries
515 515 that are not in initial_repo_list and removes them.
516 516
517 517 :param initial_repo_list: list of repositories found by scanning methods
518 518 :param remove_obsolete: check for obsolete entries in database
519 519 """
520 520 from rhodecode.model.repo import RepoModel
521 521 from rhodecode.model.repo_group import RepoGroupModel
522 522 from rhodecode.model.settings import SettingsModel
523 523
524 524 sa = meta.Session()
525 525 repo_model = RepoModel()
526 526 user = User.get_first_super_admin()
527 527 added = []
528 528
529 529 # creation defaults
530 530 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
531 531 enable_statistics = defs.get('repo_enable_statistics')
532 532 enable_locking = defs.get('repo_enable_locking')
533 533 enable_downloads = defs.get('repo_enable_downloads')
534 534 private = defs.get('repo_private')
535 535
536 536 for name, repo in initial_repo_list.items():
537 537 group = map_groups(name)
538 538 unicode_name = safe_unicode(name)
539 539 db_repo = repo_model.get_by_repo_name(unicode_name)
540 540 # found repo that is on filesystem not in RhodeCode database
541 541 if not db_repo:
542 542 log.info('repository %s not found, creating now', name)
543 543 added.append(name)
544 544 desc = (repo.description
545 545 if repo.description != 'unknown'
546 546 else '%s repository' % name)
547 547
548 548 db_repo = repo_model._create_repo(
549 549 repo_name=name,
550 550 repo_type=repo.alias,
551 551 description=desc,
552 552 repo_group=getattr(group, 'group_id', None),
553 553 owner=user,
554 554 enable_locking=enable_locking,
555 555 enable_downloads=enable_downloads,
556 556 enable_statistics=enable_statistics,
557 557 private=private,
558 558 state=Repository.STATE_CREATED
559 559 )
560 560 sa.commit()
561 561 # we added that repo just now, and make sure we updated server info
562 562 if db_repo.repo_type == 'git':
563 563 git_repo = db_repo.scm_instance()
564 564 # update repository server-info
565 565 log.debug('Running update server info')
566 566 git_repo._update_server_info()
567 567
568 568 db_repo.update_commit_cache()
569 569
570 570 config = db_repo._config
571 571 config.set('extensions', 'largefiles', '')
572 572 repo = db_repo.scm_instance(config=config)
573 573 repo.install_hooks()
574 574
575 575 removed = []
576 576 if remove_obsolete:
577 577 # remove from database those repositories that are not in the filesystem
578 578 for repo in sa.query(Repository).all():
579 579 if repo.repo_name not in initial_repo_list.keys():
580 580 log.debug("Removing non-existing repository found in db `%s`",
581 581 repo.repo_name)
582 582 try:
583 583 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
584 584 sa.commit()
585 585 removed.append(repo.repo_name)
586 586 except Exception:
587 587 # don't hold further removals on error
588 588 log.error(traceback.format_exc())
589 589 sa.rollback()
590 590
591 591 def splitter(full_repo_name):
592 592 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
593 593 gr_name = None
594 594 if len(_parts) == 2:
595 595 gr_name = _parts[0]
596 596 return gr_name
597 597
598 598 initial_repo_group_list = [splitter(x) for x in
599 599 initial_repo_list.keys() if splitter(x)]
600 600
601 601 # remove from database those repository groups that are not in the
602 602 # filesystem due to parent child relationships we need to delete them
603 603 # in a specific order of most nested first
604 604 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
605 605 nested_sort = lambda gr: len(gr.split('/'))
606 606 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
607 607 if group_name not in initial_repo_group_list:
608 608 repo_group = RepoGroup.get_by_group_name(group_name)
609 609 if (repo_group.children.all() or
610 610 not RepoGroupModel().check_exist_filesystem(
611 611 group_name=group_name, exc_on_failure=False)):
612 612 continue
613 613
614 614 log.info(
615 615 'Removing non-existing repository group found in db `%s`',
616 616 group_name)
617 617 try:
618 618 RepoGroupModel(sa).delete(group_name, fs_remove=False)
619 619 sa.commit()
620 620 removed.append(group_name)
621 621 except Exception:
622 622 # don't hold further removals on error
623 623 log.exception(
624 624 'Unable to remove repository group `%s`',
625 625 group_name)
626 626 sa.rollback()
627 627 raise
628 628
629 629 return added, removed
630 630
631 631
632 632 def load_rcextensions(root_path):
633 633 import rhodecode
634 634 from rhodecode.config import conf
635 635
636 636 path = os.path.join(root_path)
637 637 sys.path.append(path)
638 638
639 639 try:
640 640 rcextensions = __import__('rcextensions')
641 641 except ImportError:
642 642 if os.path.isdir(os.path.join(path, 'rcextensions')):
643 643 log.warn('Unable to load rcextensions from %s', path)
644 644 rcextensions = None
645 645
646 646 if rcextensions:
647 647 log.info('Loaded rcextensions from %s...', rcextensions)
648 648 rhodecode.EXTENSIONS = rcextensions
649 649
650 650 # Additional mappings that are not present in the pygments lexers
651 651 conf.LANGUAGES_EXTENSIONS_MAP.update(
652 652 getattr(rhodecode.EXTENSIONS, 'EXTRA_MAPPINGS', {}))
653 653
654 654
655 655 def get_custom_lexer(extension):
656 656 """
657 657 returns a custom lexer if it is defined in rcextensions module, or None
658 658 if there's no custom lexer defined
659 659 """
660 660 import rhodecode
661 661 from pygments import lexers
662 662
663 663 # custom override made by RhodeCode
664 664 if extension in ['mako']:
665 665 return lexers.get_lexer_by_name('html+mako')
666 666
667 667 # check if we didn't define this extension as other lexer
668 668 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
669 669 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
670 670 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
671 671 return lexers.get_lexer_by_name(_lexer_name)
672 672
673 673
674 674 #==============================================================================
675 675 # TEST FUNCTIONS AND CREATORS
676 676 #==============================================================================
677 677 def create_test_index(repo_location, config):
678 678 """
679 679 Makes default test index.
680 680 """
681 681 import rc_testdata
682 682
683 683 rc_testdata.extract_search_index(
684 684 'vcs_search_index', os.path.dirname(config['search.location']))
685 685
686 686
687 687 def create_test_directory(test_path):
688 688 """
689 689 Create test directory if it doesn't exist.
690 690 """
691 691 if not os.path.isdir(test_path):
692 692 log.debug('Creating testdir %s', test_path)
693 693 os.makedirs(test_path)
694 694
695 695
696 696 def create_test_database(test_path, config):
697 697 """
698 698 Makes a fresh database.
699 699 """
700 700 from rhodecode.lib.db_manage import DbManage
701 701
702 702 # PART ONE create db
703 703 dbconf = config['sqlalchemy.db1.url']
704 704 log.debug('making test db %s', dbconf)
705 705
706 706 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
707 707 tests=True, cli_args={'force_ask': True})
708 708 dbmanage.create_tables(override=True)
709 709 dbmanage.set_db_version()
710 710 # for tests dynamically set new root paths based on generated content
711 711 dbmanage.create_settings(dbmanage.config_prompt(test_path))
712 712 dbmanage.create_default_user()
713 713 dbmanage.create_test_admin_and_users()
714 714 dbmanage.create_permissions()
715 715 dbmanage.populate_default_permissions()
716 716 Session().commit()
717 717
718 718
719 719 def create_test_repositories(test_path, config):
720 720 """
721 721 Creates test repositories in the temporary directory. Repositories are
722 722 extracted from archives within the rc_testdata package.
723 723 """
724 724 import rc_testdata
725 725 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
726 726
727 727 log.debug('making test vcs repositories')
728 728
729 729 idx_path = config['search.location']
730 730 data_path = config['cache_dir']
731 731
732 732 # clean index and data
733 733 if idx_path and os.path.exists(idx_path):
734 734 log.debug('remove %s', idx_path)
735 735 shutil.rmtree(idx_path)
736 736
737 737 if data_path and os.path.exists(data_path):
738 738 log.debug('remove %s', data_path)
739 739 shutil.rmtree(data_path)
740 740
741 741 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
742 742 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
743 743
744 744 # Note: Subversion is in the process of being integrated with the system,
745 745 # until we have a properly packed version of the test svn repository, this
746 746 # tries to copy over the repo from a package "rc_testdata"
747 747 svn_repo_path = rc_testdata.get_svn_repo_archive()
748 748 with tarfile.open(svn_repo_path) as tar:
749 749 tar.extractall(jn(test_path, SVN_REPO))
750 750
751 751
752 752 def password_changed(auth_user, session):
753 753 # Never report password change in case of default user or anonymous user.
754 754 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
755 755 return False
756 756
757 757 password_hash = md5(auth_user.password) if auth_user.password else None
758 758 rhodecode_user = session.get('rhodecode_user', {})
759 759 session_password_hash = rhodecode_user.get('password', '')
760 760 return password_hash != session_password_hash
761 761
762 762
763 763 def read_opensource_licenses():
764 764 global _license_cache
765 765
766 766 if not _license_cache:
767 767 licenses = pkg_resources.resource_string(
768 768 'rhodecode', 'config/licenses.json')
769 769 _license_cache = json.loads(licenses)
770 770
771 771 return _license_cache
772 772
773 773
774 774 def generate_platform_uuid():
775 775 """
776 776 Generates platform UUID based on it's name
777 777 """
778 778 import platform
779 779
780 780 try:
781 781 uuid_list = [platform.platform()]
782 782 return hashlib.sha256(':'.join(uuid_list)).hexdigest()
783 783 except Exception as e:
784 784 log.error('Failed to generate host uuid: %s', e)
785 785 return 'UNDEFINED'
786 786
787 787
788 788 def send_test_email(recipients, email_body='TEST EMAIL'):
789 789 """
790 790 Simple code for generating test emails.
791 791 Usage::
792 792
793 793 from rhodecode.lib import utils
794 794 utils.send_test_email()
795 795 """
796 796 from rhodecode.lib.celerylib import tasks, run_task
797 797
798 798 email_body = email_body_plaintext = email_body
799 799 subject = 'SUBJECT FROM: {}'.format(socket.gethostname())
800 800 tasks.send_email(recipients, subject, email_body_plaintext, email_body)
General Comments 0
You need to be logged in to leave comments. Login now