##// END OF EJS Templates
auth: reduced usage of raw auth calls inside templates
marcink -
r3587:4c21f44c new-ui
parent child Browse files
Show More
@@ -1,701 +1,723 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 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
29 29 from rhodecode.lib.utils2 import (
30 30 StrictAttributeDict, str2bool, safe_int, datetime_to_time, safe_unicode)
31 31 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
32 32 from rhodecode.model import repo
33 33 from rhodecode.model import repo_group
34 34 from rhodecode.model import user_group
35 35 from rhodecode.model import user
36 36 from rhodecode.model.db import User
37 37 from rhodecode.model.scm import ScmModel
38 38 from rhodecode.model.settings import VcsSettingsModel
39 39
40 40 log = logging.getLogger(__name__)
41 41
42 42
43 43 ADMIN_PREFIX = '/_admin'
44 44 STATIC_FILE_PREFIX = '/_static'
45 45
46 46 URL_NAME_REQUIREMENTS = {
47 47 # group name can have a slash in them, but they must not end with a slash
48 48 'group_name': r'.*?[^/]',
49 49 'repo_group_name': r'.*?[^/]',
50 50 # repo names can have a slash in them, but they must not end with a slash
51 51 'repo_name': r'.*?[^/]',
52 52 # file path eats up everything at the end
53 53 'f_path': r'.*',
54 54 # reference types
55 55 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
56 56 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
57 57 }
58 58
59 59
60 60 def add_route_with_slash(config,name, pattern, **kw):
61 61 config.add_route(name, pattern, **kw)
62 62 if not pattern.endswith('/'):
63 63 config.add_route(name + '_slash', pattern + '/', **kw)
64 64
65 65
66 66 def add_route_requirements(route_path, requirements=None):
67 67 """
68 68 Adds regex requirements to pyramid routes using a mapping dict
69 69 e.g::
70 70 add_route_requirements('{repo_name}/settings')
71 71 """
72 72 requirements = requirements or URL_NAME_REQUIREMENTS
73 73 for key, regex in requirements.items():
74 74 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
75 75 return route_path
76 76
77 77
78 78 def get_format_ref_id(repo):
79 79 """Returns a `repo` specific reference formatter function"""
80 80 if h.is_svn(repo):
81 81 return _format_ref_id_svn
82 82 else:
83 83 return _format_ref_id
84 84
85 85
86 86 def _format_ref_id(name, raw_id):
87 87 """Default formatting of a given reference `name`"""
88 88 return name
89 89
90 90
91 91 def _format_ref_id_svn(name, raw_id):
92 92 """Special way of formatting a reference for Subversion including path"""
93 93 return '%s@%s' % (name, raw_id)
94 94
95 95
96 96 class TemplateArgs(StrictAttributeDict):
97 97 pass
98 98
99 99
100 100 class BaseAppView(object):
101 101
102 102 def __init__(self, context, request):
103 103 self.request = request
104 104 self.context = context
105 105 self.session = request.session
106 106 if not hasattr(request, 'user'):
107 107 # NOTE(marcink): edge case, we ended up in matched route
108 108 # but probably of web-app context, e.g API CALL/VCS CALL
109 109 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
110 110 log.warning('Unable to process request `%s` in this scope', request)
111 111 raise HTTPBadRequest()
112 112
113 113 self._rhodecode_user = request.user # auth user
114 114 self._rhodecode_db_user = self._rhodecode_user.get_instance()
115 115 self._maybe_needs_password_change(
116 116 request.matched_route.name, self._rhodecode_db_user)
117 117
118 118 def _maybe_needs_password_change(self, view_name, user_obj):
119 119 log.debug('Checking if user %s needs password change on view %s',
120 120 user_obj, view_name)
121 121 skip_user_views = [
122 122 'logout', 'login',
123 123 'my_account_password', 'my_account_password_update'
124 124 ]
125 125
126 126 if not user_obj:
127 127 return
128 128
129 129 if user_obj.username == User.DEFAULT_USER:
130 130 return
131 131
132 132 now = time.time()
133 133 should_change = user_obj.user_data.get('force_password_change')
134 134 change_after = safe_int(should_change) or 0
135 135 if should_change and now > change_after:
136 136 log.debug('User %s requires password change', user_obj)
137 137 h.flash('You are required to change your password', 'warning',
138 138 ignore_duplicate=True)
139 139
140 140 if view_name not in skip_user_views:
141 141 raise HTTPFound(
142 142 self.request.route_path('my_account_password'))
143 143
144 144 def _log_creation_exception(self, e, repo_name):
145 145 _ = self.request.translate
146 146 reason = None
147 147 if len(e.args) == 2:
148 148 reason = e.args[1]
149 149
150 150 if reason == 'INVALID_CERTIFICATE':
151 151 log.exception(
152 152 'Exception creating a repository: invalid certificate')
153 153 msg = (_('Error creating repository %s: invalid certificate')
154 154 % repo_name)
155 155 else:
156 156 log.exception("Exception creating a repository")
157 157 msg = (_('Error creating repository %s')
158 158 % repo_name)
159 159 return msg
160 160
161 161 def _get_local_tmpl_context(self, include_app_defaults=True):
162 162 c = TemplateArgs()
163 163 c.auth_user = self.request.user
164 164 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
165 165 c.rhodecode_user = self.request.user
166 166
167 167 if include_app_defaults:
168 168 from rhodecode.lib.base import attach_context_attributes
169 169 attach_context_attributes(c, self.request, self.request.user.user_id)
170 170
171 c.is_super_admin = c.auth_user.is_admin
172
173 c.can_create_repo = c.is_super_admin
174 c.can_create_repo_group = c.is_super_admin
175 c.can_create_user_group = c.is_super_admin
176
177 c.is_delegated_admin = False
178
179 if not c.auth_user.is_default:
180 c.can_create_repo = h.HasPermissionAny('hg.create.repository')(
181 user=self.request.user)
182 repositories = c.auth_user.repositories_admin or c.can_create_repo
183
184 c.can_create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')(
185 user=self.request.user)
186 repository_groups = c.auth_user.repository_groups_admin or c.can_create_repo_group
187
188 c.can_create_user_group = h.HasPermissionAny('hg.usergroup.create.true')(
189 user=self.request.user)
190 user_groups = c.auth_user.user_groups_admin or c.can_create_user_group
191 # delegated admin can create, or manage some objects
192 c.is_delegated_admin = repositories or repository_groups or user_groups
171 193 return c
172 194
173 195 def _get_template_context(self, tmpl_args, **kwargs):
174 196
175 197 local_tmpl_args = {
176 198 'defaults': {},
177 199 'errors': {},
178 200 'c': tmpl_args
179 201 }
180 202 local_tmpl_args.update(kwargs)
181 203 return local_tmpl_args
182 204
183 205 def load_default_context(self):
184 206 """
185 207 example:
186 208
187 209 def load_default_context(self):
188 210 c = self._get_local_tmpl_context()
189 211 c.custom_var = 'foobar'
190 212
191 213 return c
192 214 """
193 215 raise NotImplementedError('Needs implementation in view class')
194 216
195 217
196 218 class RepoAppView(BaseAppView):
197 219
198 220 def __init__(self, context, request):
199 221 super(RepoAppView, self).__init__(context, request)
200 222 self.db_repo = request.db_repo
201 223 self.db_repo_name = self.db_repo.repo_name
202 224 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
203 225
204 226 def _handle_missing_requirements(self, error):
205 227 log.error(
206 228 'Requirements are missing for repository %s: %s',
207 229 self.db_repo_name, safe_unicode(error))
208 230
209 231 def _get_local_tmpl_context(self, include_app_defaults=True):
210 232 _ = self.request.translate
211 233 c = super(RepoAppView, self)._get_local_tmpl_context(
212 234 include_app_defaults=include_app_defaults)
213 235
214 236 # register common vars for this type of view
215 237 c.rhodecode_db_repo = self.db_repo
216 238 c.repo_name = self.db_repo_name
217 239 c.repository_pull_requests = self.db_repo_pull_requests
218 240 self.path_filter = PathFilter(None)
219 241
220 242 c.repository_requirements_missing = {}
221 243 try:
222 244 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
223 245 if self.rhodecode_vcs_repo:
224 246 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
225 247 c.auth_user.username)
226 248 self.path_filter = PathFilter(path_perms)
227 249 except RepositoryRequirementError as e:
228 250 c.repository_requirements_missing = {'error': str(e)}
229 251 self._handle_missing_requirements(e)
230 252 self.rhodecode_vcs_repo = None
231 253
232 254 c.path_filter = self.path_filter # used by atom_feed_entry.mako
233 255
234 256 if self.rhodecode_vcs_repo is None:
235 257 # unable to fetch this repo as vcs instance, report back to user
236 258 h.flash(_(
237 259 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
238 260 "Please check if it exist, or is not damaged.") %
239 261 {'repo_name': c.repo_name},
240 262 category='error', ignore_duplicate=True)
241 263 if c.repository_requirements_missing:
242 264 route = self.request.matched_route.name
243 265 if route.startswith(('edit_repo', 'repo_summary')):
244 266 # allow summary and edit repo on missing requirements
245 267 return c
246 268
247 269 raise HTTPFound(
248 270 h.route_path('repo_summary', repo_name=self.db_repo_name))
249 271
250 272 else: # redirect if we don't show missing requirements
251 273 raise HTTPFound(h.route_path('home'))
252 274
253 275 c.has_origin_repo_read_perm = False
254 276 if self.db_repo.fork:
255 277 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
256 278 'repository.write', 'repository.read', 'repository.admin')(
257 279 self.db_repo.fork.repo_name, 'summary fork link')
258 280
259 281 return c
260 282
261 283 def _get_f_path_unchecked(self, matchdict, default=None):
262 284 """
263 285 Should only be used by redirects, everything else should call _get_f_path
264 286 """
265 287 f_path = matchdict.get('f_path')
266 288 if f_path:
267 289 # fix for multiple initial slashes that causes errors for GIT
268 290 return f_path.lstrip('/')
269 291
270 292 return default
271 293
272 294 def _get_f_path(self, matchdict, default=None):
273 295 f_path_match = self._get_f_path_unchecked(matchdict, default)
274 296 return self.path_filter.assert_path_permissions(f_path_match)
275 297
276 298 def _get_general_setting(self, target_repo, settings_key, default=False):
277 299 settings_model = VcsSettingsModel(repo=target_repo)
278 300 settings = settings_model.get_general_settings()
279 301 return settings.get(settings_key, default)
280 302
281 303 def get_recache_flag(self):
282 304 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
283 305 flag_val = self.request.GET.get(flag_name)
284 306 if str2bool(flag_val):
285 307 return True
286 308 return False
287 309
288 310
289 311 class PathFilter(object):
290 312
291 313 # Expects and instance of BasePathPermissionChecker or None
292 314 def __init__(self, permission_checker):
293 315 self.permission_checker = permission_checker
294 316
295 317 def assert_path_permissions(self, path):
296 318 if path and self.permission_checker and not self.permission_checker.has_access(path):
297 319 raise HTTPForbidden()
298 320 return path
299 321
300 322 def filter_patchset(self, patchset):
301 323 if not self.permission_checker or not patchset:
302 324 return patchset, False
303 325 had_filtered = False
304 326 filtered_patchset = []
305 327 for patch in patchset:
306 328 filename = patch.get('filename', None)
307 329 if not filename or self.permission_checker.has_access(filename):
308 330 filtered_patchset.append(patch)
309 331 else:
310 332 had_filtered = True
311 333 if had_filtered:
312 334 if isinstance(patchset, diffs.LimitedDiffContainer):
313 335 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
314 336 return filtered_patchset, True
315 337 else:
316 338 return patchset, False
317 339
318 340 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
319 341 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
320 342 result = diffset.render_patchset(
321 343 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
322 344 result.has_hidden_changes = has_hidden_changes
323 345 return result
324 346
325 347 def get_raw_patch(self, diff_processor):
326 348 if self.permission_checker is None:
327 349 return diff_processor.as_raw()
328 350 elif self.permission_checker.has_full_access:
329 351 return diff_processor.as_raw()
330 352 else:
331 353 return '# Repository has user-specific filters, raw patch generation is disabled.'
332 354
333 355 @property
334 356 def is_enabled(self):
335 357 return self.permission_checker is not None
336 358
337 359
338 360 class RepoGroupAppView(BaseAppView):
339 361 def __init__(self, context, request):
340 362 super(RepoGroupAppView, self).__init__(context, request)
341 363 self.db_repo_group = request.db_repo_group
342 364 self.db_repo_group_name = self.db_repo_group.group_name
343 365
344 366 def _get_local_tmpl_context(self, include_app_defaults=True):
345 367 _ = self.request.translate
346 368 c = super(RepoGroupAppView, self)._get_local_tmpl_context(
347 369 include_app_defaults=include_app_defaults)
348 370 c.repo_group = self.db_repo_group
349 371 return c
350 372
351 373 def _revoke_perms_on_yourself(self, form_result):
352 374 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
353 375 form_result['perm_updates'])
354 376 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
355 377 form_result['perm_additions'])
356 378 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
357 379 form_result['perm_deletions'])
358 380 admin_perm = 'group.admin'
359 381 if _updates and _updates[0][1] != admin_perm or \
360 382 _additions and _additions[0][1] != admin_perm or \
361 383 _deletions and _deletions[0][1] != admin_perm:
362 384 return True
363 385 return False
364 386
365 387
366 388 class UserGroupAppView(BaseAppView):
367 389 def __init__(self, context, request):
368 390 super(UserGroupAppView, self).__init__(context, request)
369 391 self.db_user_group = request.db_user_group
370 392 self.db_user_group_name = self.db_user_group.users_group_name
371 393
372 394
373 395 class UserAppView(BaseAppView):
374 396 def __init__(self, context, request):
375 397 super(UserAppView, self).__init__(context, request)
376 398 self.db_user = request.db_user
377 399 self.db_user_id = self.db_user.user_id
378 400
379 401 _ = self.request.translate
380 402 if not request.db_user_supports_default:
381 403 if self.db_user.username == User.DEFAULT_USER:
382 404 h.flash(_("Editing user `{}` is disabled.".format(
383 405 User.DEFAULT_USER)), category='warning')
384 406 raise HTTPFound(h.route_path('users'))
385 407
386 408
387 409 class DataGridAppView(object):
388 410 """
389 411 Common class to have re-usable grid rendering components
390 412 """
391 413
392 414 def _extract_ordering(self, request, column_map=None):
393 415 column_map = column_map or {}
394 416 column_index = safe_int(request.GET.get('order[0][column]'))
395 417 order_dir = request.GET.get(
396 418 'order[0][dir]', 'desc')
397 419 order_by = request.GET.get(
398 420 'columns[%s][data][sort]' % column_index, 'name_raw')
399 421
400 422 # translate datatable to DB columns
401 423 order_by = column_map.get(order_by) or order_by
402 424
403 425 search_q = request.GET.get('search[value]')
404 426 return search_q, order_by, order_dir
405 427
406 428 def _extract_chunk(self, request):
407 429 start = safe_int(request.GET.get('start'), 0)
408 430 length = safe_int(request.GET.get('length'), 25)
409 431 draw = safe_int(request.GET.get('draw'))
410 432 return draw, start, length
411 433
412 434 def _get_order_col(self, order_by, model):
413 435 if isinstance(order_by, compat.string_types):
414 436 try:
415 437 return operator.attrgetter(order_by)(model)
416 438 except AttributeError:
417 439 return None
418 440 else:
419 441 return order_by
420 442
421 443
422 444 class BaseReferencesView(RepoAppView):
423 445 """
424 446 Base for reference view for branches, tags and bookmarks.
425 447 """
426 448 def load_default_context(self):
427 449 c = self._get_local_tmpl_context()
428 450
429 451
430 452 return c
431 453
432 454 def load_refs_context(self, ref_items, partials_template):
433 455 _render = self.request.get_partial_renderer(partials_template)
434 456 pre_load = ["author", "date", "message"]
435 457
436 458 is_svn = h.is_svn(self.rhodecode_vcs_repo)
437 459 is_hg = h.is_hg(self.rhodecode_vcs_repo)
438 460
439 461 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
440 462
441 463 closed_refs = {}
442 464 if is_hg:
443 465 closed_refs = self.rhodecode_vcs_repo.branches_closed
444 466
445 467 data = []
446 468 for ref_name, commit_id in ref_items:
447 469 commit = self.rhodecode_vcs_repo.get_commit(
448 470 commit_id=commit_id, pre_load=pre_load)
449 471 closed = ref_name in closed_refs
450 472
451 473 # TODO: johbo: Unify generation of reference links
452 474 use_commit_id = '/' in ref_name or is_svn
453 475
454 476 if use_commit_id:
455 477 files_url = h.route_path(
456 478 'repo_files',
457 479 repo_name=self.db_repo_name,
458 480 f_path=ref_name if is_svn else '',
459 481 commit_id=commit_id)
460 482
461 483 else:
462 484 files_url = h.route_path(
463 485 'repo_files',
464 486 repo_name=self.db_repo_name,
465 487 f_path=ref_name if is_svn else '',
466 488 commit_id=ref_name,
467 489 _query=dict(at=ref_name))
468 490
469 491 data.append({
470 492 "name": _render('name', ref_name, files_url, closed),
471 493 "name_raw": ref_name,
472 494 "date": _render('date', commit.date),
473 495 "date_raw": datetime_to_time(commit.date),
474 496 "author": _render('author', commit.author),
475 497 "commit": _render(
476 498 'commit', commit.message, commit.raw_id, commit.idx),
477 499 "commit_raw": commit.idx,
478 500 "compare": _render(
479 501 'compare', format_ref_id(ref_name, commit.raw_id)),
480 502 })
481 503
482 504 return data
483 505
484 506
485 507 class RepoRoutePredicate(object):
486 508 def __init__(self, val, config):
487 509 self.val = val
488 510
489 511 def text(self):
490 512 return 'repo_route = %s' % self.val
491 513
492 514 phash = text
493 515
494 516 def __call__(self, info, request):
495 517 if hasattr(request, 'vcs_call'):
496 518 # skip vcs calls
497 519 return
498 520
499 521 repo_name = info['match']['repo_name']
500 522 repo_model = repo.RepoModel()
501 523
502 524 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
503 525
504 526 def redirect_if_creating(route_info, db_repo):
505 527 skip_views = ['edit_repo_advanced_delete']
506 528 route = route_info['route']
507 529 # we should skip delete view so we can actually "remove" repositories
508 530 # if they get stuck in creating state.
509 531 if route.name in skip_views:
510 532 return
511 533
512 534 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
513 535 repo_creating_url = request.route_path(
514 536 'repo_creating', repo_name=db_repo.repo_name)
515 537 raise HTTPFound(repo_creating_url)
516 538
517 539 if by_name_match:
518 540 # register this as request object we can re-use later
519 541 request.db_repo = by_name_match
520 542 redirect_if_creating(info, by_name_match)
521 543 return True
522 544
523 545 by_id_match = repo_model.get_repo_by_id(repo_name)
524 546 if by_id_match:
525 547 request.db_repo = by_id_match
526 548 redirect_if_creating(info, by_id_match)
527 549 return True
528 550
529 551 return False
530 552
531 553
532 554 class RepoForbidArchivedRoutePredicate(object):
533 555 def __init__(self, val, config):
534 556 self.val = val
535 557
536 558 def text(self):
537 559 return 'repo_forbid_archived = %s' % self.val
538 560
539 561 phash = text
540 562
541 563 def __call__(self, info, request):
542 564 _ = request.translate
543 565 rhodecode_db_repo = request.db_repo
544 566
545 567 log.debug(
546 568 '%s checking if archived flag for repo for %s',
547 569 self.__class__.__name__, rhodecode_db_repo.repo_name)
548 570
549 571 if rhodecode_db_repo.archived:
550 572 log.warning('Current view is not supported for archived repo:%s',
551 573 rhodecode_db_repo.repo_name)
552 574
553 575 h.flash(
554 576 h.literal(_('Action not supported for archived repository.')),
555 577 category='warning')
556 578 summary_url = request.route_path(
557 579 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
558 580 raise HTTPFound(summary_url)
559 581 return True
560 582
561 583
562 584 class RepoTypeRoutePredicate(object):
563 585 def __init__(self, val, config):
564 586 self.val = val or ['hg', 'git', 'svn']
565 587
566 588 def text(self):
567 589 return 'repo_accepted_type = %s' % self.val
568 590
569 591 phash = text
570 592
571 593 def __call__(self, info, request):
572 594 if hasattr(request, 'vcs_call'):
573 595 # skip vcs calls
574 596 return
575 597
576 598 rhodecode_db_repo = request.db_repo
577 599
578 600 log.debug(
579 601 '%s checking repo type for %s in %s',
580 602 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
581 603
582 604 if rhodecode_db_repo.repo_type in self.val:
583 605 return True
584 606 else:
585 607 log.warning('Current view is not supported for repo type:%s',
586 608 rhodecode_db_repo.repo_type)
587 609 return False
588 610
589 611
590 612 class RepoGroupRoutePredicate(object):
591 613 def __init__(self, val, config):
592 614 self.val = val
593 615
594 616 def text(self):
595 617 return 'repo_group_route = %s' % self.val
596 618
597 619 phash = text
598 620
599 621 def __call__(self, info, request):
600 622 if hasattr(request, 'vcs_call'):
601 623 # skip vcs calls
602 624 return
603 625
604 626 repo_group_name = info['match']['repo_group_name']
605 627 repo_group_model = repo_group.RepoGroupModel()
606 628 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
607 629
608 630 if by_name_match:
609 631 # register this as request object we can re-use later
610 632 request.db_repo_group = by_name_match
611 633 return True
612 634
613 635 return False
614 636
615 637
616 638 class UserGroupRoutePredicate(object):
617 639 def __init__(self, val, config):
618 640 self.val = val
619 641
620 642 def text(self):
621 643 return 'user_group_route = %s' % self.val
622 644
623 645 phash = text
624 646
625 647 def __call__(self, info, request):
626 648 if hasattr(request, 'vcs_call'):
627 649 # skip vcs calls
628 650 return
629 651
630 652 user_group_id = info['match']['user_group_id']
631 653 user_group_model = user_group.UserGroup()
632 654 by_id_match = user_group_model.get(user_group_id, cache=False)
633 655
634 656 if by_id_match:
635 657 # register this as request object we can re-use later
636 658 request.db_user_group = by_id_match
637 659 return True
638 660
639 661 return False
640 662
641 663
642 664 class UserRoutePredicateBase(object):
643 665 supports_default = None
644 666
645 667 def __init__(self, val, config):
646 668 self.val = val
647 669
648 670 def text(self):
649 671 raise NotImplementedError()
650 672
651 673 def __call__(self, info, request):
652 674 if hasattr(request, 'vcs_call'):
653 675 # skip vcs calls
654 676 return
655 677
656 678 user_id = info['match']['user_id']
657 679 user_model = user.User()
658 680 by_id_match = user_model.get(user_id, cache=False)
659 681
660 682 if by_id_match:
661 683 # register this as request object we can re-use later
662 684 request.db_user = by_id_match
663 685 request.db_user_supports_default = self.supports_default
664 686 return True
665 687
666 688 return False
667 689
668 690
669 691 class UserRoutePredicate(UserRoutePredicateBase):
670 692 supports_default = False
671 693
672 694 def text(self):
673 695 return 'user_route = %s' % self.val
674 696
675 697 phash = text
676 698
677 699
678 700 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
679 701 supports_default = True
680 702
681 703 def text(self):
682 704 return 'user_with_default_route = %s' % self.val
683 705
684 706 phash = text
685 707
686 708
687 709 def includeme(config):
688 710 config.add_route_predicate(
689 711 'repo_route', RepoRoutePredicate)
690 712 config.add_route_predicate(
691 713 'repo_accepted_types', RepoTypeRoutePredicate)
692 714 config.add_route_predicate(
693 715 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
694 716 config.add_route_predicate(
695 717 'repo_group_route', RepoGroupRoutePredicate)
696 718 config.add_route_predicate(
697 719 'user_group_route', UserGroupRoutePredicate)
698 720 config.add_route_predicate(
699 721 'user_route_with_default', UserRouteWithDefaultPredicate)
700 722 config.add_route_predicate(
701 723 'user_route', UserRoutePredicate)
@@ -1,69 +1,72 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 from pyramid.httpexceptions import HTTPFound
23 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
24 24 from pyramid.view import view_config
25 25
26 26 from rhodecode.apps._base import BaseAppView
27 27 from rhodecode.lib import helpers as h
28 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
28 from rhodecode.lib.auth import (LoginRequired, NotAnonymous)
29 29 from rhodecode.model.db import PullRequest
30 30
31 31
32 32 log = logging.getLogger(__name__)
33 33
34 34
35 35 class AdminMainView(BaseAppView):
36 36 def load_default_context(self):
37 37 c = self._get_local_tmpl_context()
38 38 return c
39 39
40 40 @LoginRequired()
41 @HasPermissionAllDecorator('hg.admin')
41 @NotAnonymous()
42 42 @view_config(
43 43 route_name='admin_home', request_method='GET',
44 44 renderer='rhodecode:templates/admin/main.mako')
45 45 def admin_main(self):
46 46 c = self.load_default_context()
47 47 c.active = 'admin'
48
49 if not (c.is_super_admin or c.is_delegated_admin):
50 raise HTTPNotFound()
51
48 52 return self._get_template_context(c)
49 53
50 54 @LoginRequired()
51 55 @view_config(route_name='pull_requests_global_0', request_method='GET')
52 56 @view_config(route_name='pull_requests_global_1', request_method='GET')
53 57 @view_config(route_name='pull_requests_global', request_method='GET')
54 58 def pull_requests(self):
55 59 """
56 60 Global redirect for Pull Requests
57
58 :param pull_request_id: id of pull requests in the system
61 pull_request_id: id of pull requests in the system
59 62 """
60 63
61 64 pull_request = PullRequest.get_or_404(
62 65 self.request.matchdict['pull_request_id'])
63 66 pull_request_id = pull_request.pull_request_id
64 67
65 68 repo_name = pull_request.target_repo.repo_name
66 69
67 70 raise HTTPFound(
68 71 h.route_path('pullrequest_show', repo_name=repo_name,
69 72 pull_request_id=pull_request_id))
@@ -1,2356 +1,2352 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 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 authentication and permission libraries
23 23 """
24 24
25 25 import os
26 26 import time
27 27 import inspect
28 28 import collections
29 29 import fnmatch
30 30 import hashlib
31 31 import itertools
32 32 import logging
33 33 import random
34 34 import traceback
35 35 from functools import wraps
36 36
37 37 import ipaddress
38 38
39 39 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
40 40 from sqlalchemy.orm.exc import ObjectDeletedError
41 41 from sqlalchemy.orm import joinedload
42 42 from zope.cachedescriptors.property import Lazy as LazyProperty
43 43
44 44 import rhodecode
45 45 from rhodecode.model import meta
46 46 from rhodecode.model.meta import Session
47 47 from rhodecode.model.user import UserModel
48 48 from rhodecode.model.db import (
49 49 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
50 50 UserIpMap, UserApiKeys, RepoGroup, UserGroup)
51 51 from rhodecode.lib import rc_cache
52 52 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5, safe_int, sha1
53 53 from rhodecode.lib.utils import (
54 54 get_repo_slug, get_repo_group_slug, get_user_group_slug)
55 55 from rhodecode.lib.caching_query import FromCache
56 56
57 57
58 58 if rhodecode.is_unix:
59 59 import bcrypt
60 60
61 61 log = logging.getLogger(__name__)
62 62
63 63 csrf_token_key = "csrf_token"
64 64
65 65
66 66 class PasswordGenerator(object):
67 67 """
68 68 This is a simple class for generating password from different sets of
69 69 characters
70 70 usage::
71 71 passwd_gen = PasswordGenerator()
72 72 #print 8-letter password containing only big and small letters
73 73 of alphabet
74 74 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
75 75 """
76 76 ALPHABETS_NUM = r'''1234567890'''
77 77 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
78 78 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
79 79 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
80 80 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
81 81 + ALPHABETS_NUM + ALPHABETS_SPECIAL
82 82 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
83 83 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
84 84 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
85 85 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
86 86
87 87 def __init__(self, passwd=''):
88 88 self.passwd = passwd
89 89
90 90 def gen_password(self, length, type_=None):
91 91 if type_ is None:
92 92 type_ = self.ALPHABETS_FULL
93 93 self.passwd = ''.join([random.choice(type_) for _ in range(length)])
94 94 return self.passwd
95 95
96 96
97 97 class _RhodeCodeCryptoBase(object):
98 98 ENC_PREF = None
99 99
100 100 def hash_create(self, str_):
101 101 """
102 102 hash the string using
103 103
104 104 :param str_: password to hash
105 105 """
106 106 raise NotImplementedError
107 107
108 108 def hash_check_with_upgrade(self, password, hashed):
109 109 """
110 110 Returns tuple in which first element is boolean that states that
111 111 given password matches it's hashed version, and the second is new hash
112 112 of the password, in case this password should be migrated to new
113 113 cipher.
114 114 """
115 115 checked_hash = self.hash_check(password, hashed)
116 116 return checked_hash, None
117 117
118 118 def hash_check(self, password, hashed):
119 119 """
120 120 Checks matching password with it's hashed value.
121 121
122 122 :param password: password
123 123 :param hashed: password in hashed form
124 124 """
125 125 raise NotImplementedError
126 126
127 127 def _assert_bytes(self, value):
128 128 """
129 129 Passing in an `unicode` object can lead to hard to detect issues
130 130 if passwords contain non-ascii characters. Doing a type check
131 131 during runtime, so that such mistakes are detected early on.
132 132 """
133 133 if not isinstance(value, str):
134 134 raise TypeError(
135 135 "Bytestring required as input, got %r." % (value, ))
136 136
137 137
138 138 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
139 139 ENC_PREF = ('$2a$10', '$2b$10')
140 140
141 141 def hash_create(self, str_):
142 142 self._assert_bytes(str_)
143 143 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
144 144
145 145 def hash_check_with_upgrade(self, password, hashed):
146 146 """
147 147 Returns tuple in which first element is boolean that states that
148 148 given password matches it's hashed version, and the second is new hash
149 149 of the password, in case this password should be migrated to new
150 150 cipher.
151 151
152 152 This implements special upgrade logic which works like that:
153 153 - check if the given password == bcrypted hash, if yes then we
154 154 properly used password and it was already in bcrypt. Proceed
155 155 without any changes
156 156 - if bcrypt hash check is not working try with sha256. If hash compare
157 157 is ok, it means we using correct but old hashed password. indicate
158 158 hash change and proceed
159 159 """
160 160
161 161 new_hash = None
162 162
163 163 # regular pw check
164 164 password_match_bcrypt = self.hash_check(password, hashed)
165 165
166 166 # now we want to know if the password was maybe from sha256
167 167 # basically calling _RhodeCodeCryptoSha256().hash_check()
168 168 if not password_match_bcrypt:
169 169 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
170 170 new_hash = self.hash_create(password) # make new bcrypt hash
171 171 password_match_bcrypt = True
172 172
173 173 return password_match_bcrypt, new_hash
174 174
175 175 def hash_check(self, password, hashed):
176 176 """
177 177 Checks matching password with it's hashed value.
178 178
179 179 :param password: password
180 180 :param hashed: password in hashed form
181 181 """
182 182 self._assert_bytes(password)
183 183 try:
184 184 return bcrypt.hashpw(password, hashed) == hashed
185 185 except ValueError as e:
186 186 # we're having a invalid salt here probably, we should not crash
187 187 # just return with False as it would be a wrong password.
188 188 log.debug('Failed to check password hash using bcrypt %s',
189 189 safe_str(e))
190 190
191 191 return False
192 192
193 193
194 194 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
195 195 ENC_PREF = '_'
196 196
197 197 def hash_create(self, str_):
198 198 self._assert_bytes(str_)
199 199 return hashlib.sha256(str_).hexdigest()
200 200
201 201 def hash_check(self, password, hashed):
202 202 """
203 203 Checks matching password with it's hashed value.
204 204
205 205 :param password: password
206 206 :param hashed: password in hashed form
207 207 """
208 208 self._assert_bytes(password)
209 209 return hashlib.sha256(password).hexdigest() == hashed
210 210
211 211
212 212 class _RhodeCodeCryptoTest(_RhodeCodeCryptoBase):
213 213 ENC_PREF = '_'
214 214
215 215 def hash_create(self, str_):
216 216 self._assert_bytes(str_)
217 217 return sha1(str_)
218 218
219 219 def hash_check(self, password, hashed):
220 220 """
221 221 Checks matching password with it's hashed value.
222 222
223 223 :param password: password
224 224 :param hashed: password in hashed form
225 225 """
226 226 self._assert_bytes(password)
227 227 return sha1(password) == hashed
228 228
229 229
230 230 def crypto_backend():
231 231 """
232 232 Return the matching crypto backend.
233 233
234 234 Selection is based on if we run tests or not, we pick sha1-test backend to run
235 235 tests faster since BCRYPT is expensive to calculate
236 236 """
237 237 if rhodecode.is_test:
238 238 RhodeCodeCrypto = _RhodeCodeCryptoTest()
239 239 else:
240 240 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
241 241
242 242 return RhodeCodeCrypto
243 243
244 244
245 245 def get_crypt_password(password):
246 246 """
247 247 Create the hash of `password` with the active crypto backend.
248 248
249 249 :param password: The cleartext password.
250 250 :type password: unicode
251 251 """
252 252 password = safe_str(password)
253 253 return crypto_backend().hash_create(password)
254 254
255 255
256 256 def check_password(password, hashed):
257 257 """
258 258 Check if the value in `password` matches the hash in `hashed`.
259 259
260 260 :param password: The cleartext password.
261 261 :type password: unicode
262 262
263 263 :param hashed: The expected hashed version of the password.
264 264 :type hashed: The hash has to be passed in in text representation.
265 265 """
266 266 password = safe_str(password)
267 267 return crypto_backend().hash_check(password, hashed)
268 268
269 269
270 270 def generate_auth_token(data, salt=None):
271 271 """
272 272 Generates API KEY from given string
273 273 """
274 274
275 275 if salt is None:
276 276 salt = os.urandom(16)
277 277 return hashlib.sha1(safe_str(data) + salt).hexdigest()
278 278
279 279
280 280 def get_came_from(request):
281 281 """
282 282 get query_string+path from request sanitized after removing auth_token
283 283 """
284 284 _req = request
285 285
286 286 path = _req.path
287 287 if 'auth_token' in _req.GET:
288 288 # sanitize the request and remove auth_token for redirection
289 289 _req.GET.pop('auth_token')
290 290 qs = _req.query_string
291 291 if qs:
292 292 path += '?' + qs
293 293
294 294 return path
295 295
296 296
297 297 class CookieStoreWrapper(object):
298 298
299 299 def __init__(self, cookie_store):
300 300 self.cookie_store = cookie_store
301 301
302 302 def __repr__(self):
303 303 return 'CookieStore<%s>' % (self.cookie_store)
304 304
305 305 def get(self, key, other=None):
306 306 if isinstance(self.cookie_store, dict):
307 307 return self.cookie_store.get(key, other)
308 308 elif isinstance(self.cookie_store, AuthUser):
309 309 return self.cookie_store.__dict__.get(key, other)
310 310
311 311
312 312 def _cached_perms_data(user_id, scope, user_is_admin,
313 313 user_inherit_default_permissions, explicit, algo,
314 314 calculate_super_admin):
315 315
316 316 permissions = PermissionCalculator(
317 317 user_id, scope, user_is_admin, user_inherit_default_permissions,
318 318 explicit, algo, calculate_super_admin)
319 319 return permissions.calculate()
320 320
321 321
322 322 class PermOrigin(object):
323 323 SUPER_ADMIN = 'superadmin'
324 324 ARCHIVED = 'archived'
325 325
326 326 REPO_USER = 'user:%s'
327 327 REPO_USERGROUP = 'usergroup:%s'
328 328 REPO_OWNER = 'repo.owner'
329 329 REPO_DEFAULT = 'repo.default'
330 330 REPO_DEFAULT_NO_INHERIT = 'repo.default.no.inherit'
331 331 REPO_PRIVATE = 'repo.private'
332 332
333 333 REPOGROUP_USER = 'user:%s'
334 334 REPOGROUP_USERGROUP = 'usergroup:%s'
335 335 REPOGROUP_OWNER = 'group.owner'
336 336 REPOGROUP_DEFAULT = 'group.default'
337 337 REPOGROUP_DEFAULT_NO_INHERIT = 'group.default.no.inherit'
338 338
339 339 USERGROUP_USER = 'user:%s'
340 340 USERGROUP_USERGROUP = 'usergroup:%s'
341 341 USERGROUP_OWNER = 'usergroup.owner'
342 342 USERGROUP_DEFAULT = 'usergroup.default'
343 343 USERGROUP_DEFAULT_NO_INHERIT = 'usergroup.default.no.inherit'
344 344
345 345
346 346 class PermOriginDict(dict):
347 347 """
348 348 A special dict used for tracking permissions along with their origins.
349 349
350 350 `__setitem__` has been overridden to expect a tuple(perm, origin)
351 351 `__getitem__` will return only the perm
352 352 `.perm_origin_stack` will return the stack of (perm, origin) set per key
353 353
354 354 >>> perms = PermOriginDict()
355 355 >>> perms['resource'] = 'read', 'default'
356 356 >>> perms['resource']
357 357 'read'
358 358 >>> perms['resource'] = 'write', 'admin'
359 359 >>> perms['resource']
360 360 'write'
361 361 >>> perms.perm_origin_stack
362 362 {'resource': [('read', 'default'), ('write', 'admin')]}
363 363 """
364 364
365 365 def __init__(self, *args, **kw):
366 366 dict.__init__(self, *args, **kw)
367 367 self.perm_origin_stack = collections.OrderedDict()
368 368
369 369 def __setitem__(self, key, (perm, origin)):
370 370 self.perm_origin_stack.setdefault(key, []).append(
371 371 (perm, origin))
372 372 dict.__setitem__(self, key, perm)
373 373
374 374
375 375 class BranchPermOriginDict(PermOriginDict):
376 376 """
377 377 Dedicated branch permissions dict, with tracking of patterns and origins.
378 378
379 379 >>> perms = BranchPermOriginDict()
380 380 >>> perms['resource'] = '*pattern', 'read', 'default'
381 381 >>> perms['resource']
382 382 {'*pattern': 'read'}
383 383 >>> perms['resource'] = '*pattern', 'write', 'admin'
384 384 >>> perms['resource']
385 385 {'*pattern': 'write'}
386 386 >>> perms.perm_origin_stack
387 387 {'resource': {'*pattern': [('read', 'default'), ('write', 'admin')]}}
388 388 """
389 389 def __setitem__(self, key, (pattern, perm, origin)):
390 390
391 391 self.perm_origin_stack.setdefault(key, {}) \
392 392 .setdefault(pattern, []).append((perm, origin))
393 393
394 394 if key in self:
395 395 self[key].__setitem__(pattern, perm)
396 396 else:
397 397 patterns = collections.OrderedDict()
398 398 patterns[pattern] = perm
399 399 dict.__setitem__(self, key, patterns)
400 400
401 401
402 402 class PermissionCalculator(object):
403 403
404 404 def __init__(
405 405 self, user_id, scope, user_is_admin,
406 406 user_inherit_default_permissions, explicit, algo,
407 407 calculate_super_admin_as_user=False):
408 408
409 409 self.user_id = user_id
410 410 self.user_is_admin = user_is_admin
411 411 self.inherit_default_permissions = user_inherit_default_permissions
412 412 self.explicit = explicit
413 413 self.algo = algo
414 414 self.calculate_super_admin_as_user = calculate_super_admin_as_user
415 415
416 416 scope = scope or {}
417 417 self.scope_repo_id = scope.get('repo_id')
418 418 self.scope_repo_group_id = scope.get('repo_group_id')
419 419 self.scope_user_group_id = scope.get('user_group_id')
420 420
421 421 self.default_user_id = User.get_default_user(cache=True).user_id
422 422
423 423 self.permissions_repositories = PermOriginDict()
424 424 self.permissions_repository_groups = PermOriginDict()
425 425 self.permissions_user_groups = PermOriginDict()
426 426 self.permissions_repository_branches = BranchPermOriginDict()
427 427 self.permissions_global = set()
428 428
429 429 self.default_repo_perms = Permission.get_default_repo_perms(
430 430 self.default_user_id, self.scope_repo_id)
431 431 self.default_repo_groups_perms = Permission.get_default_group_perms(
432 432 self.default_user_id, self.scope_repo_group_id)
433 433 self.default_user_group_perms = \
434 434 Permission.get_default_user_group_perms(
435 435 self.default_user_id, self.scope_user_group_id)
436 436
437 437 # default branch perms
438 438 self.default_branch_repo_perms = \
439 439 Permission.get_default_repo_branch_perms(
440 440 self.default_user_id, self.scope_repo_id)
441 441
442 442 def calculate(self):
443 443 if self.user_is_admin and not self.calculate_super_admin_as_user:
444 444 return self._calculate_admin_permissions()
445 445
446 446 self._calculate_global_default_permissions()
447 447 self._calculate_global_permissions()
448 448 self._calculate_default_permissions()
449 449 self._calculate_repository_permissions()
450 450 self._calculate_repository_branch_permissions()
451 451 self._calculate_repository_group_permissions()
452 452 self._calculate_user_group_permissions()
453 453 return self._permission_structure()
454 454
455 455 def _calculate_admin_permissions(self):
456 456 """
457 457 admin user have all default rights for repositories
458 458 and groups set to admin
459 459 """
460 460 self.permissions_global.add('hg.admin')
461 461 self.permissions_global.add('hg.create.write_on_repogroup.true')
462 462
463 463 # repositories
464 464 for perm in self.default_repo_perms:
465 465 r_k = perm.UserRepoToPerm.repository.repo_name
466 466 archived = perm.UserRepoToPerm.repository.archived
467 467 p = 'repository.admin'
468 468 self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN
469 469 # special case for archived repositories, which we block still even for
470 470 # super admins
471 471 if archived:
472 472 p = 'repository.read'
473 473 self.permissions_repositories[r_k] = p, PermOrigin.ARCHIVED
474 474
475 475 # repository groups
476 476 for perm in self.default_repo_groups_perms:
477 477 rg_k = perm.UserRepoGroupToPerm.group.group_name
478 478 p = 'group.admin'
479 479 self.permissions_repository_groups[rg_k] = p, PermOrigin.SUPER_ADMIN
480 480
481 481 # user groups
482 482 for perm in self.default_user_group_perms:
483 483 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
484 484 p = 'usergroup.admin'
485 485 self.permissions_user_groups[u_k] = p, PermOrigin.SUPER_ADMIN
486 486
487 487 # branch permissions
488 488 # since super-admin also can have custom rule permissions
489 489 # we *always* need to calculate those inherited from default, and also explicit
490 490 self._calculate_default_permissions_repository_branches(
491 491 user_inherit_object_permissions=False)
492 492 self._calculate_repository_branch_permissions()
493 493
494 494 return self._permission_structure()
495 495
496 496 def _calculate_global_default_permissions(self):
497 497 """
498 498 global permissions taken from the default user
499 499 """
500 500 default_global_perms = UserToPerm.query()\
501 501 .filter(UserToPerm.user_id == self.default_user_id)\
502 502 .options(joinedload(UserToPerm.permission))
503 503
504 504 for perm in default_global_perms:
505 505 self.permissions_global.add(perm.permission.permission_name)
506 506
507 507 if self.user_is_admin:
508 508 self.permissions_global.add('hg.admin')
509 509 self.permissions_global.add('hg.create.write_on_repogroup.true')
510 510
511 511 def _calculate_global_permissions(self):
512 512 """
513 513 Set global system permissions with user permissions or permissions
514 514 taken from the user groups of the current user.
515 515
516 516 The permissions include repo creating, repo group creating, forking
517 517 etc.
518 518 """
519 519
520 520 # now we read the defined permissions and overwrite what we have set
521 521 # before those can be configured from groups or users explicitly.
522 522
523 523 # In case we want to extend this list we should make sure
524 524 # this is in sync with User.DEFAULT_USER_PERMISSIONS definitions
525 525 _configurable = frozenset([
526 526 'hg.fork.none', 'hg.fork.repository',
527 527 'hg.create.none', 'hg.create.repository',
528 528 'hg.usergroup.create.false', 'hg.usergroup.create.true',
529 529 'hg.repogroup.create.false', 'hg.repogroup.create.true',
530 530 'hg.create.write_on_repogroup.false', 'hg.create.write_on_repogroup.true',
531 531 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
532 532 ])
533 533
534 534 # USER GROUPS comes first user group global permissions
535 535 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
536 536 .options(joinedload(UserGroupToPerm.permission))\
537 537 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
538 538 UserGroupMember.users_group_id))\
539 539 .filter(UserGroupMember.user_id == self.user_id)\
540 540 .order_by(UserGroupToPerm.users_group_id)\
541 541 .all()
542 542
543 543 # need to group here by groups since user can be in more than
544 544 # one group, so we get all groups
545 545 _explicit_grouped_perms = [
546 546 [x, list(y)] for x, y in
547 547 itertools.groupby(user_perms_from_users_groups,
548 548 lambda _x: _x.users_group)]
549 549
550 550 for gr, perms in _explicit_grouped_perms:
551 551 # since user can be in multiple groups iterate over them and
552 552 # select the lowest permissions first (more explicit)
553 553 # TODO(marcink): do this^^
554 554
555 555 # group doesn't inherit default permissions so we actually set them
556 556 if not gr.inherit_default_permissions:
557 557 # NEED TO IGNORE all previously set configurable permissions
558 558 # and replace them with explicitly set from this user
559 559 # group permissions
560 560 self.permissions_global = self.permissions_global.difference(
561 561 _configurable)
562 562 for perm in perms:
563 563 self.permissions_global.add(perm.permission.permission_name)
564 564
565 565 # user explicit global permissions
566 566 user_perms = Session().query(UserToPerm)\
567 567 .options(joinedload(UserToPerm.permission))\
568 568 .filter(UserToPerm.user_id == self.user_id).all()
569 569
570 570 if not self.inherit_default_permissions:
571 571 # NEED TO IGNORE all configurable permissions and
572 572 # replace them with explicitly set from this user permissions
573 573 self.permissions_global = self.permissions_global.difference(
574 574 _configurable)
575 575 for perm in user_perms:
576 576 self.permissions_global.add(perm.permission.permission_name)
577 577
578 578 def _calculate_default_permissions_repositories(self, user_inherit_object_permissions):
579 579 for perm in self.default_repo_perms:
580 580 r_k = perm.UserRepoToPerm.repository.repo_name
581 581 archived = perm.UserRepoToPerm.repository.archived
582 582 p = perm.Permission.permission_name
583 583 o = PermOrigin.REPO_DEFAULT
584 584 self.permissions_repositories[r_k] = p, o
585 585
586 586 # if we decide this user isn't inheriting permissions from
587 587 # default user we set him to .none so only explicit
588 588 # permissions work
589 589 if not user_inherit_object_permissions:
590 590 p = 'repository.none'
591 591 o = PermOrigin.REPO_DEFAULT_NO_INHERIT
592 592 self.permissions_repositories[r_k] = p, o
593 593
594 594 if perm.Repository.private and not (
595 595 perm.Repository.user_id == self.user_id):
596 596 # disable defaults for private repos,
597 597 p = 'repository.none'
598 598 o = PermOrigin.REPO_PRIVATE
599 599 self.permissions_repositories[r_k] = p, o
600 600
601 601 elif perm.Repository.user_id == self.user_id:
602 602 # set admin if owner
603 603 p = 'repository.admin'
604 604 o = PermOrigin.REPO_OWNER
605 605 self.permissions_repositories[r_k] = p, o
606 606
607 607 if self.user_is_admin:
608 608 p = 'repository.admin'
609 609 o = PermOrigin.SUPER_ADMIN
610 610 self.permissions_repositories[r_k] = p, o
611 611
612 612 # finally in case of archived repositories, we downgrade higher
613 613 # permissions to read
614 614 if archived:
615 615 current_perm = self.permissions_repositories[r_k]
616 616 if current_perm in ['repository.write', 'repository.admin']:
617 617 p = 'repository.read'
618 618 o = PermOrigin.ARCHIVED
619 619 self.permissions_repositories[r_k] = p, o
620 620
621 621 def _calculate_default_permissions_repository_branches(self, user_inherit_object_permissions):
622 622 for perm in self.default_branch_repo_perms:
623 623
624 624 r_k = perm.UserRepoToPerm.repository.repo_name
625 625 p = perm.Permission.permission_name
626 626 pattern = perm.UserToRepoBranchPermission.branch_pattern
627 627 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
628 628
629 629 if not self.explicit:
630 630 cur_perm = self.permissions_repository_branches.get(r_k)
631 631 if cur_perm:
632 632 cur_perm = cur_perm[pattern]
633 633 cur_perm = cur_perm or 'branch.none'
634 634
635 635 p = self._choose_permission(p, cur_perm)
636 636
637 637 # NOTE(marcink): register all pattern/perm instances in this
638 638 # special dict that aggregates entries
639 639 self.permissions_repository_branches[r_k] = pattern, p, o
640 640
641 641 def _calculate_default_permissions_repository_groups(self, user_inherit_object_permissions):
642 642 for perm in self.default_repo_groups_perms:
643 643 rg_k = perm.UserRepoGroupToPerm.group.group_name
644 644 p = perm.Permission.permission_name
645 645 o = PermOrigin.REPOGROUP_DEFAULT
646 646 self.permissions_repository_groups[rg_k] = p, o
647 647
648 648 # if we decide this user isn't inheriting permissions from default
649 649 # user we set him to .none so only explicit permissions work
650 650 if not user_inherit_object_permissions:
651 651 p = 'group.none'
652 652 o = PermOrigin.REPOGROUP_DEFAULT_NO_INHERIT
653 653 self.permissions_repository_groups[rg_k] = p, o
654 654
655 655 if perm.RepoGroup.user_id == self.user_id:
656 656 # set admin if owner
657 657 p = 'group.admin'
658 658 o = PermOrigin.REPOGROUP_OWNER
659 659 self.permissions_repository_groups[rg_k] = p, o
660 660
661 661 if self.user_is_admin:
662 662 p = 'group.admin'
663 663 o = PermOrigin.SUPER_ADMIN
664 664 self.permissions_repository_groups[rg_k] = p, o
665 665
666 666 def _calculate_default_permissions_user_groups(self, user_inherit_object_permissions):
667 667 for perm in self.default_user_group_perms:
668 668 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
669 669 p = perm.Permission.permission_name
670 670 o = PermOrigin.USERGROUP_DEFAULT
671 671 self.permissions_user_groups[u_k] = p, o
672 672
673 673 # if we decide this user isn't inheriting permissions from default
674 674 # user we set him to .none so only explicit permissions work
675 675 if not user_inherit_object_permissions:
676 676 p = 'usergroup.none'
677 677 o = PermOrigin.USERGROUP_DEFAULT_NO_INHERIT
678 678 self.permissions_user_groups[u_k] = p, o
679 679
680 680 if perm.UserGroup.user_id == self.user_id:
681 681 # set admin if owner
682 682 p = 'usergroup.admin'
683 683 o = PermOrigin.USERGROUP_OWNER
684 684 self.permissions_user_groups[u_k] = p, o
685 685
686 686 if self.user_is_admin:
687 687 p = 'usergroup.admin'
688 688 o = PermOrigin.SUPER_ADMIN
689 689 self.permissions_user_groups[u_k] = p, o
690 690
691 691 def _calculate_default_permissions(self):
692 692 """
693 693 Set default user permissions for repositories, repository branches,
694 694 repository groups, user groups taken from the default user.
695 695
696 696 Calculate inheritance of object permissions based on what we have now
697 697 in GLOBAL permissions. We check if .false is in GLOBAL since this is
698 698 explicitly set. Inherit is the opposite of .false being there.
699 699
700 700 .. note::
701 701
702 702 the syntax is little bit odd but what we need to check here is
703 703 the opposite of .false permission being in the list so even for
704 704 inconsistent state when both .true/.false is there
705 705 .false is more important
706 706
707 707 """
708 708 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
709 709 in self.permissions_global)
710 710
711 711 # default permissions inherited from `default` user permissions
712 712 self._calculate_default_permissions_repositories(
713 713 user_inherit_object_permissions)
714 714
715 715 self._calculate_default_permissions_repository_branches(
716 716 user_inherit_object_permissions)
717 717
718 718 self._calculate_default_permissions_repository_groups(
719 719 user_inherit_object_permissions)
720 720
721 721 self._calculate_default_permissions_user_groups(
722 722 user_inherit_object_permissions)
723 723
724 724 def _calculate_repository_permissions(self):
725 725 """
726 726 Repository permissions for the current user.
727 727
728 728 Check if the user is part of user groups for this repository and
729 729 fill in the permission from it. `_choose_permission` decides of which
730 730 permission should be selected based on selected method.
731 731 """
732 732
733 733 # user group for repositories permissions
734 734 user_repo_perms_from_user_group = Permission\
735 735 .get_default_repo_perms_from_user_group(
736 736 self.user_id, self.scope_repo_id)
737 737
738 738 multiple_counter = collections.defaultdict(int)
739 739 for perm in user_repo_perms_from_user_group:
740 740 r_k = perm.UserGroupRepoToPerm.repository.repo_name
741 741 multiple_counter[r_k] += 1
742 742 p = perm.Permission.permission_name
743 743 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
744 744 .users_group.users_group_name
745 745
746 746 if multiple_counter[r_k] > 1:
747 747 cur_perm = self.permissions_repositories[r_k]
748 748 p = self._choose_permission(p, cur_perm)
749 749
750 750 self.permissions_repositories[r_k] = p, o
751 751
752 752 if perm.Repository.user_id == self.user_id:
753 753 # set admin if owner
754 754 p = 'repository.admin'
755 755 o = PermOrigin.REPO_OWNER
756 756 self.permissions_repositories[r_k] = p, o
757 757
758 758 if self.user_is_admin:
759 759 p = 'repository.admin'
760 760 o = PermOrigin.SUPER_ADMIN
761 761 self.permissions_repositories[r_k] = p, o
762 762
763 763 # user explicit permissions for repositories, overrides any specified
764 764 # by the group permission
765 765 user_repo_perms = Permission.get_default_repo_perms(
766 766 self.user_id, self.scope_repo_id)
767 767 for perm in user_repo_perms:
768 768 r_k = perm.UserRepoToPerm.repository.repo_name
769 769 p = perm.Permission.permission_name
770 770 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
771 771
772 772 if not self.explicit:
773 773 cur_perm = self.permissions_repositories.get(
774 774 r_k, 'repository.none')
775 775 p = self._choose_permission(p, cur_perm)
776 776
777 777 self.permissions_repositories[r_k] = p, o
778 778
779 779 if perm.Repository.user_id == self.user_id:
780 780 # set admin if owner
781 781 p = 'repository.admin'
782 782 o = PermOrigin.REPO_OWNER
783 783 self.permissions_repositories[r_k] = p, o
784 784
785 785 if self.user_is_admin:
786 786 p = 'repository.admin'
787 787 o = PermOrigin.SUPER_ADMIN
788 788 self.permissions_repositories[r_k] = p, o
789 789
790 790 def _calculate_repository_branch_permissions(self):
791 791 # user group for repositories permissions
792 792 user_repo_branch_perms_from_user_group = Permission\
793 793 .get_default_repo_branch_perms_from_user_group(
794 794 self.user_id, self.scope_repo_id)
795 795
796 796 multiple_counter = collections.defaultdict(int)
797 797 for perm in user_repo_branch_perms_from_user_group:
798 798 r_k = perm.UserGroupRepoToPerm.repository.repo_name
799 799 p = perm.Permission.permission_name
800 800 pattern = perm.UserGroupToRepoBranchPermission.branch_pattern
801 801 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
802 802 .users_group.users_group_name
803 803
804 804 multiple_counter[r_k] += 1
805 805 if multiple_counter[r_k] > 1:
806 806 cur_perm = self.permissions_repository_branches[r_k][pattern]
807 807 p = self._choose_permission(p, cur_perm)
808 808
809 809 self.permissions_repository_branches[r_k] = pattern, p, o
810 810
811 811 # user explicit branch permissions for repositories, overrides
812 812 # any specified by the group permission
813 813 user_repo_branch_perms = Permission.get_default_repo_branch_perms(
814 814 self.user_id, self.scope_repo_id)
815 815
816 816 for perm in user_repo_branch_perms:
817 817
818 818 r_k = perm.UserRepoToPerm.repository.repo_name
819 819 p = perm.Permission.permission_name
820 820 pattern = perm.UserToRepoBranchPermission.branch_pattern
821 821 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
822 822
823 823 if not self.explicit:
824 824 cur_perm = self.permissions_repository_branches.get(r_k)
825 825 if cur_perm:
826 826 cur_perm = cur_perm[pattern]
827 827 cur_perm = cur_perm or 'branch.none'
828 828 p = self._choose_permission(p, cur_perm)
829 829
830 830 # NOTE(marcink): register all pattern/perm instances in this
831 831 # special dict that aggregates entries
832 832 self.permissions_repository_branches[r_k] = pattern, p, o
833 833
834 834 def _calculate_repository_group_permissions(self):
835 835 """
836 836 Repository group permissions for the current user.
837 837
838 838 Check if the user is part of user groups for repository groups and
839 839 fill in the permissions from it. `_choose_permission` decides of which
840 840 permission should be selected based on selected method.
841 841 """
842 842 # user group for repo groups permissions
843 843 user_repo_group_perms_from_user_group = Permission\
844 844 .get_default_group_perms_from_user_group(
845 845 self.user_id, self.scope_repo_group_id)
846 846
847 847 multiple_counter = collections.defaultdict(int)
848 848 for perm in user_repo_group_perms_from_user_group:
849 849 rg_k = perm.UserGroupRepoGroupToPerm.group.group_name
850 850 multiple_counter[rg_k] += 1
851 851 o = PermOrigin.REPOGROUP_USERGROUP % perm.UserGroupRepoGroupToPerm\
852 852 .users_group.users_group_name
853 853 p = perm.Permission.permission_name
854 854
855 855 if multiple_counter[rg_k] > 1:
856 856 cur_perm = self.permissions_repository_groups[rg_k]
857 857 p = self._choose_permission(p, cur_perm)
858 858 self.permissions_repository_groups[rg_k] = p, o
859 859
860 860 if perm.RepoGroup.user_id == self.user_id:
861 861 # set admin if owner, even for member of other user group
862 862 p = 'group.admin'
863 863 o = PermOrigin.REPOGROUP_OWNER
864 864 self.permissions_repository_groups[rg_k] = p, o
865 865
866 866 if self.user_is_admin:
867 867 p = 'group.admin'
868 868 o = PermOrigin.SUPER_ADMIN
869 869 self.permissions_repository_groups[rg_k] = p, o
870 870
871 871 # user explicit permissions for repository groups
872 872 user_repo_groups_perms = Permission.get_default_group_perms(
873 873 self.user_id, self.scope_repo_group_id)
874 874 for perm in user_repo_groups_perms:
875 875 rg_k = perm.UserRepoGroupToPerm.group.group_name
876 876 o = PermOrigin.REPOGROUP_USER % perm.UserRepoGroupToPerm\
877 877 .user.username
878 878 p = perm.Permission.permission_name
879 879
880 880 if not self.explicit:
881 881 cur_perm = self.permissions_repository_groups.get(rg_k, 'group.none')
882 882 p = self._choose_permission(p, cur_perm)
883 883
884 884 self.permissions_repository_groups[rg_k] = p, o
885 885
886 886 if perm.RepoGroup.user_id == self.user_id:
887 887 # set admin if owner
888 888 p = 'group.admin'
889 889 o = PermOrigin.REPOGROUP_OWNER
890 890 self.permissions_repository_groups[rg_k] = p, o
891 891
892 892 if self.user_is_admin:
893 893 p = 'group.admin'
894 894 o = PermOrigin.SUPER_ADMIN
895 895 self.permissions_repository_groups[rg_k] = p, o
896 896
897 897 def _calculate_user_group_permissions(self):
898 898 """
899 899 User group permissions for the current user.
900 900 """
901 901 # user group for user group permissions
902 902 user_group_from_user_group = Permission\
903 903 .get_default_user_group_perms_from_user_group(
904 904 self.user_id, self.scope_user_group_id)
905 905
906 906 multiple_counter = collections.defaultdict(int)
907 907 for perm in user_group_from_user_group:
908 908 ug_k = perm.UserGroupUserGroupToPerm\
909 909 .target_user_group.users_group_name
910 910 multiple_counter[ug_k] += 1
911 911 o = PermOrigin.USERGROUP_USERGROUP % perm.UserGroupUserGroupToPerm\
912 912 .user_group.users_group_name
913 913 p = perm.Permission.permission_name
914 914
915 915 if multiple_counter[ug_k] > 1:
916 916 cur_perm = self.permissions_user_groups[ug_k]
917 917 p = self._choose_permission(p, cur_perm)
918 918
919 919 self.permissions_user_groups[ug_k] = p, o
920 920
921 921 if perm.UserGroup.user_id == self.user_id:
922 922 # set admin if owner, even for member of other user group
923 923 p = 'usergroup.admin'
924 924 o = PermOrigin.USERGROUP_OWNER
925 925 self.permissions_user_groups[ug_k] = p, o
926 926
927 927 if self.user_is_admin:
928 928 p = 'usergroup.admin'
929 929 o = PermOrigin.SUPER_ADMIN
930 930 self.permissions_user_groups[ug_k] = p, o
931 931
932 932 # user explicit permission for user groups
933 933 user_user_groups_perms = Permission.get_default_user_group_perms(
934 934 self.user_id, self.scope_user_group_id)
935 935 for perm in user_user_groups_perms:
936 936 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
937 937 o = PermOrigin.USERGROUP_USER % perm.UserUserGroupToPerm\
938 938 .user.username
939 939 p = perm.Permission.permission_name
940 940
941 941 if not self.explicit:
942 942 cur_perm = self.permissions_user_groups.get(ug_k, 'usergroup.none')
943 943 p = self._choose_permission(p, cur_perm)
944 944
945 945 self.permissions_user_groups[ug_k] = p, o
946 946
947 947 if perm.UserGroup.user_id == self.user_id:
948 948 # set admin if owner
949 949 p = 'usergroup.admin'
950 950 o = PermOrigin.USERGROUP_OWNER
951 951 self.permissions_user_groups[ug_k] = p, o
952 952
953 953 if self.user_is_admin:
954 954 p = 'usergroup.admin'
955 955 o = PermOrigin.SUPER_ADMIN
956 956 self.permissions_user_groups[ug_k] = p, o
957 957
958 958 def _choose_permission(self, new_perm, cur_perm):
959 959 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
960 960 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
961 961 if self.algo == 'higherwin':
962 962 if new_perm_val > cur_perm_val:
963 963 return new_perm
964 964 return cur_perm
965 965 elif self.algo == 'lowerwin':
966 966 if new_perm_val < cur_perm_val:
967 967 return new_perm
968 968 return cur_perm
969 969
970 970 def _permission_structure(self):
971 971 return {
972 972 'global': self.permissions_global,
973 973 'repositories': self.permissions_repositories,
974 974 'repository_branches': self.permissions_repository_branches,
975 975 'repositories_groups': self.permissions_repository_groups,
976 976 'user_groups': self.permissions_user_groups,
977 977 }
978 978
979 979
980 980 def allowed_auth_token_access(view_name, auth_token, whitelist=None):
981 981 """
982 982 Check if given controller_name is in whitelist of auth token access
983 983 """
984 984 if not whitelist:
985 985 from rhodecode import CONFIG
986 986 whitelist = aslist(
987 987 CONFIG.get('api_access_controllers_whitelist'), sep=',')
988 988 # backward compat translation
989 989 compat = {
990 990 # old controller, new VIEW
991 991 'ChangesetController:*': 'RepoCommitsView:*',
992 992 'ChangesetController:changeset_patch': 'RepoCommitsView:repo_commit_patch',
993 993 'ChangesetController:changeset_raw': 'RepoCommitsView:repo_commit_raw',
994 994 'FilesController:raw': 'RepoCommitsView:repo_commit_raw',
995 995 'FilesController:archivefile': 'RepoFilesView:repo_archivefile',
996 996 'GistsController:*': 'GistView:*',
997 997 }
998 998
999 999 log.debug(
1000 1000 'Allowed views for AUTH TOKEN access: %s', whitelist)
1001 1001 auth_token_access_valid = False
1002 1002
1003 1003 for entry in whitelist:
1004 1004 token_match = True
1005 1005 if entry in compat:
1006 1006 # translate from old Controllers to Pyramid Views
1007 1007 entry = compat[entry]
1008 1008
1009 1009 if '@' in entry:
1010 1010 # specific AuthToken
1011 1011 entry, allowed_token = entry.split('@', 1)
1012 1012 token_match = auth_token == allowed_token
1013 1013
1014 1014 if fnmatch.fnmatch(view_name, entry) and token_match:
1015 1015 auth_token_access_valid = True
1016 1016 break
1017 1017
1018 1018 if auth_token_access_valid:
1019 1019 log.debug('view: `%s` matches entry in whitelist: %s',
1020 1020 view_name, whitelist)
1021 1021
1022 1022 else:
1023 1023 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
1024 1024 % (view_name, whitelist))
1025 1025 if auth_token:
1026 1026 # if we use auth token key and don't have access it's a warning
1027 1027 log.warning(msg)
1028 1028 else:
1029 1029 log.debug(msg)
1030 1030
1031 1031 return auth_token_access_valid
1032 1032
1033 1033
1034 1034 class AuthUser(object):
1035 1035 """
1036 1036 A simple object that handles all attributes of user in RhodeCode
1037 1037
1038 1038 It does lookup based on API key,given user, or user present in session
1039 1039 Then it fills all required information for such user. It also checks if
1040 1040 anonymous access is enabled and if so, it returns default user as logged in
1041 1041 """
1042 1042 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
1043 1043
1044 1044 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
1045 1045
1046 1046 self.user_id = user_id
1047 1047 self._api_key = api_key
1048 1048
1049 1049 self.api_key = None
1050 1050 self.username = username
1051 1051 self.ip_addr = ip_addr
1052 1052 self.name = ''
1053 1053 self.lastname = ''
1054 1054 self.first_name = ''
1055 1055 self.last_name = ''
1056 1056 self.email = ''
1057 1057 self.is_authenticated = False
1058 1058 self.admin = False
1059 1059 self.inherit_default_permissions = False
1060 1060 self.password = ''
1061 1061
1062 1062 self.anonymous_user = None # propagated on propagate_data
1063 1063 self.propagate_data()
1064 1064 self._instance = None
1065 1065 self._permissions_scoped_cache = {} # used to bind scoped calculation
1066 1066
1067 1067 @LazyProperty
1068 1068 def permissions(self):
1069 1069 return self.get_perms(user=self, cache=None)
1070 1070
1071 1071 @LazyProperty
1072 1072 def permissions_safe(self):
1073 1073 """
1074 1074 Filtered permissions excluding not allowed repositories
1075 1075 """
1076 1076 perms = self.get_perms(user=self, cache=None)
1077 1077
1078 1078 perms['repositories'] = {
1079 1079 k: v for k, v in perms['repositories'].items()
1080 1080 if v != 'repository.none'}
1081 1081 perms['repositories_groups'] = {
1082 1082 k: v for k, v in perms['repositories_groups'].items()
1083 1083 if v != 'group.none'}
1084 1084 perms['user_groups'] = {
1085 1085 k: v for k, v in perms['user_groups'].items()
1086 1086 if v != 'usergroup.none'}
1087 1087 perms['repository_branches'] = {
1088 1088 k: v for k, v in perms['repository_branches'].iteritems()
1089 1089 if v != 'branch.none'}
1090 1090 return perms
1091 1091
1092 1092 @LazyProperty
1093 1093 def permissions_full_details(self):
1094 1094 return self.get_perms(
1095 1095 user=self, cache=None, calculate_super_admin=True)
1096 1096
1097 1097 def permissions_with_scope(self, scope):
1098 1098 """
1099 1099 Call the get_perms function with scoped data. The scope in that function
1100 1100 narrows the SQL calls to the given ID of objects resulting in fetching
1101 1101 Just particular permission we want to obtain. If scope is an empty dict
1102 1102 then it basically narrows the scope to GLOBAL permissions only.
1103 1103
1104 1104 :param scope: dict
1105 1105 """
1106 1106 if 'repo_name' in scope:
1107 1107 obj = Repository.get_by_repo_name(scope['repo_name'])
1108 1108 if obj:
1109 1109 scope['repo_id'] = obj.repo_id
1110 1110 _scope = collections.OrderedDict()
1111 1111 _scope['repo_id'] = -1
1112 1112 _scope['user_group_id'] = -1
1113 1113 _scope['repo_group_id'] = -1
1114 1114
1115 1115 for k in sorted(scope.keys()):
1116 1116 _scope[k] = scope[k]
1117 1117
1118 1118 # store in cache to mimic how the @LazyProperty works,
1119 1119 # the difference here is that we use the unique key calculated
1120 1120 # from params and values
1121 1121 return self.get_perms(user=self, cache=None, scope=_scope)
1122 1122
1123 1123 def get_instance(self):
1124 1124 return User.get(self.user_id)
1125 1125
1126 1126 def propagate_data(self):
1127 1127 """
1128 1128 Fills in user data and propagates values to this instance. Maps fetched
1129 1129 user attributes to this class instance attributes
1130 1130 """
1131 1131 log.debug('AuthUser: starting data propagation for new potential user')
1132 1132 user_model = UserModel()
1133 1133 anon_user = self.anonymous_user = User.get_default_user(cache=True)
1134 1134 is_user_loaded = False
1135 1135
1136 1136 # lookup by userid
1137 1137 if self.user_id is not None and self.user_id != anon_user.user_id:
1138 1138 log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id)
1139 1139 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
1140 1140
1141 1141 # try go get user by api key
1142 1142 elif self._api_key and self._api_key != anon_user.api_key:
1143 1143 log.debug('Trying Auth User lookup by API KEY: `%s`', self._api_key)
1144 1144 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
1145 1145
1146 1146 # lookup by username
1147 1147 elif self.username:
1148 1148 log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username)
1149 1149 is_user_loaded = user_model.fill_data(self, username=self.username)
1150 1150 else:
1151 1151 log.debug('No data in %s that could been used to log in', self)
1152 1152
1153 1153 if not is_user_loaded:
1154 1154 log.debug(
1155 1155 'Failed to load user. Fallback to default user %s', anon_user)
1156 1156 # if we cannot authenticate user try anonymous
1157 1157 if anon_user.active:
1158 1158 log.debug('default user is active, using it as a session user')
1159 1159 user_model.fill_data(self, user_id=anon_user.user_id)
1160 1160 # then we set this user is logged in
1161 1161 self.is_authenticated = True
1162 1162 else:
1163 1163 log.debug('default user is NOT active')
1164 1164 # in case of disabled anonymous user we reset some of the
1165 1165 # parameters so such user is "corrupted", skipping the fill_data
1166 1166 for attr in ['user_id', 'username', 'admin', 'active']:
1167 1167 setattr(self, attr, None)
1168 1168 self.is_authenticated = False
1169 1169
1170 1170 if not self.username:
1171 1171 self.username = 'None'
1172 1172
1173 1173 log.debug('AuthUser: propagated user is now %s', self)
1174 1174
1175 1175 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
1176 1176 calculate_super_admin=False, cache=None):
1177 1177 """
1178 1178 Fills user permission attribute with permissions taken from database
1179 1179 works for permissions given for repositories, and for permissions that
1180 1180 are granted to groups
1181 1181
1182 1182 :param user: instance of User object from database
1183 1183 :param explicit: In case there are permissions both for user and a group
1184 1184 that user is part of, explicit flag will defiine if user will
1185 1185 explicitly override permissions from group, if it's False it will
1186 1186 make decision based on the algo
1187 1187 :param algo: algorithm to decide what permission should be choose if
1188 1188 it's multiple defined, eg user in two different groups. It also
1189 1189 decides if explicit flag is turned off how to specify the permission
1190 1190 for case when user is in a group + have defined separate permission
1191 1191 :param calculate_super_admin: calculate permissions for super-admin in the
1192 1192 same way as for regular user without speedups
1193 1193 :param cache: Use caching for calculation, None = let the cache backend decide
1194 1194 """
1195 1195 user_id = user.user_id
1196 1196 user_is_admin = user.is_admin
1197 1197
1198 1198 # inheritance of global permissions like create repo/fork repo etc
1199 1199 user_inherit_default_permissions = user.inherit_default_permissions
1200 1200
1201 1201 cache_seconds = safe_int(
1202 1202 rhodecode.CONFIG.get('rc_cache.cache_perms.expiration_time'))
1203 1203
1204 1204 if cache is None:
1205 1205 # let the backend cache decide
1206 1206 cache_on = cache_seconds > 0
1207 1207 else:
1208 1208 cache_on = cache
1209 1209
1210 1210 log.debug(
1211 1211 'Computing PERMISSION tree for user %s scope `%s` '
1212 1212 'with caching: %s[TTL: %ss]', user, scope, cache_on, cache_seconds or 0)
1213 1213
1214 1214 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
1215 1215 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1216 1216
1217 1217 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
1218 1218 condition=cache_on)
1219 1219 def compute_perm_tree(cache_name,
1220 1220 user_id, scope, user_is_admin,user_inherit_default_permissions,
1221 1221 explicit, algo, calculate_super_admin):
1222 1222 return _cached_perms_data(
1223 1223 user_id, scope, user_is_admin, user_inherit_default_permissions,
1224 1224 explicit, algo, calculate_super_admin)
1225 1225
1226 1226 start = time.time()
1227 1227 result = compute_perm_tree(
1228 1228 'permissions', user_id, scope, user_is_admin,
1229 1229 user_inherit_default_permissions, explicit, algo,
1230 1230 calculate_super_admin)
1231 1231
1232 1232 result_repr = []
1233 1233 for k in result:
1234 1234 result_repr.append((k, len(result[k])))
1235 1235 total = time.time() - start
1236 1236 log.debug('PERMISSION tree for user %s computed in %.3fs: %s',
1237 1237 user, total, result_repr)
1238 1238
1239 1239 return result
1240 1240
1241 1241 @property
1242 1242 def is_default(self):
1243 1243 return self.username == User.DEFAULT_USER
1244 1244
1245 1245 @property
1246 1246 def is_admin(self):
1247 1247 return self.admin
1248 1248
1249 1249 @property
1250 1250 def is_user_object(self):
1251 1251 return self.user_id is not None
1252 1252
1253 1253 @property
1254 1254 def repositories_admin(self):
1255 1255 """
1256 1256 Returns list of repositories you're an admin of
1257 1257 """
1258 1258 return [
1259 1259 x[0] for x in self.permissions['repositories'].items()
1260 1260 if x[1] == 'repository.admin']
1261 1261
1262 1262 @property
1263 1263 def repository_groups_admin(self):
1264 1264 """
1265 1265 Returns list of repository groups you're an admin of
1266 1266 """
1267 1267 return [
1268 1268 x[0] for x in self.permissions['repositories_groups'].items()
1269 1269 if x[1] == 'group.admin']
1270 1270
1271 1271 @property
1272 1272 def user_groups_admin(self):
1273 1273 """
1274 1274 Returns list of user groups you're an admin of
1275 1275 """
1276 1276 return [
1277 1277 x[0] for x in self.permissions['user_groups'].items()
1278 1278 if x[1] == 'usergroup.admin']
1279 1279
1280 1280 def repo_acl_ids(self, perms=None, name_filter=None, cache=False):
1281 1281 """
1282 1282 Returns list of repository ids that user have access to based on given
1283 1283 perms. The cache flag should be only used in cases that are used for
1284 1284 display purposes, NOT IN ANY CASE for permission checks.
1285 1285 """
1286 1286 from rhodecode.model.scm import RepoList
1287 1287 if not perms:
1288 1288 perms = [
1289 1289 'repository.read', 'repository.write', 'repository.admin']
1290 1290
1291 1291 def _cached_repo_acl(user_id, perm_def, _name_filter):
1292 1292 qry = Repository.query()
1293 1293 if _name_filter:
1294 1294 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1295 1295 qry = qry.filter(
1296 1296 Repository.repo_name.ilike(ilike_expression))
1297 1297
1298 1298 return [x.repo_id for x in
1299 1299 RepoList(qry, perm_set=perm_def)]
1300 1300
1301 1301 return _cached_repo_acl(self.user_id, perms, name_filter)
1302 1302
1303 1303 def repo_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1304 1304 """
1305 1305 Returns list of repository group ids that user have access to based on given
1306 1306 perms. The cache flag should be only used in cases that are used for
1307 1307 display purposes, NOT IN ANY CASE for permission checks.
1308 1308 """
1309 1309 from rhodecode.model.scm import RepoGroupList
1310 1310 if not perms:
1311 1311 perms = [
1312 1312 'group.read', 'group.write', 'group.admin']
1313 1313
1314 1314 def _cached_repo_group_acl(user_id, perm_def, _name_filter):
1315 1315 qry = RepoGroup.query()
1316 1316 if _name_filter:
1317 1317 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1318 1318 qry = qry.filter(
1319 1319 RepoGroup.group_name.ilike(ilike_expression))
1320 1320
1321 1321 return [x.group_id for x in
1322 1322 RepoGroupList(qry, perm_set=perm_def)]
1323 1323
1324 1324 return _cached_repo_group_acl(self.user_id, perms, name_filter)
1325 1325
1326 1326 def user_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1327 1327 """
1328 1328 Returns list of user group ids that user have access to based on given
1329 1329 perms. The cache flag should be only used in cases that are used for
1330 1330 display purposes, NOT IN ANY CASE for permission checks.
1331 1331 """
1332 1332 from rhodecode.model.scm import UserGroupList
1333 1333 if not perms:
1334 1334 perms = [
1335 1335 'usergroup.read', 'usergroup.write', 'usergroup.admin']
1336 1336
1337 1337 def _cached_user_group_acl(user_id, perm_def, name_filter):
1338 1338 qry = UserGroup.query()
1339 1339 if name_filter:
1340 1340 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1341 1341 qry = qry.filter(
1342 1342 UserGroup.users_group_name.ilike(ilike_expression))
1343 1343
1344 1344 return [x.users_group_id for x in
1345 1345 UserGroupList(qry, perm_set=perm_def)]
1346 1346
1347 1347 return _cached_user_group_acl(self.user_id, perms, name_filter)
1348 1348
1349 1349 @property
1350 1350 def ip_allowed(self):
1351 1351 """
1352 1352 Checks if ip_addr used in constructor is allowed from defined list of
1353 1353 allowed ip_addresses for user
1354 1354
1355 1355 :returns: boolean, True if ip is in allowed ip range
1356 1356 """
1357 1357 # check IP
1358 1358 inherit = self.inherit_default_permissions
1359 1359 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
1360 1360 inherit_from_default=inherit)
1361 1361 @property
1362 1362 def personal_repo_group(self):
1363 1363 return RepoGroup.get_user_personal_repo_group(self.user_id)
1364 1364
1365 1365 @LazyProperty
1366 1366 def feed_token(self):
1367 1367 return self.get_instance().feed_token
1368 1368
1369 1369 @classmethod
1370 1370 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
1371 1371 allowed_ips = AuthUser.get_allowed_ips(
1372 1372 user_id, cache=True, inherit_from_default=inherit_from_default)
1373 1373 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
1374 1374 log.debug('IP:%s for user %s is in range of %s',
1375 1375 ip_addr, user_id, allowed_ips)
1376 1376 return True
1377 1377 else:
1378 1378 log.info('Access for IP:%s forbidden for user %s, '
1379 1379 'not in %s', ip_addr, user_id, allowed_ips)
1380 1380 return False
1381 1381
1382 1382 def get_branch_permissions(self, repo_name, perms=None):
1383 1383 perms = perms or self.permissions_with_scope({'repo_name': repo_name})
1384 1384 branch_perms = perms.get('repository_branches', {})
1385 1385 if not branch_perms:
1386 1386 return {}
1387 1387 repo_branch_perms = branch_perms.get(repo_name)
1388 1388 return repo_branch_perms or {}
1389 1389
1390 1390 def get_rule_and_branch_permission(self, repo_name, branch_name):
1391 1391 """
1392 1392 Check if this AuthUser has defined any permissions for branches. If any of
1393 1393 the rules match in order, we return the matching permissions
1394 1394 """
1395 1395
1396 1396 rule = default_perm = ''
1397 1397
1398 1398 repo_branch_perms = self.get_branch_permissions(repo_name=repo_name)
1399 1399 if not repo_branch_perms:
1400 1400 return rule, default_perm
1401 1401
1402 1402 # now calculate the permissions
1403 1403 for pattern, branch_perm in repo_branch_perms.items():
1404 1404 if fnmatch.fnmatch(branch_name, pattern):
1405 1405 rule = '`{}`=>{}'.format(pattern, branch_perm)
1406 1406 return rule, branch_perm
1407 1407
1408 1408 return rule, default_perm
1409 1409
1410 1410 def __repr__(self):
1411 1411 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1412 1412 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1413 1413
1414 1414 def set_authenticated(self, authenticated=True):
1415 1415 if self.user_id != self.anonymous_user.user_id:
1416 1416 self.is_authenticated = authenticated
1417 1417
1418 1418 def get_cookie_store(self):
1419 1419 return {
1420 1420 'username': self.username,
1421 1421 'password': md5(self.password or ''),
1422 1422 'user_id': self.user_id,
1423 1423 'is_authenticated': self.is_authenticated
1424 1424 }
1425 1425
1426 1426 @classmethod
1427 1427 def from_cookie_store(cls, cookie_store):
1428 1428 """
1429 1429 Creates AuthUser from a cookie store
1430 1430
1431 1431 :param cls:
1432 1432 :param cookie_store:
1433 1433 """
1434 1434 user_id = cookie_store.get('user_id')
1435 1435 username = cookie_store.get('username')
1436 1436 api_key = cookie_store.get('api_key')
1437 1437 return AuthUser(user_id, api_key, username)
1438 1438
1439 1439 @classmethod
1440 1440 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1441 1441 _set = set()
1442 1442
1443 1443 if inherit_from_default:
1444 1444 def_user_id = User.get_default_user(cache=True).user_id
1445 1445 default_ips = UserIpMap.query().filter(UserIpMap.user_id == def_user_id)
1446 1446 if cache:
1447 1447 default_ips = default_ips.options(
1448 1448 FromCache("sql_cache_short", "get_user_ips_default"))
1449 1449
1450 1450 # populate from default user
1451 1451 for ip in default_ips:
1452 1452 try:
1453 1453 _set.add(ip.ip_addr)
1454 1454 except ObjectDeletedError:
1455 1455 # since we use heavy caching sometimes it happens that
1456 1456 # we get deleted objects here, we just skip them
1457 1457 pass
1458 1458
1459 1459 # NOTE:(marcink) we don't want to load any rules for empty
1460 1460 # user_id which is the case of access of non logged users when anonymous
1461 1461 # access is disabled
1462 1462 user_ips = []
1463 1463 if user_id:
1464 1464 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1465 1465 if cache:
1466 1466 user_ips = user_ips.options(
1467 1467 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1468 1468
1469 1469 for ip in user_ips:
1470 1470 try:
1471 1471 _set.add(ip.ip_addr)
1472 1472 except ObjectDeletedError:
1473 1473 # since we use heavy caching sometimes it happens that we get
1474 1474 # deleted objects here, we just skip them
1475 1475 pass
1476 1476 return _set or {ip for ip in ['0.0.0.0/0', '::/0']}
1477 1477
1478 1478
1479 1479 def set_available_permissions(settings):
1480 1480 """
1481 1481 This function will propagate pyramid settings with all available defined
1482 1482 permission given in db. We don't want to check each time from db for new
1483 1483 permissions since adding a new permission also requires application restart
1484 1484 ie. to decorate new views with the newly created permission
1485 1485
1486 1486 :param settings: current pyramid registry.settings
1487 1487
1488 1488 """
1489 1489 log.debug('auth: getting information about all available permissions')
1490 1490 try:
1491 1491 sa = meta.Session
1492 1492 all_perms = sa.query(Permission).all()
1493 1493 settings.setdefault('available_permissions',
1494 1494 [x.permission_name for x in all_perms])
1495 1495 log.debug('auth: set available permissions')
1496 1496 except Exception:
1497 1497 log.exception('Failed to fetch permissions from the database.')
1498 1498 raise
1499 1499
1500 1500
1501 1501 def get_csrf_token(session, force_new=False, save_if_missing=True):
1502 1502 """
1503 1503 Return the current authentication token, creating one if one doesn't
1504 1504 already exist and the save_if_missing flag is present.
1505 1505
1506 1506 :param session: pass in the pyramid session, else we use the global ones
1507 1507 :param force_new: force to re-generate the token and store it in session
1508 1508 :param save_if_missing: save the newly generated token if it's missing in
1509 1509 session
1510 1510 """
1511 1511 # NOTE(marcink): probably should be replaced with below one from pyramid 1.9
1512 1512 # from pyramid.csrf import get_csrf_token
1513 1513
1514 1514 if (csrf_token_key not in session and save_if_missing) or force_new:
1515 1515 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1516 1516 session[csrf_token_key] = token
1517 1517 if hasattr(session, 'save'):
1518 1518 session.save()
1519 1519 return session.get(csrf_token_key)
1520 1520
1521 1521
1522 1522 def get_request(perm_class_instance):
1523 1523 from pyramid.threadlocal import get_current_request
1524 1524 pyramid_request = get_current_request()
1525 1525 return pyramid_request
1526 1526
1527 1527
1528 1528 # CHECK DECORATORS
1529 1529 class CSRFRequired(object):
1530 1530 """
1531 1531 Decorator for authenticating a form
1532 1532
1533 1533 This decorator uses an authorization token stored in the client's
1534 1534 session for prevention of certain Cross-site request forgery (CSRF)
1535 1535 attacks (See
1536 1536 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1537 1537 information).
1538 1538
1539 1539 For use with the ``webhelpers.secure_form`` helper functions.
1540 1540
1541 1541 """
1542 1542 def __init__(self, token=csrf_token_key, header='X-CSRF-Token',
1543 1543 except_methods=None):
1544 1544 self.token = token
1545 1545 self.header = header
1546 1546 self.except_methods = except_methods or []
1547 1547
1548 1548 def __call__(self, func):
1549 1549 return get_cython_compat_decorator(self.__wrapper, func)
1550 1550
1551 1551 def _get_csrf(self, _request):
1552 1552 return _request.POST.get(self.token, _request.headers.get(self.header))
1553 1553
1554 1554 def check_csrf(self, _request, cur_token):
1555 1555 supplied_token = self._get_csrf(_request)
1556 1556 return supplied_token and supplied_token == cur_token
1557 1557
1558 1558 def _get_request(self):
1559 1559 return get_request(self)
1560 1560
1561 1561 def __wrapper(self, func, *fargs, **fkwargs):
1562 1562 request = self._get_request()
1563 1563
1564 1564 if request.method in self.except_methods:
1565 1565 return func(*fargs, **fkwargs)
1566 1566
1567 1567 cur_token = get_csrf_token(request.session, save_if_missing=False)
1568 1568 if self.check_csrf(request, cur_token):
1569 1569 if request.POST.get(self.token):
1570 1570 del request.POST[self.token]
1571 1571 return func(*fargs, **fkwargs)
1572 1572 else:
1573 1573 reason = 'token-missing'
1574 1574 supplied_token = self._get_csrf(request)
1575 1575 if supplied_token and cur_token != supplied_token:
1576 1576 reason = 'token-mismatch [%s:%s]' % (
1577 1577 cur_token or ''[:6], supplied_token or ''[:6])
1578 1578
1579 1579 csrf_message = \
1580 1580 ("Cross-site request forgery detected, request denied. See "
1581 1581 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1582 1582 "more information.")
1583 1583 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1584 1584 'REMOTE_ADDR:%s, HEADERS:%s' % (
1585 1585 request, reason, request.remote_addr, request.headers))
1586 1586
1587 1587 raise HTTPForbidden(explanation=csrf_message)
1588 1588
1589 1589
1590 1590 class LoginRequired(object):
1591 1591 """
1592 1592 Must be logged in to execute this function else
1593 1593 redirect to login page
1594 1594
1595 1595 :param api_access: if enabled this checks only for valid auth token
1596 1596 and grants access based on valid token
1597 1597 """
1598 1598 def __init__(self, auth_token_access=None):
1599 1599 self.auth_token_access = auth_token_access
1600 1600
1601 1601 def __call__(self, func):
1602 1602 return get_cython_compat_decorator(self.__wrapper, func)
1603 1603
1604 1604 def _get_request(self):
1605 1605 return get_request(self)
1606 1606
1607 1607 def __wrapper(self, func, *fargs, **fkwargs):
1608 1608 from rhodecode.lib import helpers as h
1609 1609 cls = fargs[0]
1610 1610 user = cls._rhodecode_user
1611 1611 request = self._get_request()
1612 1612 _ = request.translate
1613 1613
1614 1614 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1615 1615 log.debug('Starting login restriction checks for user: %s', user)
1616 1616 # check if our IP is allowed
1617 1617 ip_access_valid = True
1618 1618 if not user.ip_allowed:
1619 1619 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr,))),
1620 1620 category='warning')
1621 1621 ip_access_valid = False
1622 1622
1623 1623 # check if we used an APIKEY and it's a valid one
1624 1624 # defined white-list of controllers which API access will be enabled
1625 1625 _auth_token = request.GET.get(
1626 1626 'auth_token', '') or request.GET.get('api_key', '')
1627 1627 auth_token_access_valid = allowed_auth_token_access(
1628 1628 loc, auth_token=_auth_token)
1629 1629
1630 1630 # explicit controller is enabled or API is in our whitelist
1631 1631 if self.auth_token_access or auth_token_access_valid:
1632 1632 log.debug('Checking AUTH TOKEN access for %s', cls)
1633 1633 db_user = user.get_instance()
1634 1634
1635 1635 if db_user:
1636 1636 if self.auth_token_access:
1637 1637 roles = self.auth_token_access
1638 1638 else:
1639 1639 roles = [UserApiKeys.ROLE_HTTP]
1640 1640 token_match = db_user.authenticate_by_token(
1641 1641 _auth_token, roles=roles)
1642 1642 else:
1643 1643 log.debug('Unable to fetch db instance for auth user: %s', user)
1644 1644 token_match = False
1645 1645
1646 1646 if _auth_token and token_match:
1647 1647 auth_token_access_valid = True
1648 1648 log.debug('AUTH TOKEN ****%s is VALID', _auth_token[-4:])
1649 1649 else:
1650 1650 auth_token_access_valid = False
1651 1651 if not _auth_token:
1652 1652 log.debug("AUTH TOKEN *NOT* present in request")
1653 1653 else:
1654 1654 log.warning("AUTH TOKEN ****%s *NOT* valid", _auth_token[-4:])
1655 1655
1656 1656 log.debug('Checking if %s is authenticated @ %s', user.username, loc)
1657 1657 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1658 1658 else 'AUTH_TOKEN_AUTH'
1659 1659
1660 1660 if ip_access_valid and (
1661 1661 user.is_authenticated or auth_token_access_valid):
1662 1662 log.info('user %s authenticating with:%s IS authenticated on func %s',
1663 1663 user, reason, loc)
1664 1664
1665 1665 return func(*fargs, **fkwargs)
1666 1666 else:
1667 1667 log.warning(
1668 1668 'user %s authenticating with:%s NOT authenticated on '
1669 1669 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s',
1670 1670 user, reason, loc, ip_access_valid, auth_token_access_valid)
1671 1671 # we preserve the get PARAM
1672 1672 came_from = get_came_from(request)
1673 1673
1674 1674 log.debug('redirecting to login page with %s', came_from)
1675 1675 raise HTTPFound(
1676 1676 h.route_path('login', _query={'came_from': came_from}))
1677 1677
1678 1678
1679 1679 class NotAnonymous(object):
1680 1680 """
1681 1681 Must be logged in to execute this function else
1682 1682 redirect to login page
1683 1683 """
1684 1684
1685 1685 def __call__(self, func):
1686 1686 return get_cython_compat_decorator(self.__wrapper, func)
1687 1687
1688 1688 def _get_request(self):
1689 1689 return get_request(self)
1690 1690
1691 1691 def __wrapper(self, func, *fargs, **fkwargs):
1692 1692 import rhodecode.lib.helpers as h
1693 1693 cls = fargs[0]
1694 1694 self.user = cls._rhodecode_user
1695 1695 request = self._get_request()
1696 1696 _ = request.translate
1697 1697 log.debug('Checking if user is not anonymous @%s', cls)
1698 1698
1699 1699 anonymous = self.user.username == User.DEFAULT_USER
1700 1700
1701 1701 if anonymous:
1702 1702 came_from = get_came_from(request)
1703 1703 h.flash(_('You need to be a registered user to '
1704 1704 'perform this action'),
1705 1705 category='warning')
1706 1706 raise HTTPFound(
1707 1707 h.route_path('login', _query={'came_from': came_from}))
1708 1708 else:
1709 1709 return func(*fargs, **fkwargs)
1710 1710
1711 1711
1712 1712 class PermsDecorator(object):
1713 1713 """
1714 1714 Base class for controller decorators, we extract the current user from
1715 1715 the class itself, which has it stored in base controllers
1716 1716 """
1717 1717
1718 1718 def __init__(self, *required_perms):
1719 1719 self.required_perms = set(required_perms)
1720 1720
1721 1721 def __call__(self, func):
1722 1722 return get_cython_compat_decorator(self.__wrapper, func)
1723 1723
1724 1724 def _get_request(self):
1725 1725 return get_request(self)
1726 1726
1727 1727 def __wrapper(self, func, *fargs, **fkwargs):
1728 1728 import rhodecode.lib.helpers as h
1729 1729 cls = fargs[0]
1730 1730 _user = cls._rhodecode_user
1731 1731 request = self._get_request()
1732 1732 _ = request.translate
1733 1733
1734 1734 log.debug('checking %s permissions %s for %s %s',
1735 1735 self.__class__.__name__, self.required_perms, cls, _user)
1736 1736
1737 1737 if self.check_permissions(_user):
1738 1738 log.debug('Permission granted for %s %s', cls, _user)
1739 1739 return func(*fargs, **fkwargs)
1740 1740
1741 1741 else:
1742 1742 log.debug('Permission denied for %s %s', cls, _user)
1743 1743 anonymous = _user.username == User.DEFAULT_USER
1744 1744
1745 1745 if anonymous:
1746 1746 came_from = get_came_from(self._get_request())
1747 1747 h.flash(_('You need to be signed in to view this page'),
1748 1748 category='warning')
1749 1749 raise HTTPFound(
1750 1750 h.route_path('login', _query={'came_from': came_from}))
1751 1751
1752 1752 else:
1753 1753 # redirect with 404 to prevent resource discovery
1754 1754 raise HTTPNotFound()
1755 1755
1756 1756 def check_permissions(self, user):
1757 1757 """Dummy function for overriding"""
1758 1758 raise NotImplementedError(
1759 1759 'You have to write this function in child class')
1760 1760
1761 1761
1762 1762 class HasPermissionAllDecorator(PermsDecorator):
1763 1763 """
1764 1764 Checks for access permission for all given predicates. All of them
1765 1765 have to be meet in order to fulfill the request
1766 1766 """
1767 1767
1768 1768 def check_permissions(self, user):
1769 1769 perms = user.permissions_with_scope({})
1770 1770 if self.required_perms.issubset(perms['global']):
1771 1771 return True
1772 1772 return False
1773 1773
1774 1774
1775 1775 class HasPermissionAnyDecorator(PermsDecorator):
1776 1776 """
1777 1777 Checks for access permission for any of given predicates. In order to
1778 1778 fulfill the request any of predicates must be meet
1779 1779 """
1780 1780
1781 1781 def check_permissions(self, user):
1782 1782 perms = user.permissions_with_scope({})
1783 1783 if self.required_perms.intersection(perms['global']):
1784 1784 return True
1785 1785 return False
1786 1786
1787 1787
1788 1788 class HasRepoPermissionAllDecorator(PermsDecorator):
1789 1789 """
1790 1790 Checks for access permission for all given predicates for specific
1791 1791 repository. All of them have to be meet in order to fulfill the request
1792 1792 """
1793 1793 def _get_repo_name(self):
1794 1794 _request = self._get_request()
1795 1795 return get_repo_slug(_request)
1796 1796
1797 1797 def check_permissions(self, user):
1798 1798 perms = user.permissions
1799 1799 repo_name = self._get_repo_name()
1800 1800
1801 1801 try:
1802 1802 user_perms = {perms['repositories'][repo_name]}
1803 1803 except KeyError:
1804 1804 log.debug('cannot locate repo with name: `%s` in permissions defs',
1805 1805 repo_name)
1806 1806 return False
1807 1807
1808 1808 log.debug('checking `%s` permissions for repo `%s`',
1809 1809 user_perms, repo_name)
1810 1810 if self.required_perms.issubset(user_perms):
1811 1811 return True
1812 1812 return False
1813 1813
1814 1814
1815 1815 class HasRepoPermissionAnyDecorator(PermsDecorator):
1816 1816 """
1817 1817 Checks for access permission for any of given predicates for specific
1818 1818 repository. In order to fulfill the request any of predicates must be meet
1819 1819 """
1820 1820 def _get_repo_name(self):
1821 1821 _request = self._get_request()
1822 1822 return get_repo_slug(_request)
1823 1823
1824 1824 def check_permissions(self, user):
1825 1825 perms = user.permissions
1826 1826 repo_name = self._get_repo_name()
1827 1827
1828 1828 try:
1829 1829 user_perms = {perms['repositories'][repo_name]}
1830 1830 except KeyError:
1831 1831 log.debug(
1832 1832 'cannot locate repo with name: `%s` in permissions defs',
1833 1833 repo_name)
1834 1834 return False
1835 1835
1836 1836 log.debug('checking `%s` permissions for repo `%s`',
1837 1837 user_perms, repo_name)
1838 1838 if self.required_perms.intersection(user_perms):
1839 1839 return True
1840 1840 return False
1841 1841
1842 1842
1843 1843 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1844 1844 """
1845 1845 Checks for access permission for all given predicates for specific
1846 1846 repository group. All of them have to be meet in order to
1847 1847 fulfill the request
1848 1848 """
1849 1849 def _get_repo_group_name(self):
1850 1850 _request = self._get_request()
1851 1851 return get_repo_group_slug(_request)
1852 1852
1853 1853 def check_permissions(self, user):
1854 1854 perms = user.permissions
1855 1855 group_name = self._get_repo_group_name()
1856 1856 try:
1857 1857 user_perms = {perms['repositories_groups'][group_name]}
1858 1858 except KeyError:
1859 1859 log.debug(
1860 1860 'cannot locate repo group with name: `%s` in permissions defs',
1861 1861 group_name)
1862 1862 return False
1863 1863
1864 1864 log.debug('checking `%s` permissions for repo group `%s`',
1865 1865 user_perms, group_name)
1866 1866 if self.required_perms.issubset(user_perms):
1867 1867 return True
1868 1868 return False
1869 1869
1870 1870
1871 1871 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1872 1872 """
1873 1873 Checks for access permission for any of given predicates for specific
1874 1874 repository group. In order to fulfill the request any
1875 1875 of predicates must be met
1876 1876 """
1877 1877 def _get_repo_group_name(self):
1878 1878 _request = self._get_request()
1879 1879 return get_repo_group_slug(_request)
1880 1880
1881 1881 def check_permissions(self, user):
1882 1882 perms = user.permissions
1883 1883 group_name = self._get_repo_group_name()
1884 1884
1885 1885 try:
1886 1886 user_perms = {perms['repositories_groups'][group_name]}
1887 1887 except KeyError:
1888 1888 log.debug(
1889 1889 'cannot locate repo group with name: `%s` in permissions defs',
1890 1890 group_name)
1891 1891 return False
1892 1892
1893 1893 log.debug('checking `%s` permissions for repo group `%s`',
1894 1894 user_perms, group_name)
1895 1895 if self.required_perms.intersection(user_perms):
1896 1896 return True
1897 1897 return False
1898 1898
1899 1899
1900 1900 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1901 1901 """
1902 1902 Checks for access permission for all given predicates for specific
1903 1903 user group. All of them have to be meet in order to fulfill the request
1904 1904 """
1905 1905 def _get_user_group_name(self):
1906 1906 _request = self._get_request()
1907 1907 return get_user_group_slug(_request)
1908 1908
1909 1909 def check_permissions(self, user):
1910 1910 perms = user.permissions
1911 1911 group_name = self._get_user_group_name()
1912 1912 try:
1913 1913 user_perms = {perms['user_groups'][group_name]}
1914 1914 except KeyError:
1915 1915 return False
1916 1916
1917 1917 if self.required_perms.issubset(user_perms):
1918 1918 return True
1919 1919 return False
1920 1920
1921 1921
1922 1922 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1923 1923 """
1924 1924 Checks for access permission for any of given predicates for specific
1925 1925 user group. In order to fulfill the request any of predicates must be meet
1926 1926 """
1927 1927 def _get_user_group_name(self):
1928 1928 _request = self._get_request()
1929 1929 return get_user_group_slug(_request)
1930 1930
1931 1931 def check_permissions(self, user):
1932 1932 perms = user.permissions
1933 1933 group_name = self._get_user_group_name()
1934 1934 try:
1935 1935 user_perms = {perms['user_groups'][group_name]}
1936 1936 except KeyError:
1937 1937 return False
1938 1938
1939 1939 if self.required_perms.intersection(user_perms):
1940 1940 return True
1941 1941 return False
1942 1942
1943 1943
1944 1944 # CHECK FUNCTIONS
1945 1945 class PermsFunction(object):
1946 1946 """Base function for other check functions"""
1947 1947
1948 1948 def __init__(self, *perms):
1949 1949 self.required_perms = set(perms)
1950 1950 self.repo_name = None
1951 1951 self.repo_group_name = None
1952 1952 self.user_group_name = None
1953 1953
1954 1954 def __bool__(self):
1955 1955 frame = inspect.currentframe()
1956 1956 stack_trace = traceback.format_stack(frame)
1957 1957 log.error('Checking bool value on a class instance of perm '
1958 1958 'function is not allowed: %s', ''.join(stack_trace))
1959 1959 # rather than throwing errors, here we always return False so if by
1960 1960 # accident someone checks truth for just an instance it will always end
1961 1961 # up in returning False
1962 1962 return False
1963 1963 __nonzero__ = __bool__
1964 1964
1965 1965 def __call__(self, check_location='', user=None):
1966 1966 if not user:
1967 1967 log.debug('Using user attribute from global request')
1968 1968 request = self._get_request()
1969 1969 user = request.user
1970 1970
1971 1971 # init auth user if not already given
1972 1972 if not isinstance(user, AuthUser):
1973 1973 log.debug('Wrapping user %s into AuthUser', user)
1974 1974 user = AuthUser(user.user_id)
1975 1975
1976 1976 cls_name = self.__class__.__name__
1977 1977 check_scope = self._get_check_scope(cls_name)
1978 1978 check_location = check_location or 'unspecified location'
1979 1979
1980 1980 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
1981 1981 self.required_perms, user, check_scope, check_location)
1982 1982 if not user:
1983 1983 log.warning('Empty user given for permission check')
1984 1984 return False
1985 1985
1986 1986 if self.check_permissions(user):
1987 1987 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1988 1988 check_scope, user, check_location)
1989 1989 return True
1990 1990
1991 1991 else:
1992 1992 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1993 1993 check_scope, user, check_location)
1994 1994 return False
1995 1995
1996 1996 def _get_request(self):
1997 1997 return get_request(self)
1998 1998
1999 1999 def _get_check_scope(self, cls_name):
2000 2000 return {
2001 2001 'HasPermissionAll': 'GLOBAL',
2002 2002 'HasPermissionAny': 'GLOBAL',
2003 2003 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
2004 2004 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
2005 2005 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
2006 2006 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
2007 2007 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
2008 2008 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
2009 2009 }.get(cls_name, '?:%s' % cls_name)
2010 2010
2011 2011 def check_permissions(self, user):
2012 2012 """Dummy function for overriding"""
2013 2013 raise Exception('You have to write this function in child class')
2014 2014
2015 2015
2016 2016 class HasPermissionAll(PermsFunction):
2017 2017 def check_permissions(self, user):
2018 2018 perms = user.permissions_with_scope({})
2019 2019 if self.required_perms.issubset(perms.get('global')):
2020 2020 return True
2021 2021 return False
2022 2022
2023 2023
2024 2024 class HasPermissionAny(PermsFunction):
2025 2025 def check_permissions(self, user):
2026 2026 perms = user.permissions_with_scope({})
2027 2027 if self.required_perms.intersection(perms.get('global')):
2028 2028 return True
2029 2029 return False
2030 2030
2031 2031
2032 2032 class HasRepoPermissionAll(PermsFunction):
2033 2033 def __call__(self, repo_name=None, check_location='', user=None):
2034 2034 self.repo_name = repo_name
2035 2035 return super(HasRepoPermissionAll, self).__call__(check_location, user)
2036 2036
2037 2037 def _get_repo_name(self):
2038 2038 if not self.repo_name:
2039 2039 _request = self._get_request()
2040 2040 self.repo_name = get_repo_slug(_request)
2041 2041 return self.repo_name
2042 2042
2043 2043 def check_permissions(self, user):
2044 2044 self.repo_name = self._get_repo_name()
2045 2045 perms = user.permissions
2046 2046 try:
2047 2047 user_perms = {perms['repositories'][self.repo_name]}
2048 2048 except KeyError:
2049 2049 return False
2050 2050 if self.required_perms.issubset(user_perms):
2051 2051 return True
2052 2052 return False
2053 2053
2054 2054
2055 2055 class HasRepoPermissionAny(PermsFunction):
2056 2056 def __call__(self, repo_name=None, check_location='', user=None):
2057 2057 self.repo_name = repo_name
2058 2058 return super(HasRepoPermissionAny, self).__call__(check_location, user)
2059 2059
2060 2060 def _get_repo_name(self):
2061 2061 if not self.repo_name:
2062 2062 _request = self._get_request()
2063 2063 self.repo_name = get_repo_slug(_request)
2064 2064 return self.repo_name
2065 2065
2066 2066 def check_permissions(self, user):
2067 2067 self.repo_name = self._get_repo_name()
2068 2068 perms = user.permissions
2069 2069 try:
2070 2070 user_perms = {perms['repositories'][self.repo_name]}
2071 2071 except KeyError:
2072 2072 return False
2073 2073 if self.required_perms.intersection(user_perms):
2074 2074 return True
2075 2075 return False
2076 2076
2077 2077
2078 2078 class HasRepoGroupPermissionAny(PermsFunction):
2079 2079 def __call__(self, group_name=None, check_location='', user=None):
2080 2080 self.repo_group_name = group_name
2081 return super(HasRepoGroupPermissionAny, self).__call__(
2082 check_location, user)
2081 return super(HasRepoGroupPermissionAny, self).__call__(check_location, user)
2083 2082
2084 2083 def check_permissions(self, user):
2085 2084 perms = user.permissions
2086 2085 try:
2087 2086 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2088 2087 except KeyError:
2089 2088 return False
2090 2089 if self.required_perms.intersection(user_perms):
2091 2090 return True
2092 2091 return False
2093 2092
2094 2093
2095 2094 class HasRepoGroupPermissionAll(PermsFunction):
2096 2095 def __call__(self, group_name=None, check_location='', user=None):
2097 2096 self.repo_group_name = group_name
2098 return super(HasRepoGroupPermissionAll, self).__call__(
2099 check_location, user)
2097 return super(HasRepoGroupPermissionAll, self).__call__(check_location, user)
2100 2098
2101 2099 def check_permissions(self, user):
2102 2100 perms = user.permissions
2103 2101 try:
2104 2102 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2105 2103 except KeyError:
2106 2104 return False
2107 2105 if self.required_perms.issubset(user_perms):
2108 2106 return True
2109 2107 return False
2110 2108
2111 2109
2112 2110 class HasUserGroupPermissionAny(PermsFunction):
2113 2111 def __call__(self, user_group_name=None, check_location='', user=None):
2114 2112 self.user_group_name = user_group_name
2115 return super(HasUserGroupPermissionAny, self).__call__(
2116 check_location, user)
2113 return super(HasUserGroupPermissionAny, self).__call__(check_location, user)
2117 2114
2118 2115 def check_permissions(self, user):
2119 2116 perms = user.permissions
2120 2117 try:
2121 2118 user_perms = {perms['user_groups'][self.user_group_name]}
2122 2119 except KeyError:
2123 2120 return False
2124 2121 if self.required_perms.intersection(user_perms):
2125 2122 return True
2126 2123 return False
2127 2124
2128 2125
2129 2126 class HasUserGroupPermissionAll(PermsFunction):
2130 2127 def __call__(self, user_group_name=None, check_location='', user=None):
2131 2128 self.user_group_name = user_group_name
2132 return super(HasUserGroupPermissionAll, self).__call__(
2133 check_location, user)
2129 return super(HasUserGroupPermissionAll, self).__call__(check_location, user)
2134 2130
2135 2131 def check_permissions(self, user):
2136 2132 perms = user.permissions
2137 2133 try:
2138 2134 user_perms = {perms['user_groups'][self.user_group_name]}
2139 2135 except KeyError:
2140 2136 return False
2141 2137 if self.required_perms.issubset(user_perms):
2142 2138 return True
2143 2139 return False
2144 2140
2145 2141
2146 2142 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
2147 2143 class HasPermissionAnyMiddleware(object):
2148 2144 def __init__(self, *perms):
2149 2145 self.required_perms = set(perms)
2150 2146
2151 2147 def __call__(self, auth_user, repo_name):
2152 2148 # repo_name MUST be unicode, since we handle keys in permission
2153 2149 # dict by unicode
2154 2150 repo_name = safe_unicode(repo_name)
2155 2151 log.debug(
2156 2152 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
2157 2153 self.required_perms, auth_user, repo_name)
2158 2154
2159 2155 if self.check_permissions(auth_user, repo_name):
2160 2156 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
2161 2157 repo_name, auth_user, 'PermissionMiddleware')
2162 2158 return True
2163 2159
2164 2160 else:
2165 2161 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
2166 2162 repo_name, auth_user, 'PermissionMiddleware')
2167 2163 return False
2168 2164
2169 2165 def check_permissions(self, user, repo_name):
2170 2166 perms = user.permissions_with_scope({'repo_name': repo_name})
2171 2167
2172 2168 try:
2173 2169 user_perms = {perms['repositories'][repo_name]}
2174 2170 except Exception:
2175 2171 log.exception('Error while accessing user permissions')
2176 2172 return False
2177 2173
2178 2174 if self.required_perms.intersection(user_perms):
2179 2175 return True
2180 2176 return False
2181 2177
2182 2178
2183 2179 # SPECIAL VERSION TO HANDLE API AUTH
2184 2180 class _BaseApiPerm(object):
2185 2181 def __init__(self, *perms):
2186 2182 self.required_perms = set(perms)
2187 2183
2188 2184 def __call__(self, check_location=None, user=None, repo_name=None,
2189 2185 group_name=None, user_group_name=None):
2190 2186 cls_name = self.__class__.__name__
2191 2187 check_scope = 'global:%s' % (self.required_perms,)
2192 2188 if repo_name:
2193 2189 check_scope += ', repo_name:%s' % (repo_name,)
2194 2190
2195 2191 if group_name:
2196 2192 check_scope += ', repo_group_name:%s' % (group_name,)
2197 2193
2198 2194 if user_group_name:
2199 2195 check_scope += ', user_group_name:%s' % (user_group_name,)
2200 2196
2201 2197 log.debug('checking cls:%s %s %s @ %s',
2202 2198 cls_name, self.required_perms, check_scope, check_location)
2203 2199 if not user:
2204 2200 log.debug('Empty User passed into arguments')
2205 2201 return False
2206 2202
2207 2203 # process user
2208 2204 if not isinstance(user, AuthUser):
2209 2205 user = AuthUser(user.user_id)
2210 2206 if not check_location:
2211 2207 check_location = 'unspecified'
2212 2208 if self.check_permissions(user.permissions, repo_name, group_name,
2213 2209 user_group_name):
2214 2210 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2215 2211 check_scope, user, check_location)
2216 2212 return True
2217 2213
2218 2214 else:
2219 2215 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2220 2216 check_scope, user, check_location)
2221 2217 return False
2222 2218
2223 2219 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2224 2220 user_group_name=None):
2225 2221 """
2226 2222 implement in child class should return True if permissions are ok,
2227 2223 False otherwise
2228 2224
2229 2225 :param perm_defs: dict with permission definitions
2230 2226 :param repo_name: repo name
2231 2227 """
2232 2228 raise NotImplementedError()
2233 2229
2234 2230
2235 2231 class HasPermissionAllApi(_BaseApiPerm):
2236 2232 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2237 2233 user_group_name=None):
2238 2234 if self.required_perms.issubset(perm_defs.get('global')):
2239 2235 return True
2240 2236 return False
2241 2237
2242 2238
2243 2239 class HasPermissionAnyApi(_BaseApiPerm):
2244 2240 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2245 2241 user_group_name=None):
2246 2242 if self.required_perms.intersection(perm_defs.get('global')):
2247 2243 return True
2248 2244 return False
2249 2245
2250 2246
2251 2247 class HasRepoPermissionAllApi(_BaseApiPerm):
2252 2248 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2253 2249 user_group_name=None):
2254 2250 try:
2255 2251 _user_perms = {perm_defs['repositories'][repo_name]}
2256 2252 except KeyError:
2257 2253 log.warning(traceback.format_exc())
2258 2254 return False
2259 2255 if self.required_perms.issubset(_user_perms):
2260 2256 return True
2261 2257 return False
2262 2258
2263 2259
2264 2260 class HasRepoPermissionAnyApi(_BaseApiPerm):
2265 2261 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2266 2262 user_group_name=None):
2267 2263 try:
2268 2264 _user_perms = {perm_defs['repositories'][repo_name]}
2269 2265 except KeyError:
2270 2266 log.warning(traceback.format_exc())
2271 2267 return False
2272 2268 if self.required_perms.intersection(_user_perms):
2273 2269 return True
2274 2270 return False
2275 2271
2276 2272
2277 2273 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
2278 2274 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2279 2275 user_group_name=None):
2280 2276 try:
2281 2277 _user_perms = {perm_defs['repositories_groups'][group_name]}
2282 2278 except KeyError:
2283 2279 log.warning(traceback.format_exc())
2284 2280 return False
2285 2281 if self.required_perms.intersection(_user_perms):
2286 2282 return True
2287 2283 return False
2288 2284
2289 2285
2290 2286 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
2291 2287 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2292 2288 user_group_name=None):
2293 2289 try:
2294 2290 _user_perms = {perm_defs['repositories_groups'][group_name]}
2295 2291 except KeyError:
2296 2292 log.warning(traceback.format_exc())
2297 2293 return False
2298 2294 if self.required_perms.issubset(_user_perms):
2299 2295 return True
2300 2296 return False
2301 2297
2302 2298
2303 2299 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
2304 2300 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2305 2301 user_group_name=None):
2306 2302 try:
2307 2303 _user_perms = {perm_defs['user_groups'][user_group_name]}
2308 2304 except KeyError:
2309 2305 log.warning(traceback.format_exc())
2310 2306 return False
2311 2307 if self.required_perms.intersection(_user_perms):
2312 2308 return True
2313 2309 return False
2314 2310
2315 2311
2316 2312 def check_ip_access(source_ip, allowed_ips=None):
2317 2313 """
2318 2314 Checks if source_ip is a subnet of any of allowed_ips.
2319 2315
2320 2316 :param source_ip:
2321 2317 :param allowed_ips: list of allowed ips together with mask
2322 2318 """
2323 2319 log.debug('checking if ip:%s is subnet of %s', source_ip, allowed_ips)
2324 2320 source_ip_address = ipaddress.ip_address(safe_unicode(source_ip))
2325 2321 if isinstance(allowed_ips, (tuple, list, set)):
2326 2322 for ip in allowed_ips:
2327 2323 ip = safe_unicode(ip)
2328 2324 try:
2329 2325 network_address = ipaddress.ip_network(ip, strict=False)
2330 2326 if source_ip_address in network_address:
2331 2327 log.debug('IP %s is network %s', source_ip_address, network_address)
2332 2328 return True
2333 2329 # for any case we cannot determine the IP, don't crash just
2334 2330 # skip it and log as error, we want to say forbidden still when
2335 2331 # sending bad IP
2336 2332 except Exception:
2337 2333 log.error(traceback.format_exc())
2338 2334 continue
2339 2335 return False
2340 2336
2341 2337
2342 2338 def get_cython_compat_decorator(wrapper, func):
2343 2339 """
2344 2340 Creates a cython compatible decorator. The previously used
2345 2341 decorator.decorator() function seems to be incompatible with cython.
2346 2342
2347 2343 :param wrapper: __wrapper method of the decorator class
2348 2344 :param func: decorated function
2349 2345 """
2350 2346 @wraps(func)
2351 2347 def local_wrapper(*args, **kwds):
2352 2348 return wrapper(func, *args, **kwds)
2353 2349 local_wrapper.__wrapped__ = func
2354 2350 return local_wrapper
2355 2351
2356 2352
@@ -1,584 +1,583 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 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 The base Controller API
23 23 Provides the BaseController class for subclassing. And usage in different
24 24 controllers
25 25 """
26 26
27 27 import logging
28 28 import socket
29 29
30 30 import markupsafe
31 31 import ipaddress
32 32
33 33 from paste.auth.basic import AuthBasicAuthenticator
34 34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
35 35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
36 36
37 37 import rhodecode
38 38 from rhodecode.apps._base import TemplateArgs
39 39 from rhodecode.authentication.base import VCS_TYPE
40 40 from rhodecode.lib import auth, utils2
41 41 from rhodecode.lib import helpers as h
42 42 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
43 43 from rhodecode.lib.exceptions import UserCreationError
44 44 from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes)
45 45 from rhodecode.lib.utils2 import (
46 46 str2bool, safe_unicode, AttributeDict, safe_int, sha1, aslist, safe_str)
47 47 from rhodecode.model.db import Repository, User, ChangesetComment, UserBookmark
48 48 from rhodecode.model.notification import NotificationModel
49 49 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53
54 54 def _filter_proxy(ip):
55 55 """
56 56 Passed in IP addresses in HEADERS can be in a special format of multiple
57 57 ips. Those comma separated IPs are passed from various proxies in the
58 58 chain of request processing. The left-most being the original client.
59 59 We only care about the first IP which came from the org. client.
60 60
61 61 :param ip: ip string from headers
62 62 """
63 63 if ',' in ip:
64 64 _ips = ip.split(',')
65 65 _first_ip = _ips[0].strip()
66 66 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
67 67 return _first_ip
68 68 return ip
69 69
70 70
71 71 def _filter_port(ip):
72 72 """
73 73 Removes a port from ip, there are 4 main cases to handle here.
74 74 - ipv4 eg. 127.0.0.1
75 75 - ipv6 eg. ::1
76 76 - ipv4+port eg. 127.0.0.1:8080
77 77 - ipv6+port eg. [::1]:8080
78 78
79 79 :param ip:
80 80 """
81 81 def is_ipv6(ip_addr):
82 82 if hasattr(socket, 'inet_pton'):
83 83 try:
84 84 socket.inet_pton(socket.AF_INET6, ip_addr)
85 85 except socket.error:
86 86 return False
87 87 else:
88 88 # fallback to ipaddress
89 89 try:
90 90 ipaddress.IPv6Address(safe_unicode(ip_addr))
91 91 except Exception:
92 92 return False
93 93 return True
94 94
95 95 if ':' not in ip: # must be ipv4 pure ip
96 96 return ip
97 97
98 98 if '[' in ip and ']' in ip: # ipv6 with port
99 99 return ip.split(']')[0][1:].lower()
100 100
101 101 # must be ipv6 or ipv4 with port
102 102 if is_ipv6(ip):
103 103 return ip
104 104 else:
105 105 ip, _port = ip.split(':')[:2] # means ipv4+port
106 106 return ip
107 107
108 108
109 109 def get_ip_addr(environ):
110 110 proxy_key = 'HTTP_X_REAL_IP'
111 111 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
112 112 def_key = 'REMOTE_ADDR'
113 113 _filters = lambda x: _filter_port(_filter_proxy(x))
114 114
115 115 ip = environ.get(proxy_key)
116 116 if ip:
117 117 return _filters(ip)
118 118
119 119 ip = environ.get(proxy_key2)
120 120 if ip:
121 121 return _filters(ip)
122 122
123 123 ip = environ.get(def_key, '0.0.0.0')
124 124 return _filters(ip)
125 125
126 126
127 127 def get_server_ip_addr(environ, log_errors=True):
128 128 hostname = environ.get('SERVER_NAME')
129 129 try:
130 130 return socket.gethostbyname(hostname)
131 131 except Exception as e:
132 132 if log_errors:
133 133 # in some cases this lookup is not possible, and we don't want to
134 134 # make it an exception in logs
135 135 log.exception('Could not retrieve server ip address: %s', e)
136 136 return hostname
137 137
138 138
139 139 def get_server_port(environ):
140 140 return environ.get('SERVER_PORT')
141 141
142 142
143 143 def get_access_path(environ):
144 144 path = environ.get('PATH_INFO')
145 145 org_req = environ.get('pylons.original_request')
146 146 if org_req:
147 147 path = org_req.environ.get('PATH_INFO')
148 148 return path
149 149
150 150
151 151 def get_user_agent(environ):
152 152 return environ.get('HTTP_USER_AGENT')
153 153
154 154
155 155 def vcs_operation_context(
156 156 environ, repo_name, username, action, scm, check_locking=True,
157 157 is_shadow_repo=False, check_branch_perms=False, detect_force_push=False):
158 158 """
159 159 Generate the context for a vcs operation, e.g. push or pull.
160 160
161 161 This context is passed over the layers so that hooks triggered by the
162 162 vcs operation know details like the user, the user's IP address etc.
163 163
164 164 :param check_locking: Allows to switch of the computation of the locking
165 165 data. This serves mainly the need of the simplevcs middleware to be
166 166 able to disable this for certain operations.
167 167
168 168 """
169 169 # Tri-state value: False: unlock, None: nothing, True: lock
170 170 make_lock = None
171 171 locked_by = [None, None, None]
172 172 is_anonymous = username == User.DEFAULT_USER
173 173 user = User.get_by_username(username)
174 174 if not is_anonymous and check_locking:
175 175 log.debug('Checking locking on repository "%s"', repo_name)
176 176 repo = Repository.get_by_repo_name(repo_name)
177 177 make_lock, __, locked_by = repo.get_locking_state(
178 178 action, user.user_id)
179 179 user_id = user.user_id
180 180 settings_model = VcsSettingsModel(repo=repo_name)
181 181 ui_settings = settings_model.get_ui_settings()
182 182
183 183 # NOTE(marcink): This should be also in sync with
184 184 # rhodecode/apps/ssh_support/lib/backends/base.py:update_environment scm_data
185 185 store = [x for x in ui_settings if x.key == '/']
186 186 repo_store = ''
187 187 if store:
188 188 repo_store = store[0].value
189 189
190 190 scm_data = {
191 191 'ip': get_ip_addr(environ),
192 192 'username': username,
193 193 'user_id': user_id,
194 194 'action': action,
195 195 'repository': repo_name,
196 196 'scm': scm,
197 197 'config': rhodecode.CONFIG['__file__'],
198 198 'repo_store': repo_store,
199 199 'make_lock': make_lock,
200 200 'locked_by': locked_by,
201 201 'server_url': utils2.get_server_url(environ),
202 202 'user_agent': get_user_agent(environ),
203 203 'hooks': get_enabled_hook_classes(ui_settings),
204 204 'is_shadow_repo': is_shadow_repo,
205 205 'detect_force_push': detect_force_push,
206 206 'check_branch_perms': check_branch_perms,
207 207 }
208 208 return scm_data
209 209
210 210
211 211 class BasicAuth(AuthBasicAuthenticator):
212 212
213 213 def __init__(self, realm, authfunc, registry, auth_http_code=None,
214 214 initial_call_detection=False, acl_repo_name=None):
215 215 self.realm = realm
216 216 self.initial_call = initial_call_detection
217 217 self.authfunc = authfunc
218 218 self.registry = registry
219 219 self.acl_repo_name = acl_repo_name
220 220 self._rc_auth_http_code = auth_http_code
221 221
222 222 def _get_response_from_code(self, http_code):
223 223 try:
224 224 return get_exception(safe_int(http_code))
225 225 except Exception:
226 226 log.exception('Failed to fetch response for code %s', http_code)
227 227 return HTTPForbidden
228 228
229 229 def get_rc_realm(self):
230 230 return safe_str(self.registry.rhodecode_settings.get('rhodecode_realm'))
231 231
232 232 def build_authentication(self):
233 233 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
234 234 if self._rc_auth_http_code and not self.initial_call:
235 235 # return alternative HTTP code if alternative http return code
236 236 # is specified in RhodeCode config, but ONLY if it's not the
237 237 # FIRST call
238 238 custom_response_klass = self._get_response_from_code(
239 239 self._rc_auth_http_code)
240 240 return custom_response_klass(headers=head)
241 241 return HTTPUnauthorized(headers=head)
242 242
243 243 def authenticate(self, environ):
244 244 authorization = AUTHORIZATION(environ)
245 245 if not authorization:
246 246 return self.build_authentication()
247 247 (authmeth, auth) = authorization.split(' ', 1)
248 248 if 'basic' != authmeth.lower():
249 249 return self.build_authentication()
250 250 auth = auth.strip().decode('base64')
251 251 _parts = auth.split(':', 1)
252 252 if len(_parts) == 2:
253 253 username, password = _parts
254 254 auth_data = self.authfunc(
255 255 username, password, environ, VCS_TYPE,
256 256 registry=self.registry, acl_repo_name=self.acl_repo_name)
257 257 if auth_data:
258 258 return {'username': username, 'auth_data': auth_data}
259 259 if username and password:
260 260 # we mark that we actually executed authentication once, at
261 261 # that point we can use the alternative auth code
262 262 self.initial_call = False
263 263
264 264 return self.build_authentication()
265 265
266 266 __call__ = authenticate
267 267
268 268
269 269 def calculate_version_hash(config):
270 270 return sha1(
271 271 config.get('beaker.session.secret', '') +
272 272 rhodecode.__version__)[:8]
273 273
274 274
275 275 def get_current_lang(request):
276 276 # NOTE(marcink): remove after pyramid move
277 277 try:
278 278 return translation.get_lang()[0]
279 279 except:
280 280 pass
281 281
282 282 return getattr(request, '_LOCALE_', request.locale_name)
283 283
284 284
285 285 def attach_context_attributes(context, request, user_id=None):
286 286 """
287 287 Attach variables into template context called `c`.
288 288 """
289 289 config = request.registry.settings
290 290
291
292 291 rc_config = SettingsModel().get_all_settings(cache=True)
293 292
294 293 context.rhodecode_version = rhodecode.__version__
295 294 context.rhodecode_edition = config.get('rhodecode.edition')
296 295 # unique secret + version does not leak the version but keep consistency
297 296 context.rhodecode_version_hash = calculate_version_hash(config)
298 297
299 298 # Default language set for the incoming request
300 299 context.language = get_current_lang(request)
301 300
302 301 # Visual options
303 302 context.visual = AttributeDict({})
304 303
305 304 # DB stored Visual Items
306 305 context.visual.show_public_icon = str2bool(
307 306 rc_config.get('rhodecode_show_public_icon'))
308 307 context.visual.show_private_icon = str2bool(
309 308 rc_config.get('rhodecode_show_private_icon'))
310 309 context.visual.stylify_metatags = str2bool(
311 310 rc_config.get('rhodecode_stylify_metatags'))
312 311 context.visual.dashboard_items = safe_int(
313 312 rc_config.get('rhodecode_dashboard_items', 100))
314 313 context.visual.admin_grid_items = safe_int(
315 314 rc_config.get('rhodecode_admin_grid_items', 100))
316 315 context.visual.show_revision_number = str2bool(
317 316 rc_config.get('rhodecode_show_revision_number', True))
318 317 context.visual.show_sha_length = safe_int(
319 318 rc_config.get('rhodecode_show_sha_length', 100))
320 319 context.visual.repository_fields = str2bool(
321 320 rc_config.get('rhodecode_repository_fields'))
322 321 context.visual.show_version = str2bool(
323 322 rc_config.get('rhodecode_show_version'))
324 323 context.visual.use_gravatar = str2bool(
325 324 rc_config.get('rhodecode_use_gravatar'))
326 325 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
327 326 context.visual.default_renderer = rc_config.get(
328 327 'rhodecode_markup_renderer', 'rst')
329 328 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
330 329 context.visual.rhodecode_support_url = \
331 330 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
332 331
333 332 context.visual.affected_files_cut_off = 60
334 333
335 334 context.pre_code = rc_config.get('rhodecode_pre_code')
336 335 context.post_code = rc_config.get('rhodecode_post_code')
337 336 context.rhodecode_name = rc_config.get('rhodecode_title')
338 337 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
339 338 # if we have specified default_encoding in the request, it has more
340 339 # priority
341 340 if request.GET.get('default_encoding'):
342 341 context.default_encodings.insert(0, request.GET.get('default_encoding'))
343 342 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
344 343 context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl')
345 344
346 345 # INI stored
347 346 context.labs_active = str2bool(
348 347 config.get('labs_settings_active', 'false'))
349 348 context.ssh_enabled = str2bool(
350 349 config.get('ssh.generate_authorized_keyfile', 'false'))
351 350 context.ssh_key_generator_enabled = str2bool(
352 351 config.get('ssh.enable_ui_key_generator', 'true'))
353 352
354 353 context.visual.allow_repo_location_change = str2bool(
355 354 config.get('allow_repo_location_change', True))
356 355 context.visual.allow_custom_hooks_settings = str2bool(
357 356 config.get('allow_custom_hooks_settings', True))
358 357 context.debug_style = str2bool(config.get('debug_style', False))
359 358
360 359 context.rhodecode_instanceid = config.get('instance_id')
361 360
362 361 context.visual.cut_off_limit_diff = safe_int(
363 362 config.get('cut_off_limit_diff'))
364 363 context.visual.cut_off_limit_file = safe_int(
365 364 config.get('cut_off_limit_file'))
366 365
367 366 # AppEnlight
368 367 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
369 368 context.appenlight_api_public_key = config.get(
370 369 'appenlight.api_public_key', '')
371 370 context.appenlight_server_url = config.get('appenlight.server_url', '')
372 371
373 372 diffmode = {
374 373 "unified": "unified",
375 374 "sideside": "sideside"
376 375 }.get(request.GET.get('diffmode'))
377 376
378 377 if diffmode and diffmode != request.session.get('rc_user_session_attr.diffmode'):
379 378 request.session['rc_user_session_attr.diffmode'] = diffmode
380 379
381 380 # session settings per user
382 381 session_attrs = {
383 382 # defaults
384 383 "clone_url_format": "http",
385 384 "diffmode": "sideside"
386 385 }
387 386 for k, v in request.session.items():
388 387 pref = 'rc_user_session_attr.'
389 388 if k and k.startswith(pref):
390 389 k = k[len(pref):]
391 390 session_attrs[k] = v
392 391
393 392 context.user_session_attrs = session_attrs
394 393
395 394 # JS template context
396 395 context.template_context = {
397 396 'repo_name': None,
398 397 'repo_type': None,
399 398 'repo_landing_commit': None,
400 399 'rhodecode_user': {
401 400 'username': None,
402 401 'email': None,
403 402 'notification_status': False
404 403 },
405 404 'session_attrs': session_attrs,
406 405 'visual': {
407 406 'default_renderer': None
408 407 },
409 408 'commit_data': {
410 409 'commit_id': None
411 410 },
412 411 'pull_request_data': {'pull_request_id': None},
413 412 'timeago': {
414 413 'refresh_time': 120 * 1000,
415 414 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
416 415 },
417 416 'pyramid_dispatch': {
418 417
419 418 },
420 419 'extra': {'plugins': {}}
421 420 }
422 421 # END CONFIG VARS
423 422
424 423 context.csrf_token = auth.get_csrf_token(session=request.session)
425 424 context.backends = rhodecode.BACKENDS.keys()
426 425 context.backends.sort()
427 426 unread_count = 0
428 427 user_bookmark_list = []
429 428 if user_id:
430 429 unread_count = NotificationModel().get_unread_cnt_for_user(user_id)
431 430 user_bookmark_list = UserBookmark.get_bookmarks_for_user(user_id)
432 431 context.unread_notifications = unread_count
433 432 context.bookmark_items = user_bookmark_list
434 433
435 434 # web case
436 435 if hasattr(request, 'user'):
437 436 context.auth_user = request.user
438 437 context.rhodecode_user = request.user
439 438
440 439 # api case
441 440 if hasattr(request, 'rpc_user'):
442 441 context.auth_user = request.rpc_user
443 442 context.rhodecode_user = request.rpc_user
444 443
445 444 # attach the whole call context to the request
446 445 request.call_context = context
447 446
448 447
449 448 def get_auth_user(request):
450 449 environ = request.environ
451 450 session = request.session
452 451
453 452 ip_addr = get_ip_addr(environ)
454 453 # make sure that we update permissions each time we call controller
455 454 _auth_token = (request.GET.get('auth_token', '') or
456 455 request.GET.get('api_key', ''))
457 456
458 457 if _auth_token:
459 458 # when using API_KEY we assume user exists, and
460 459 # doesn't need auth based on cookies.
461 460 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
462 461 authenticated = False
463 462 else:
464 463 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
465 464 try:
466 465 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
467 466 ip_addr=ip_addr)
468 467 except UserCreationError as e:
469 468 h.flash(e, 'error')
470 469 # container auth or other auth functions that create users
471 470 # on the fly can throw this exception signaling that there's
472 471 # issue with user creation, explanation should be provided
473 472 # in Exception itself. We then create a simple blank
474 473 # AuthUser
475 474 auth_user = AuthUser(ip_addr=ip_addr)
476 475
477 476 # in case someone changes a password for user it triggers session
478 477 # flush and forces a re-login
479 478 if password_changed(auth_user, session):
480 479 session.invalidate()
481 480 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
482 481 auth_user = AuthUser(ip_addr=ip_addr)
483 482
484 483 authenticated = cookie_store.get('is_authenticated')
485 484
486 485 if not auth_user.is_authenticated and auth_user.is_user_object:
487 486 # user is not authenticated and not empty
488 487 auth_user.set_authenticated(authenticated)
489 488
490 489 return auth_user
491 490
492 491
493 492 def h_filter(s):
494 493 """
495 494 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
496 495 we wrap this with additional functionality that converts None to empty
497 496 strings
498 497 """
499 498 if s is None:
500 499 return markupsafe.Markup()
501 500 return markupsafe.escape(s)
502 501
503 502
504 503 def add_events_routes(config):
505 504 """
506 505 Adds routing that can be used in events. Because some events are triggered
507 506 outside of pyramid context, we need to bootstrap request with some
508 507 routing registered
509 508 """
510 509
511 510 from rhodecode.apps._base import ADMIN_PREFIX
512 511
513 512 config.add_route(name='home', pattern='/')
514 513
515 514 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
516 515 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
517 516 config.add_route(name='repo_summary', pattern='/{repo_name}')
518 517 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
519 518 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
520 519
521 520 config.add_route(name='pullrequest_show',
522 521 pattern='/{repo_name}/pull-request/{pull_request_id}')
523 522 config.add_route(name='pull_requests_global',
524 523 pattern='/pull-request/{pull_request_id}')
525 524 config.add_route(name='repo_commit',
526 525 pattern='/{repo_name}/changeset/{commit_id}')
527 526
528 527 config.add_route(name='repo_files',
529 528 pattern='/{repo_name}/files/{commit_id}/{f_path}')
530 529
531 530
532 531 def bootstrap_config(request):
533 532 import pyramid.testing
534 533 registry = pyramid.testing.Registry('RcTestRegistry')
535 534
536 535 config = pyramid.testing.setUp(registry=registry, request=request)
537 536
538 537 # allow pyramid lookup in testing
539 538 config.include('pyramid_mako')
540 539 config.include('pyramid_beaker')
541 540 config.include('rhodecode.lib.rc_cache')
542 541
543 542 add_events_routes(config)
544 543
545 544 return config
546 545
547 546
548 547 def bootstrap_request(**kwargs):
549 548 import pyramid.testing
550 549
551 550 class TestRequest(pyramid.testing.DummyRequest):
552 551 application_url = kwargs.pop('application_url', 'http://example.com')
553 552 host = kwargs.pop('host', 'example.com:80')
554 553 domain = kwargs.pop('domain', 'example.com')
555 554
556 555 def translate(self, msg):
557 556 return msg
558 557
559 558 def plularize(self, singular, plural, n):
560 559 return singular
561 560
562 561 def get_partial_renderer(self, tmpl_name):
563 562
564 563 from rhodecode.lib.partial_renderer import get_partial_renderer
565 564 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
566 565
567 566 _call_context = TemplateArgs()
568 567 _call_context.visual = TemplateArgs()
569 568 _call_context.visual.show_sha_length = 12
570 569 _call_context.visual.show_revision_number = True
571 570
572 571 @property
573 572 def call_context(self):
574 573 return self._call_context
575 574
576 575 class TestDummySession(pyramid.testing.DummySession):
577 576 def save(*arg, **kw):
578 577 pass
579 578
580 579 request = TestRequest(**kwargs)
581 580 request.session = TestDummySession()
582 581
583 582 return request
584 583
@@ -1,150 +1,150 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 %if c.show_private:
6 6 ${_('Private Gists for user %s') % c.rhodecode_user.username}
7 7 %elif c.show_public:
8 8 ${_('Public Gists for user %s') % c.rhodecode_user.username}
9 9 %else:
10 10 ${_('Public Gists')}
11 11 %endif
12 12 %if c.rhodecode_name:
13 13 &middot; ${h.branding(c.rhodecode_name)}
14 14 %endif
15 15 </%def>
16 16
17 17 <%def name="breadcrumbs_links()">
18 18 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
19 19 %if c.show_private and not c.show_public:
20 20 ${_('Private Gists for user %s') % c.rhodecode_user.username}
21 21 %elif c.show_public and not c.show_private:
22 22 ${_('Public Gists for user %s') % c.rhodecode_user.username}
23 23 %elif c.show_public and c.show_private:
24 24 ${_('All Gists for user %s') % c.rhodecode_user.username}
25 25 %else:
26 26 ${_('All Public Gists')}
27 27 %endif
28 28 - <span id="gists_count">0</span>
29 29 </%def>
30 30
31 31 <%def name="menu_bar_nav()">
32 32 ${self.menu_items(active='gists')}
33 33 </%def>
34 34
35 35
36 36
37 37 <%def name="main()">
38 38 <div class="box">
39 39 <div class="title">
40 40 ${self.breadcrumbs(class_="breadcrumbs block-left")}
41 41 %if c.rhodecode_user.username != h.DEFAULT_USER:
42 42 <ul class="links block-right">
43 43 <li>
44 44 <a href="${h.route_path('gists_new')}" class="btn btn-primary">${_(u'Create New Gist')}</a>
45 45 </li>
46 46 </ul>
47 47 %endif
48 48 </div>
49 49
50 50
51 51 <div class="sidebar-col-wrapper scw-small">
52 52 ##main
53 53 <div class="sidebar">
54 54 <ul class="nav nav-pills nav-stacked">
55 % if h.HasPermissionAll('hg.admin')('access admin gists page'):
55 % if c.is_super_admin:
56 56 <li class="${'active' if c.active=='all' else ''}"><a href="${h.route_path('gists_show', _query={'all': 1})}">${_('All gists')}</a></li>
57 57 %endif
58 58 <li class="${'active' if c.active=='public' else ''}"><a href="${h.route_path('gists_show')}">${_('All public')}</a></li>
59 59 %if c.rhodecode_user.username != h.DEFAULT_USER:
60 60 <li class="${'active' if c.active=='my_all' else ''}"><a href="${h.route_path('gists_show', _query={'public':1, 'private': 1})}">${_('My gists')}</a></li>
61 61 <li class="${'active' if c.active=='my_private' else ''}"><a href="${h.route_path('gists_show', _query={'private': 1})}">${_('My private')}</a></li>
62 62 <li class="${'active' if c.active=='my_public' else ''}"><a href="${h.route_path('gists_show', _query={'public': 1})}">${_('My public')}</a></li>
63 63 %endif
64 64 </ul>
65 65 </div>
66 66
67 67 <div class="main-content">
68 68 <div id="repos_list_wrap">
69 69 <table id="gist_list_table" class="display"></table>
70 70 </div>
71 71 </div>
72 72 </div>
73 73 </div>
74 74 <script>
75 75 $(document).ready(function() {
76 76
77 77 var get_datatable_count = function(){
78 78 var api = $('#gist_list_table').dataTable().api();
79 79 $('#gists_count').text(api.page.info().recordsDisplay);
80 80 };
81 81
82 82
83 83 // custom filter that filters by access_id, description or author
84 84 $.fn.dataTable.ext.search.push(
85 85 function( settings, data, dataIndex ) {
86 86 var query = $('#q_filter').val();
87 87 var author = data[0].strip();
88 88 var access_id = data[2].strip();
89 89 var description = data[3].strip();
90 90
91 91 var query_str = (access_id + " " + author + " " + description).toLowerCase();
92 92
93 93 if(query_str.indexOf(query.toLowerCase()) !== -1){
94 94 return true;
95 95 }
96 96 return false;
97 97 }
98 98 );
99 99
100 100 // gists list
101 101 $('#gist_list_table').DataTable({
102 102 data: ${c.data|n},
103 103 dom: 'rtp',
104 104 pageLength: ${c.visual.dashboard_items},
105 105 order: [[ 4, "desc" ]],
106 106 columns: [
107 107 { data: {"_": "author",
108 108 "sort": "author_raw"}, title: "${_("Author")}", width: "250px", className: "td-user" },
109 109 { data: {"_": "type",
110 110 "sort": "type"}, title: "${_("Type")}", width: "70px", className: "td-tags" },
111 111 { data: {"_": "access_id",
112 112 "sort": "access_id"}, title: "${_("Name")}", width:"150px", className: "td-componentname" },
113 113 { data: {"_": "description",
114 114 "sort": "description"}, title: "${_("Description")}", width: "250px", className: "td-description" },
115 115 { data: {"_": "created_on",
116 116 "sort": "created_on_raw"}, title: "${_("Created on")}", className: "td-time" },
117 117 { data: {"_": "expires",
118 118 "sort": "expires"}, title: "${_("Expires")}", className: "td-exp" }
119 119 ],
120 120 language: {
121 121 paginate: DEFAULT_GRID_PAGINATION,
122 122 emptyTable: _gettext("No gists available yet.")
123 123 },
124 124 "initComplete": function( settings, json ) {
125 125 timeagoActivate();
126 126 get_datatable_count();
127 127 }
128 128 });
129 129
130 130 // update the counter when things change
131 131 $('#gist_list_table').on('draw.dt', function() {
132 132 timeagoActivate();
133 133 get_datatable_count();
134 134 });
135 135
136 136 // filter, filter both grids
137 137 $('#q_filter').on( 'keyup', function () {
138 138 var repo_api = $('#gist_list_table').dataTable().api();
139 139 repo_api
140 140 .draw();
141 141 });
142 142
143 143 // refilter table if page load via back button
144 144 $("#q_filter").trigger('keyup');
145 145
146 146 });
147 147
148 148 </script>
149 149 </%def>
150 150
@@ -1,118 +1,118 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="robots()">
5 5 %if c.gist.gist_type != 'public':
6 6 <meta name="robots" content="noindex, nofollow">
7 7 %else:
8 8 ${parent.robots()}
9 9 %endif
10 10 </%def>
11 11
12 12 <%def name="title()">
13 13 ${_('Gist')} &middot; ${c.gist.gist_access_id}
14 14 %if c.rhodecode_name:
15 15 &middot; ${h.branding(c.rhodecode_name)}
16 16 %endif
17 17 </%def>
18 18
19 19 <%def name="breadcrumbs_links()">
20 20 ${_('Gist')} &middot; ${c.gist.gist_access_id}
21 21 </%def>
22 22
23 23 <%def name="menu_bar_nav()">
24 24 ${self.menu_items(active='gists')}
25 25 </%def>
26 26
27 27 <%def name="main()">
28 28 <div class="box">
29 29 <!-- box / title -->
30 30 <div class="title">
31 31 ${self.breadcrumbs()}
32 32 %if c.rhodecode_user.username != h.DEFAULT_USER:
33 33 <ul class="links">
34 34 <li>
35 35 <a href="${h.route_path('gists_new')}" class="btn btn-primary">${_(u'Create New Gist')}</a>
36 36 </li>
37 37 </ul>
38 38 %endif
39 39 </div>
40 40
41 41 <div class="table">
42 42 <div id="files_data">
43 43 <div id="codeblock" class="codeblock">
44 44 <div class="code-header">
45 45 <div class="gist_url">
46 46 <code>
47 47 ${c.gist.gist_url()} <span class="icon-clipboard clipboard-action" data-clipboard-text="${c.gist.gist_url()}" title="${_('Copy the url')}"></span>
48 48 </code>
49 49 </div>
50 50 <div class="stats">
51 %if h.HasPermissionAny('hg.admin')() or c.gist.gist_owner == c.rhodecode_user.user_id:
51 %if c.is_super_admin or c.gist.gist_owner == c.rhodecode_user.user_id:
52 52 <div class="remove_gist">
53 53 ${h.secure_form(h.route_path('gist_delete', gist_id=c.gist.gist_access_id), request=request)}
54 54 ${h.submit('remove_gist', _('Delete'),class_="btn btn-mini btn-danger",onclick="return confirm('"+_('Confirm to delete this Gist')+"');")}
55 55 ${h.end_form()}
56 56 </div>
57 57 %endif
58 58 <div class="buttons">
59 59 ## only owner should see that
60 60 <a href="#copySource" onclick="return false;" class="btn btn-mini icon-clipboard clipboard-action" data-clipboard-text="${c.files[0].content}">${_('Copy content')}</a>
61 61
62 %if h.HasPermissionAny('hg.admin')() or c.gist.gist_owner == c.rhodecode_user.user_id:
62 %if c.is_super_admin or c.gist.gist_owner == c.rhodecode_user.user_id:
63 63 ${h.link_to(_('Edit'), h.route_path('gist_edit', gist_id=c.gist.gist_access_id), class_="btn btn-mini")}
64 64 %endif
65 65 ${h.link_to(_('Show as Raw'), h.route_path('gist_show_formatted', gist_id=c.gist.gist_access_id, revision='tip', format='raw'), class_="btn btn-mini")}
66 66 </div>
67 67 <div class="left" >
68 68 %if c.gist.gist_type != 'public':
69 69 <span class="tag tag-ok disabled">${_('Private Gist')}</span>
70 70 %endif
71 71 <span> ${c.gist.gist_description}</span>
72 72 <span>${_('Expires')}:
73 73 %if c.gist.gist_expires == -1:
74 74 ${_('never')}
75 75 %else:
76 76 ${h.age_component(h.time_to_utcdatetime(c.gist.gist_expires))}
77 77 %endif
78 78 </span>
79 79
80 80 </div>
81 81 </div>
82 82
83 83 <div class="author">
84 84 <div title="${h.tooltip(c.file_last_commit.author)}">
85 85 ${self.gravatar_with_user(c.file_last_commit.author, 16)} - ${_('created')} ${h.age_component(c.file_last_commit.date)}
86 86 </div>
87 87
88 88 </div>
89 89 <div class="commit">${h.urlify_commit_message(c.file_last_commit.message, None)}</div>
90 90 </div>
91 91
92 92 ## iterate over the files
93 93 % for file in c.files:
94 94 <% renderer = c.render and h.renderer_from_filename(file.path, exclude=['.txt', '.TXT'])%>
95 95 <!--
96 96 <div id="${h.FID('G', file.path)}" class="stats" >
97 97 <a href="${c.gist.gist_url()}">ΒΆ</a>
98 98 <b >${file.path}</b>
99 99 <div>
100 100 ${h.link_to(_('Show as raw'), h.route_path('gist_show_formatted_path', gist_id=c.gist.gist_access_id, revision=file.commit.raw_id, format='raw', f_path=file.path), class_="btn btn-mini")}
101 101 </div>
102 102 </div>
103 103 -->
104 104 <div class="code-body textarea text-area editor">
105 105 %if renderer:
106 106 ${h.render(file.content, renderer=renderer)}
107 107 %else:
108 108 ${h.pygmentize(file,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")}
109 109 %endif
110 110 </div>
111 111 %endfor
112 112 </div>
113 113 </div>
114 114 </div>
115 115
116 116
117 117 </div>
118 118 </%def>
@@ -1,50 +1,42 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Settings administration')}
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 ${_('Settings')}
15 15 </%def>
16 16
17 17 <%def name="menu_bar_nav()">
18 18 ${self.menu_items(active='admin')}
19 19 </%def>
20 20
21 21 <%def name="menu_bar_subnav()">
22 22 ${self.admin_menu()}
23 23 </%def>
24 24
25 25 <%def name="side_bar_nav()">
26 26
27 27 </%def>
28 28
29 29 <%def name="main_content()">
30 30 Hello Admin
31 31 </%def>
32 32
33 33 <%def name="main()">
34 34 <div class="box">
35 35
36 36 ##main
37 <div class='sidebar-col-wrapper'>
38 <div class="sidebar">
39 <ul class="nav nav-pills nav-stacked">
40 ${self.side_bar_nav()}
41 </ul>
42 </div>
43
44 <div class="main-content-auto-width">
37 <div class="main-content-auto-width">
45 38 ${self.main_content()}
46 </div>
47 39 </div>
48 40 </div>
49 41
50 42 </%def> No newline at end of file
@@ -1,219 +1,219 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <div class="panel panel-default">
4 4 <div class="panel-heading">
5 5 <h3 class="panel-title">${_('Repository Group Permissions')}</h3>
6 6 </div>
7 7 <div class="panel-body">
8 8 ${h.secure_form(h.route_path('edit_repo_group_perms_update', repo_group_name=c.repo_group.group_name), request=request)}
9 9 <table id="permissions_manage" class="rctable permissions">
10 10 <tr>
11 11 <th class="td-radio">${_('None')}</th>
12 12 <th class="td-radio">${_('Read')}</th>
13 13 <th class="td-radio">${_('Write')}</th>
14 14 <th class="td-radio">${_('Admin')}</th>
15 15 <th class="td-owner">${_('User/User Group')}</th>
16 16 <th class="td-action"></th>
17 17 <th class="td-action"></th>
18 18 </tr>
19 19 ## USERS
20 20 %for _user in c.repo_group.permissions():
21 21 ## super admin/owner row
22 22 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
23 23 <tr class="perm_admin_row">
24 24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
25 25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
26 26 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
27 27 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
28 28 <td class="td-user">
29 29 ${base.gravatar(_user.email, 16)}
30 30 ${h.link_to_user(_user.username)}
31 31 %if getattr(_user, 'admin_row', None):
32 32 (${_('super admin')})
33 33 %endif
34 34 %if getattr(_user, 'owner_row', None):
35 35 (${_('owner')})
36 36 %endif
37 37 </td>
38 38 <td></td>
39 39 <td class="quick_repo_menu">
40 40 % if c.rhodecode_user.is_admin:
41 41 <i class="icon-more"></i>
42 42 <div class="menu_items_container" style="display: none;">
43 43 <ul class="menu_items">
44 44 <li>
45 45 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-groups-permissions'))}
46 46 </li>
47 47 </ul>
48 48 </div>
49 49 % endif
50 50 </td>
51 51 </tr>
52 52 %else:
53 53 <tr>
54 54 ##forbid revoking permission from yourself, except if you're an super admin
55 55 %if c.rhodecode_user.user_id != _user.user_id or c.rhodecode_user.is_admin:
56 56 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.none', checked=_user.permission=='group.none')}</td>
57 57 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.read', checked=_user.permission=='group.read')}</td>
58 58 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.write', checked=_user.permission=='group.write')}</td>
59 59 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.admin', checked=_user.permission=='group.admin')}</td>
60 60 <td class="td-user">
61 61 ${base.gravatar(_user.email, 16)}
62 62 <span class="user">
63 63 % if _user.username == h.DEFAULT_USER:
64 64 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
65 65 % else:
66 66 ${h.link_to_user(_user.username)}
67 67 %if getattr(_user, 'duplicate_perm', None):
68 68 (${_('inactive duplicate')})
69 69 %endif
70 70 % endif
71 71 </span>
72 72 </td>
73 73 <td class="td-action">
74 74 %if _user.username != h.DEFAULT_USER:
75 75 <span class="btn btn-link btn-danger revoke_perm"
76 76 member="${_user.user_id}" member_type="user">
77 77 ${_('Remove')}
78 78 </span>
79 79 %endif
80 80 </td>
81 81 <td class="quick_repo_menu">
82 82 % if c.rhodecode_user.is_admin:
83 83 <i class="icon-more"></i>
84 84 <div class="menu_items_container" style="display: none;">
85 85 <ul class="menu_items">
86 86 <li>
87 87 % if _user.username == h.DEFAULT_USER:
88 88 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='repositories-groups-permissions'))}
89 89 % else:
90 90 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-groups-permissions'))}
91 91 % endif
92 92 </li>
93 93 </ul>
94 94 </div>
95 95 % endif
96 96 </td>
97 97 %else:
98 98 ## special case for currently logged-in user permissions, we make sure he cannot take his own permissions
99 99 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.none', disabled="disabled")}</td>
100 100 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.read', disabled="disabled")}</td>
101 101 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.write', disabled="disabled")}</td>
102 102 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.admin', disabled="disabled")}</td>
103 103 <td class="td-user">
104 104 ${base.gravatar(_user.email, 16)}
105 105 <span class="user">
106 106 % if _user.username == h.DEFAULT_USER:
107 107 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
108 108 % else:
109 109 ${h.link_to_user(_user.username)}
110 110 %if getattr(_user, 'duplicate_perm', None):
111 111 (${_('inactive duplicate')})
112 112 %endif
113 113 % endif
114 114 <span class="user-perm-help-text">(${_('delegated admin')})</span>
115 115 </span>
116 116 </td>
117 117 <td></td>
118 118 <td class="quick_repo_menu">
119 119 % if c.rhodecode_user.is_admin:
120 120 <i class="icon-more"></i>
121 121 <div class="menu_items_container" style="display: none;">
122 122 <ul class="menu_items">
123 123 <li>
124 124 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-groups-permissions'))}
125 125 </li>
126 126 </ul>
127 127 </div>
128 128 % endif
129 129 </td>
130 130 %endif
131 131 </tr>
132 132 %endif
133 133 %endfor
134 134
135 135 ## USER GROUPS
136 136 %for _user_group in c.repo_group.permission_user_groups(with_members=True):
137 137 <tr id="id${id(_user_group.users_group_name)}">
138 138 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.none', checked=_user_group.permission=='group.none')}</td>
139 139 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.read', checked=_user_group.permission=='group.read')}</td>
140 140 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.write', checked=_user_group.permission=='group.write')}</td>
141 141 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.admin', checked=_user_group.permission=='group.admin')}</td>
142 142 <td class="td-componentname">
143 143 <i class="icon-user-group"></i>
144 %if h.HasPermissionAny('hg.admin')():
144 %if c.is_super_admin:
145 145 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
146 146 ${_user_group.users_group_name}
147 147 </a>
148 148 %else:
149 149 ${h.link_to_group(_user_group.users_group_name)}
150 150 %endif
151 151 (${_('members')}: ${len(_user_group.members)})
152 152 </td>
153 153 <td class="td-action">
154 154 <span class="btn btn-link btn-danger revoke_perm"
155 155 member="${_user_group.users_group_id}" member_type="user_group">
156 156 ${_('Remove')}
157 157 </span>
158 158 </td>
159 159 <td class="quick_repo_menu">
160 160 % if c.rhodecode_user.is_admin:
161 161 <i class="icon-more"></i>
162 162 <div class="menu_items_container" style="display: none;">
163 163 <ul class="menu_items">
164 164 <li>
165 165 ${h.link_to('show permissions', h.route_path('edit_user_group_perms_summary', user_group_id=_user_group.users_group_id, _anchor='repositories-groups-permissions'))}
166 166 </li>
167 167 </ul>
168 168 </div>
169 169 % endif
170 170 </td>
171 171 </tr>
172 172 %endfor
173 173
174 174 <tr class="new_members" id="add_perm_input"></tr>
175 175 <tr>
176 176 <td></td>
177 177 <td></td>
178 178 <td></td>
179 179 <td></td>
180 180 <td></td>
181 181 <td>
182 182 <span id="add_perm" class="link">
183 183 ${_('Add user/user group')}
184 184 </span>
185 185 </td>
186 186 <td></td>
187 187 </tr>
188 188 </table>
189 189
190 190 <div class="fields">
191 191 <div class="field">
192 192 <div class="label label-radio">
193 193 ${_('Apply to children')}:
194 194 </div>
195 195 <div class="radios">
196 196 ${h.radio('recursive', 'none', label=_('None'), checked="checked")}
197 197 ${h.radio('recursive', 'groups', label=_('Repository Groups'))}
198 198 ${h.radio('recursive', 'repos', label=_('Repositories'))}
199 199 ${h.radio('recursive', 'all', label=_('Both'))}
200 200 <span class="help-block">${_('Set or revoke permissions to selected types of children of this group, including non-private repositories and other groups if chosen.')}</span>
201 201 </div>
202 202 </div>
203 203 </div>
204 204 <div class="buttons">
205 205 ${h.submit('save',_('Save'),class_="btn btn-primary")}
206 206 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
207 207 </div>
208 208 ${h.end_form()}
209 209 </div>
210 210 </div>
211 211 <script type="text/javascript">
212 212 $('#add_perm').on('click', function(e){
213 213 addNewPermInput($(this), 'group');
214 214 });
215 215 $('.revoke_perm').on('click', function(e){
216 216 markRevokePermInput($(this), 'group');
217 217 });
218 218 quick_repo_menu();
219 219 </script>
@@ -1,101 +1,101 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Repository groups administration')}
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 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="repo_group_count">0</span> ${_('repository groups')}
14 14 </%def>
15 15
16 16 <%def name="menu_bar_nav()">
17 17 ${self.menu_items(active='admin')}
18 18 </%def>
19 19
20 20 <%def name="menu_bar_subnav()">
21 21 ${self.admin_menu(active='repository_groups')}
22 22 </%def>
23 23
24 24 <%def name="main()">
25 25 <div class="box">
26 26 <div class="title">
27 27
28 28 <ul class="links">
29 %if h.HasPermissionAny('hg.admin','hg.repogroup.create.true')():
29 %if c.can_create_repo_group:
30 30 <li>
31 31 <a href="${h.route_path('repo_group_new')}" class="btn btn-small btn-success">${_(u'Add Repository Group')}</a>
32 32 </li>
33 33 %endif
34 34 </ul>
35 35 </div>
36 36 <div id="repos_list_wrap">
37 37 <table id="group_list_table" class="display"></table>
38 38 </div>
39 39 </div>
40 40
41 41 <script>
42 42 $(document).ready(function() {
43 43
44 44 var get_datatable_count = function(){
45 45 var api = $('#group_list_table').dataTable().api();
46 46 $('#repo_group_count').text(api.page.info().recordsDisplay);
47 47 };
48 48
49 49 // repo group list
50 50 $('#group_list_table').DataTable({
51 51 data: ${c.data|n},
52 52 dom: 'rtp',
53 53 pageLength: ${c.visual.admin_grid_items},
54 54 order: [[ 0, "asc" ]],
55 55 columns: [
56 56 { data: {"_": "name",
57 57 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
58 58 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
59 59 { data: {"_": "desc",
60 60 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
61 61 { data: {"_": "last_change",
62 62 "sort": "last_change_raw",
63 63 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
64 64 { data: {"_": "top_level_repos",
65 65 "sort": "top_level_repos"}, title: "${_('Number of top level repositories')}" },
66 66 { data: {"_": "owner",
67 67 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
68 68 { data: {"_": "action",
69 69 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
70 70 ],
71 71 language: {
72 72 paginate: DEFAULT_GRID_PAGINATION,
73 73 emptyTable: _gettext("No repository groups available yet.")
74 74 },
75 75 "initComplete": function( settings, json ) {
76 76 get_datatable_count();
77 77 quick_repo_menu();
78 78 }
79 79 });
80 80
81 81 // update the counter when doing search
82 82 $('#group_list_table').on( 'search.dt', function (e,settings) {
83 83 get_datatable_count();
84 84 });
85 85
86 86 // filter, filter both grids
87 87 $('#q_filter').on( 'keyup', function () {
88 88
89 89 var repo_group_api = $('#group_list_table').dataTable().api();
90 90 repo_group_api
91 91 .columns(0)
92 92 .search(this.value)
93 93 .draw();
94 94 });
95 95
96 96 // refilter table if page load via back button
97 97 $("#q_filter").trigger('keyup');
98 98 });
99 99 </script>
100 100 </%def>
101 101
@@ -1,202 +1,202 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <div class="panel panel-default">
4 4 <div class="panel-heading">
5 5 <h3 class="panel-title">${_('Repository Permissions')}</h3>
6 6 </div>
7 7 <div class="panel-body">
8 8 ${h.secure_form(h.route_path('edit_repo_perms', repo_name=c.repo_name), request=request)}
9 9 <table id="permissions_manage" class="rctable permissions">
10 10 <tr>
11 11 <th class="td-radio">${_('None')}</th>
12 12 <th class="td-radio">${_('Read')}</th>
13 13 <th class="td-radio">${_('Write')}</th>
14 14 <th class="td-radio">${_('Admin')}</th>
15 15 <th class="td-owner">${_('User/User Group')}</th>
16 16 <th class="td-action"></th>
17 17 <th class="td-action"></th>
18 18 </tr>
19 19 ## USERS
20 20 %for _user in c.rhodecode_db_repo.permissions():
21 21 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
22 22 <tr class="perm_admin_row">
23 23 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
24 24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
25 25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
26 26 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
27 27 <td class="td-user">
28 28 ${base.gravatar(_user.email, 16)}
29 29 ${h.link_to_user(_user.username)}
30 30 %if getattr(_user, 'admin_row', None):
31 31 (${_('super admin')})
32 32 %endif
33 33 %if getattr(_user, 'owner_row', None):
34 34 (${_('owner')})
35 35 %endif
36 36 </td>
37 37 <td></td>
38 38 <td class="quick_repo_menu">
39 39 % if c.rhodecode_user.is_admin:
40 40 <i class="icon-more"></i>
41 41 <div class="menu_items_container" style="display: none;">
42 42 <ul class="menu_items">
43 43 <li>
44 44 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-permissions'))}
45 45 </li>
46 46 </ul>
47 47 </div>
48 48 % endif
49 49 </td>
50 50 </tr>
51 51 %elif _user.username == h.DEFAULT_USER and c.rhodecode_db_repo.private:
52 52 <tr>
53 53 <td colspan="4">
54 54 <span class="private_repo_msg">
55 55 <strong title="${h.tooltip(_user.permission)}">${_('private repository')}</strong>
56 56 </span>
57 57 </td>
58 58 <td class="private_repo_msg">
59 59 ${base.gravatar(h.DEFAULT_USER_EMAIL, 16)}
60 60 ${h.DEFAULT_USER} - ${_('only users/user groups explicitly added here will have access')}</td>
61 61 <td></td>
62 62 <td class="quick_repo_menu">
63 63 % if c.rhodecode_user.is_admin:
64 64 <i class="icon-more"></i>
65 65 <div class="menu_items_container" style="display: none;">
66 66 <ul class="menu_items">
67 67 <li>
68 68 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='repositories-permissions'))}
69 69 </li>
70 70 </ul>
71 71 </div>
72 72 % endif
73 73 </td>
74 74 </tr>
75 75 %else:
76 76 <% used_by_n_rules = len(getattr(_user, 'branch_rules', None) or []) %>
77 77 <tr>
78 78 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.none', checked=_user.permission=='repository.none', disabled="disabled" if (used_by_n_rules and _user.username != h.DEFAULT_USER) else None)}</td>
79 79 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.read', checked=_user.permission=='repository.read', disabled="disabled" if (used_by_n_rules and _user.username != h.DEFAULT_USER) else None)}</td>
80 80 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.write', checked=_user.permission=='repository.write')}</td>
81 81 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.admin', checked=_user.permission=='repository.admin')}</td>
82 82 <td class="td-user">
83 83 ${base.gravatar(_user.email, 16)}
84 84 <span class="user">
85 85 % if _user.username == h.DEFAULT_USER:
86 86 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
87 87 % else:
88 88 ${h.link_to_user(_user.username)}
89 89 %if getattr(_user, 'duplicate_perm', None):
90 90 (${_('inactive duplicate')})
91 91 %endif
92 92 %if getattr(_user, 'branch_rules', None):
93 93 % if used_by_n_rules == 1:
94 94 (${_('used by {} branch rule, requires write+ permissions').format(used_by_n_rules)})
95 95 % else:
96 96 (${_('used by {} branch rules, requires write+ permissions').format(used_by_n_rules)})
97 97 % endif
98 98 %endif
99 99 % endif
100 100 </span>
101 101 </td>
102 102 <td class="td-action">
103 103 %if _user.username != h.DEFAULT_USER and getattr(_user, 'branch_rules', None) is None:
104 104 <span class="btn btn-link btn-danger revoke_perm"
105 105 member="${_user.user_id}" member_type="user">
106 106 ${_('Remove')}
107 107 </span>
108 108 %endif
109 109 </td>
110 110 <td class="quick_repo_menu">
111 111 % if c.rhodecode_user.is_admin:
112 112 <i class="icon-more"></i>
113 113 <div class="menu_items_container" style="display: none;">
114 114 <ul class="menu_items">
115 115 <li>
116 116 % if _user.username == h.DEFAULT_USER:
117 117 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='repositories-permissions'))}
118 118 % else:
119 119 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-permissions'))}
120 120 % endif
121 121 </li>
122 122 </ul>
123 123 </div>
124 124 % endif
125 125 </td>
126 126 </tr>
127 127 %endif
128 128 %endfor
129 129
130 130 ## USER GROUPS
131 131 %for _user_group in c.rhodecode_db_repo.permission_user_groups(with_members=True):
132 132 <tr>
133 133 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.none', checked=_user_group.permission=='repository.none')}</td>
134 134 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.read', checked=_user_group.permission=='repository.read')}</td>
135 135 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.write', checked=_user_group.permission=='repository.write')}</td>
136 136 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.admin', checked=_user_group.permission=='repository.admin')}</td>
137 137 <td class="td-componentname">
138 138 <i class="icon-user-group"></i>
139 %if h.HasPermissionAny('hg.admin')():
139 %if c.is_super_admin:
140 140 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
141 141 ${_user_group.users_group_name}
142 142 </a>
143 143 %else:
144 144 ${h.link_to_group(_user_group.users_group_name)}
145 145 %endif
146 146 (${_('members')}: ${len(_user_group.members)})
147 147 </td>
148 148 <td class="td-action">
149 149 <span class="btn btn-link btn-danger revoke_perm"
150 150 member="${_user_group.users_group_id}" member_type="user_group">
151 151 ${_('Remove')}
152 152 </span>
153 153 </td>
154 154 <td class="quick_repo_menu">
155 155 % if c.rhodecode_user.is_admin:
156 156 <i class="icon-more"></i>
157 157 <div class="menu_items_container" style="display: none;">
158 158 <ul class="menu_items">
159 159 <li>
160 160 ${h.link_to('show permissions', h.route_path('edit_user_group_perms_summary', user_group_id=_user_group.users_group_id, _anchor='repositories-permissions'))}
161 161 </li>
162 162 </ul>
163 163 </div>
164 164 % endif
165 165 </td>
166 166 </tr>
167 167 %endfor
168 168 <tr class="new_members" id="add_perm_input"></tr>
169 169
170 170 <tr>
171 171 <td></td>
172 172 <td></td>
173 173 <td></td>
174 174 <td></td>
175 175 <td></td>
176 176 <td>
177 177 <span id="add_perm" class="link">
178 178 ${_('Add user/user group')}
179 179 </span>
180 180 </td>
181 181 <td></td>
182 182 </tr>
183 183
184 184 </table>
185 185
186 186 <div class="buttons">
187 187 ${h.submit('save',_('Save'),class_="btn btn-primary")}
188 188 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
189 189 </div>
190 190 ${h.end_form()}
191 191 </div>
192 192 </div>
193 193
194 194 <script type="text/javascript">
195 195 $('#add_perm').on('click', function(e){
196 196 addNewPermInput($(this), 'repository');
197 197 });
198 198 $('.revoke_perm').on('click', function(e){
199 199 markRevokePermInput($(this), 'repository');
200 200 });
201 201 quick_repo_menu();
202 202 </script>
@@ -1,104 +1,104 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Repositories administration')}
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 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="repo_count">0</span> ${_('repositories')}
14 14 </%def>
15 15
16 16 <%def name="menu_bar_nav()">
17 17 ${self.menu_items(active='admin')}
18 18 </%def>
19 19
20 20 <%def name="menu_bar_subnav()">
21 21 ${self.admin_menu(active='repositories')}
22 22 </%def>
23 23
24 24 <%def name="main()">
25 25 <div class="box">
26 26 <div class="title">
27 27 <ul class="links">
28 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
28 %if c.can_create_repo:
29 29 <li>
30 30 <a href="${h.route_path('repo_new')}" class="btn btn-small btn-success">${_(u'Add Repository')}</a>
31 31 </li>
32 32 %endif
33 33 </ul>
34 34 </div>
35 35 <div id="repos_list_wrap">
36 36 <table id="repo_list_table" class="display"></table>
37 37 </div>
38 38 </div>
39 39
40 40 <script>
41 41 $(document).ready(function() {
42 42
43 43 var get_datatable_count = function(){
44 44 var api = $('#repo_list_table').dataTable().api();
45 45 $('#repo_count').text(api.page.info().recordsDisplay);
46 46 };
47 47
48 48
49 49 // repo list
50 50 $('#repo_list_table').DataTable({
51 51 data: ${c.data|n},
52 52 dom: 'rtp',
53 53 pageLength: ${c.visual.admin_grid_items},
54 54 order: [[ 0, "asc" ]],
55 55 columns: [
56 56 { data: {"_": "name",
57 57 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
58 58 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
59 59 { data: {"_": "desc",
60 60 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
61 61 { data: {"_": "last_change",
62 62 "sort": "last_change_raw",
63 63 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
64 64 { data: {"_": "last_changeset",
65 65 "sort": "last_changeset_raw",
66 66 "type": Number}, title: "${_('Commit')}", className: "td-commit" },
67 67 { data: {"_": "owner",
68 68 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
69 69 { data: {"_": "state",
70 70 "sort": "state"}, title: "${_('State')}", className: "td-tags td-state" },
71 71 { data: {"_": "action",
72 72 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
73 73 ],
74 74 language: {
75 75 paginate: DEFAULT_GRID_PAGINATION,
76 76 emptyTable:_gettext("No repositories available yet.")
77 77 },
78 78 "initComplete": function( settings, json ) {
79 79 get_datatable_count();
80 80 quick_repo_menu();
81 81 }
82 82 });
83 83
84 84 // update the counter when doing search
85 85 $('#repo_list_table').on( 'search.dt', function (e,settings) {
86 86 get_datatable_count();
87 87 });
88 88
89 89 // filter, filter both grids
90 90 $('#q_filter').on( 'keyup', function () {
91 91 var repo_api = $('#repo_list_table').dataTable().api();
92 92 repo_api
93 93 .columns(0)
94 94 .search(this.value)
95 95 .draw();
96 96 });
97 97
98 98 // refilter table if page load via back button
99 99 $("#q_filter").trigger('keyup');
100 100 });
101 101
102 102 </script>
103 103
104 104 </%def>
@@ -1,207 +1,207 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <div class="panel panel-default">
4 4 <div class="panel-heading">
5 5 <h3 class="panel-title">${_('User Group Permissions')}</h3>
6 6 </div>
7 7 <div class="panel-body">
8 8 ${h.secure_form(h.route_path('edit_user_group_perms_update', user_group_id=c.user_group.users_group_id), request=request)}
9 9 <table id="permissions_manage" class="rctable permissions">
10 10 <tr>
11 11 <th class="td-radio">${_('None')}</th>
12 12 <th class="td-radio">${_('Read')}</th>
13 13 <th class="td-radio">${_('Write')}</th>
14 14 <th class="td-radio">${_('Admin')}</th>
15 15 <th>${_('User/User Group')}</th>
16 16 <th class="td-action"></th>
17 17 <th class="td-action"></th>
18 18 </tr>
19 19 ## USERS
20 20 %for _user in c.user_group.permissions():
21 21 ## super admin/owner row
22 22 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
23 23 <tr class="perm_admin_row">
24 24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
25 25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
26 26 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
27 27 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
28 28 <td class="td-user">
29 29 ${base.gravatar(_user.email, 16)}
30 30 <span class="user">
31 31 ${h.link_to_user(_user.username)}
32 32 %if getattr(_user, 'admin_row', None):
33 33 (${_('super admin')})
34 34 %endif
35 35 %if getattr(_user, 'owner_row', None):
36 36 (${_('owner')})
37 37 %endif
38 38 </span>
39 39 </td>
40 40 <td></td>
41 41 <td class="quick_repo_menu">
42 42 % if c.rhodecode_user.is_admin:
43 43 <i class="icon-more"></i>
44 44 <div class="menu_items_container" style="display: none;">
45 45 <ul class="menu_items">
46 46 <li>
47 47 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='user-groups-permissions'))}
48 48 </li>
49 49 </ul>
50 50 </div>
51 51 % endif
52 52 </td>
53 53 </tr>
54 54 %else:
55 55 ##forbid revoking permission from yourself, except if you're an super admin
56 56 <tr>
57 57 %if c.rhodecode_user.user_id != _user.user_id or c.rhodecode_user.is_admin:
58 58 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.none')}</td>
59 59 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.read')}</td>
60 60 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.write')}</td>
61 61 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.admin')}</td>
62 62 <td class="td-user">
63 63 ${base.gravatar(_user.email, 16)}
64 64 <span class="user">
65 65 % if _user.username == h.DEFAULT_USER:
66 66 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
67 67 % else:
68 68 ${h.link_to_user(_user.username)}
69 69 %if getattr(_user, 'duplicate_perm', None):
70 70 (${_('inactive duplicate')})
71 71 %endif
72 72 % endif
73 73 </span>
74 74 </td>
75 75 <td class="td-action">
76 76 %if _user.username != h.DEFAULT_USER:
77 77 <span class="btn btn-link btn-danger revoke_perm"
78 78 member="${_user.user_id}" member_type="user">
79 79 ${_('Remove')}
80 80 </span>
81 81 %endif
82 82 </td>
83 83 <td class="quick_repo_menu">
84 84 % if c.rhodecode_user.is_admin:
85 85 <i class="icon-more"></i>
86 86 <div class="menu_items_container" style="display: none;">
87 87 <ul class="menu_items">
88 88 <li>
89 89 % if _user.username == h.DEFAULT_USER:
90 90 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='user-groups-permissions'))}
91 91 % else:
92 92 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='user-groups-permissions'))}
93 93 % endif
94 94 </li>
95 95 </ul>
96 96 </div>
97 97 % endif
98 98 </td>
99 99 %else:
100 100 ## special case for currently logged-in user permissions, we make sure he cannot take his own permissions
101 101 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.none', disabled="disabled")}</td>
102 102 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.read', disabled="disabled")}</td>
103 103 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.write', disabled="disabled")}</td>
104 104 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.admin', disabled="disabled")}</td>
105 105 <td class="td-user">
106 106 ${base.gravatar(_user.email, 16)}
107 107 <span class="user">
108 108 % if _user.username == h.DEFAULT_USER:
109 109 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
110 110 % else:
111 111 ${h.link_to_user(_user.username)}
112 112 %if getattr(_user, 'duplicate_perm', None):
113 113 (${_('inactive duplicate')})
114 114 %endif
115 115 % endif
116 116 <span class="user-perm-help-text">(${_('delegated admin')})</span>
117 117 </span>
118 118 </td>
119 119 <td></td>
120 120 <td class="quick_repo_menu">
121 121 % if c.rhodecode_user.is_admin:
122 122 <i class="icon-more"></i>
123 123 <div class="menu_items_container" style="display: none;">
124 124 <ul class="menu_items">
125 125 <li>
126 126 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='user-groups-permissions'))}
127 127 </li>
128 128 </ul>
129 129 </div>
130 130 % endif
131 131 </td>
132 132 %endif
133 133 </tr>
134 134 %endif
135 135 %endfor
136 136
137 137 ## USER GROUPS
138 138 %for _user_group in c.user_group.permission_user_groups(with_members=True):
139 139 <tr>
140 140 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.none')}</td>
141 141 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.read')}</td>
142 142 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.write')}</td>
143 143 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.admin')}</td>
144 144 <td class="td-user">
145 145 <i class="icon-user-group"></i>
146 %if h.HasPermissionAny('hg.admin')():
146 %if c.is_super_admin:
147 147 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
148 148 ${_user_group.users_group_name}
149 149 </a>
150 150 %else:
151 151 ${h.link_to_group(_user_group.users_group_name)}
152 152 %endif
153 153 (${_('members')}: ${len(_user_group.members)})
154 154 </td>
155 155 <td class="td-action">
156 156 <span class="btn btn-link btn-danger revoke_perm"
157 157 member="${_user_group.users_group_id}" member_type="user_group">
158 158 ${_('Remove')}
159 159 </span>
160 160 </td>
161 161 <td class="quick_repo_menu">
162 162 % if c.rhodecode_user.is_admin:
163 163 <i class="icon-more"></i>
164 164 <div class="menu_items_container" style="display: none;">
165 165 <ul class="menu_items">
166 166 <li>
167 167 ${h.link_to('show permissions', h.route_path('edit_user_group_perms_summary', user_group_id=_user_group.users_group_id, _anchor='user-groups-permissions'))}
168 168 </li>
169 169 </ul>
170 170 </div>
171 171 % endif
172 172 </td>
173 173 </tr>
174 174 %endfor
175 175 <tr class="new_members" id="add_perm_input"></tr>
176 176 <tr>
177 177 <td></td>
178 178 <td></td>
179 179 <td></td>
180 180 <td></td>
181 181 <td></td>
182 182 <td>
183 183 <span id="add_perm" class="link">
184 184 ${_('Add user/user group')}
185 185 </span>
186 186 </td>
187 187 <td></td>
188 188 </tr>
189 189 </table>
190 190
191 191 <div class="buttons">
192 192 ${h.submit('save',_('Save'),class_="btn btn-primary")}
193 193 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
194 194 </div>
195 195 ${h.end_form()}
196 196 </div>
197 197 </div>
198 198
199 199 <script type="text/javascript">
200 200 $('#add_perm').on('click', function(e){
201 201 addNewPermInput($(this), 'usergroup');
202 202 });
203 203 $('.revoke_perm').on('click', function(e){
204 204 markRevokePermInput($(this), 'usergroup');
205 205 });
206 206 quick_repo_menu()
207 207 </script>
@@ -1,118 +1,118 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('User groups administration')}
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 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="user_group_count">0</span>
14 14 </%def>
15 15
16 16 <%def name="menu_bar_nav()">
17 17 ${self.menu_items(active='admin')}
18 18 </%def>
19 19
20 20 <%def name="menu_bar_subnav()">
21 21 ${self.admin_menu(active='user_groups')}
22 22 </%def>
23 23
24 24 <%def name="main()">
25 25 <div class="box">
26 26
27 27 <div class="title">
28 28 <ul class="links">
29 %if h.HasPermissionAny('hg.admin', 'hg.usergroup.create.true')():
29 %if c.can_create_user_group:
30 30 <li>
31 31 <a href="${h.route_path('user_groups_new')}" class="btn btn-small btn-success">${_(u'Add User Group')}</a>
32 32 </li>
33 33 %endif
34 34 </ul>
35 35 </div>
36 36
37 37 <div id="repos_list_wrap">
38 38 <table id="user_group_list_table" class="display"></table>
39 39 </div>
40 40
41 41 </div>
42 42 <script>
43 43 $(document).ready(function() {
44 44 var $userGroupsListTable = $('#user_group_list_table');
45 45
46 46 // user list
47 47 $userGroupsListTable.DataTable({
48 48 processing: true,
49 49 serverSide: true,
50 50 ajax: {
51 51 "url": "${h.route_path('user_groups_data')}",
52 52 "dataSrc": function (json) {
53 53 var filteredCount = json.recordsFiltered;
54 54 var filteredInactiveCount = json.recordsFilteredInactive;
55 55 var totalInactive = json.recordsTotalInactive;
56 56 var total = json.recordsTotal;
57 57
58 58 var _text = _gettext(
59 59 "{0} ({1} inactive) of {2} user groups ({3} inactive)").format(
60 60 filteredCount, filteredInactiveCount, total, totalInactive);
61 61
62 62 if (total === filteredCount) {
63 63 _text = _gettext(
64 64 "{0} user groups ({1} inactive)").format(total, totalInactive);
65 65 }
66 66 $('#user_group_count').text(_text);
67 67 return json.data;
68 68 },
69 69 },
70 70
71 71 dom: 'rtp',
72 72 pageLength: ${c.visual.admin_grid_items},
73 73 order: [[ 0, "asc" ]],
74 74 columns: [
75 75 { data: {"_": "users_group_name",
76 76 "sort": "users_group_name"}, title: "${_('Name')}", className: "td-componentname" },
77 77 { data: {"_": "description",
78 78 "sort": "description"}, title: "${_('Description')}", className: "td-description" },
79 79 { data: {"_": "members",
80 80 "sort": "members"}, title: "${_('Members')}", className: "td-number" },
81 81 { data: {"_": "sync",
82 82 "sort": "sync"}, title: "${_('Sync')}", className: "td-sync" },
83 83 { data: {"_": "active",
84 84 "sort": "active"}, title: "${_('Active')}", className: "td-active" },
85 85 { data: {"_": "owner",
86 86 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
87 87 { data: {"_": "action",
88 88 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false}
89 89 ],
90 90 language: {
91 91 paginate: DEFAULT_GRID_PAGINATION,
92 92 sProcessing: _gettext('loading...'),
93 93 emptyTable: _gettext("No user groups available yet.")
94 94 }
95 95 });
96 96
97 97 $userGroupsListTable.on('xhr.dt', function(e, settings, json, xhr){
98 98 $userGroupsListTable.css('opacity', 1);
99 99 });
100 100
101 101 $userGroupsListTable.on('preXhr.dt', function(e, settings, data){
102 102 $userGroupsListTable.css('opacity', 0.3);
103 103 });
104 104
105 105 // filter
106 106 $('#q_filter').on('keyup',
107 107 $.debounce(250, function() {
108 108 $('#user_group_list_table').DataTable().search(
109 109 $('#q_filter').val()
110 110 ).draw();
111 111 })
112 112 );
113 113
114 114 });
115 115
116 116 </script>
117 117
118 118 </%def>
@@ -1,942 +1,942 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="root.mako"/>
3 3
4 4 <%include file="/ejs_templates/templates.html"/>
5 5
6 6 <div class="outerwrapper">
7 7 <!-- HEADER -->
8 8 <div class="header">
9 9 <div id="header-inner" class="wrapper">
10 10 <div id="logo">
11 11 <div class="logo-wrapper">
12 12 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
13 13 </div>
14 14 % if c.rhodecode_name:
15 15 <div class="branding">
16 16 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
17 17 </div>
18 18 % endif
19 19 </div>
20 20 <!-- MENU BAR NAV -->
21 21 ${self.menu_bar_nav()}
22 22 <!-- END MENU BAR NAV -->
23 23 </div>
24 24 </div>
25 25 ${self.menu_bar_subnav()}
26 26 <!-- END HEADER -->
27 27
28 28 <!-- CONTENT -->
29 29 <div id="content" class="wrapper">
30 30
31 31 <rhodecode-toast id="notifications"></rhodecode-toast>
32 32
33 33 <div class="main">
34 34 ${next.main()}
35 35 </div>
36 36 </div>
37 37 <!-- END CONTENT -->
38 38
39 39 </div>
40 40 <!-- FOOTER -->
41 41 <div id="footer">
42 42 <div id="footer-inner" class="title wrapper">
43 43 <div>
44 44 <p class="footer-link-right">
45 45 % if c.visual.show_version:
46 46 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
47 47 % endif
48 48 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
49 49 % if c.visual.rhodecode_support_url:
50 50 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
51 51 % endif
52 52 </p>
53 53 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
54 54 <p class="server-instance" style="display:${sid}">
55 55 ## display hidden instance ID if specially defined
56 56 % if c.rhodecode_instanceid:
57 57 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
58 58 % endif
59 59 </p>
60 60 </div>
61 61 </div>
62 62 </div>
63 63
64 64 <!-- END FOOTER -->
65 65
66 66 ### MAKO DEFS ###
67 67
68 68 <%def name="menu_bar_subnav()">
69 69 </%def>
70 70
71 71 <%def name="breadcrumbs(class_='breadcrumbs')">
72 72 <div class="${class_}">
73 73 ${self.breadcrumbs_links()}
74 74 </div>
75 75 </%def>
76 76
77 77 <%def name="admin_menu(active=None)">
78 78 <%
79 is_super_admin = c.rhodecode_user.is_admin
80 repositories=c.rhodecode_user.repositories_admin
81 repository_groups=c.rhodecode_user.repository_groups_admin
82 user_groups=c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')()
83 is_delegated_admin = repositories or repository_groups or user_groups
84
85 79 def is_active(selected):
86 80 if selected == active:
87 81 return "active"
88 82 %>
89 83
90 84 <div id="context-bar">
91 85 <div class="wrapper">
92 86 <div class="title">
93 87 <div class="title-content">
94 88 <div class="title-main">
95 89 % if is_super_admin:
96 90 ${_('Super Admin Panel')}
97 91 % else:
98 92 ${_('Delegated Admin Panel')}
99 93 % endif
100 94 </div>
101 95 </div>
102 96 </div>
103 97
104 98 <ul id="context-pages" class="navigation horizontal-list">
105 99
106 100 ## super admin case
107 % if is_super_admin:
101 % if c.is_super_admin:
108 102 <li class="${is_active('audit_logs')}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
109 103 <li class="${is_active('repositories')}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
110 104 <li class="${is_active('repository_groups')}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
111 105 <li class="${is_active('users')}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
112 106 <li class="${is_active('user_groups')}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
113 107 <li class="${is_active('permissions')}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
114 108 <li class="${is_active('authentication')}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
115 109 <li class="${is_active('integrations')}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
116 110 <li class="${is_active('defaults')}"><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
117 111 <li class="${is_active('settings')}"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
118 112
119 113 ## delegated admin
120 % elif is_delegated_admin:
114 % elif c.is_delegated_admin:
115 <%
116 repositories=c.auth_user.repositories_admin or c.can_create_repo
117 repository_groups=c.auth_user.repository_groups_admin or c.can_create_repo_group
118 user_groups=c.auth_user.user_groups_admin or c.can_create_user_group
119 %>
120
121 121 %if repositories:
122 122 <li class="${is_active('repositories')} local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
123 123 %endif
124 124 %if repository_groups:
125 125 <li class="${is_active('repository_groups')} local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
126 126 %endif
127 127 %if user_groups:
128 128 <li class="${is_active('user_groups')} local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
129 129 %endif
130 130 % endif
131 131 </ul>
132 132
133 133 </div>
134 134 <div class="clear"></div>
135 135 </div>
136 136 </%def>
137 137
138 138 <%def name="dt_info_panel(elements)">
139 139 <dl class="dl-horizontal">
140 140 %for dt, dd, title, show_items in elements:
141 141 <dt>${dt}:</dt>
142 142 <dd title="${h.tooltip(title)}">
143 143 %if callable(dd):
144 144 ## allow lazy evaluation of elements
145 145 ${dd()}
146 146 %else:
147 147 ${dd}
148 148 %endif
149 149 %if show_items:
150 150 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
151 151 %endif
152 152 </dd>
153 153
154 154 %if show_items:
155 155 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
156 156 %for item in show_items:
157 157 <dt></dt>
158 158 <dd>${item}</dd>
159 159 %endfor
160 160 </div>
161 161 %endif
162 162
163 163 %endfor
164 164 </dl>
165 165 </%def>
166 166
167 167 <%def name="gravatar(email, size=16)">
168 168 <%
169 169 if (size > 16):
170 170 gravatar_class = 'gravatar gravatar-large'
171 171 else:
172 172 gravatar_class = 'gravatar'
173 173 %>
174 174 <%doc>
175 175 TODO: johbo: For now we serve double size images to make it smooth
176 176 for retina. This is how it worked until now. Should be replaced
177 177 with a better solution at some point.
178 178 </%doc>
179 179 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
180 180 </%def>
181 181
182 182
183 183 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
184 184 <% email = h.email_or_none(contact) %>
185 185 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
186 186 ${self.gravatar(email, size)}
187 187 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
188 188 </div>
189 189 </%def>
190 190
191 191
192 192 <%def name="repo_page_title(repo_instance)">
193 193 <div class="title-content">
194 194 <div class="title-main">
195 195 ## SVN/HG/GIT icons
196 196 %if h.is_hg(repo_instance):
197 197 <i class="icon-hg"></i>
198 198 %endif
199 199 %if h.is_git(repo_instance):
200 200 <i class="icon-git"></i>
201 201 %endif
202 202 %if h.is_svn(repo_instance):
203 203 <i class="icon-svn"></i>
204 204 %endif
205 205
206 206 ## public/private
207 207 %if repo_instance.private:
208 208 <i class="icon-repo-private"></i>
209 209 %else:
210 210 <i class="icon-repo-public"></i>
211 211 %endif
212 212
213 213 ## repo name with group name
214 214 ${h.breadcrumb_repo_link(repo_instance)}
215 215
216 216 </div>
217 217
218 218 ## FORKED
219 219 %if repo_instance.fork:
220 220 <p class="discreet">
221 221 <i class="icon-code-fork"></i> ${_('Fork of')}
222 222 ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))}
223 223 </p>
224 224 %endif
225 225
226 226 ## IMPORTED FROM REMOTE
227 227 %if repo_instance.clone_uri:
228 228 <p class="discreet">
229 229 <i class="icon-code-fork"></i> ${_('Clone from')}
230 230 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
231 231 </p>
232 232 %endif
233 233
234 234 ## LOCKING STATUS
235 235 %if repo_instance.locked[0]:
236 236 <p class="locking_locked discreet">
237 237 <i class="icon-repo-lock"></i>
238 238 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
239 239 </p>
240 240 %elif repo_instance.enable_locking:
241 241 <p class="locking_unlocked discreet">
242 242 <i class="icon-repo-unlock"></i>
243 243 ${_('Repository not locked. Pull repository to lock it.')}
244 244 </p>
245 245 %endif
246 246
247 247 </div>
248 248 </%def>
249 249
250 250 <%def name="repo_menu(active=None)">
251 251 <%
252 252 def is_active(selected):
253 253 if selected == active:
254 254 return "active"
255 255 %>
256 256
257 257 <!--- REPO CONTEXT BAR -->
258 258 <div id="context-bar">
259 259 <div class="wrapper">
260 260
261 261 <div class="title">
262 262 ${self.repo_page_title(c.rhodecode_db_repo)}
263 263 </div>
264 264
265 265 <ul id="context-pages" class="navigation horizontal-list">
266 266 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
267 267 <li class="${is_active('changelog')}"><a class="menulink" href="${h.route_path('repo_changelog', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
268 268 <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
269 269 <li class="${is_active('compare')}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
270 270
271 271 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
272 272 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
273 273 <li class="${is_active('showpullrequest')}">
274 274 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
275 275 <div class="menulabel">
276 276 %if c.repository_pull_requests == 1:
277 277 ${c.repository_pull_requests} ${_('Pull Request')}
278 278 %else:
279 279 ${c.repository_pull_requests} ${_('Pull Requests')}
280 280 %endif
281 281 </div>
282 282 </a>
283 283 </li>
284 284 %endif
285 285
286 286 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
287 287 <li class="${is_active('settings')}"><a class="menulink" href="${h.route_path('edit_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Repository Settings')}</div></a></li>
288 288 %endif
289 289
290 290 <li class="${is_active('options')}">
291 291 <a class="menulink dropdown">
292 292 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
293 293 </a>
294 294 <ul class="submenu">
295 295
296 296 %if c.rhodecode_db_repo.fork:
297 297 <li>
298 298 <a title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
299 299 href="${h.route_path('repo_compare',
300 300 repo_name=c.rhodecode_db_repo.fork.repo_name,
301 301 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
302 302 source_ref=c.rhodecode_db_repo.landing_rev[1],
303 303 target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
304 304 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
305 305 _query=dict(merge=1))}"
306 306 >
307 307 ${_('Compare fork')}
308 308 </a>
309 309 </li>
310 310 %endif
311 311
312 312 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
313 313 %if c.rhodecode_db_repo.locked[0]:
314 314 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
315 315 %else:
316 316 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
317 317 %endif
318 318 %endif
319 319 %if c.rhodecode_user.username != h.DEFAULT_USER:
320 320 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
321 321 <li><a href="${h.route_path('repo_fork_new',repo_name=c.repo_name)}">${_('Fork')}</a></li>
322 322 <li><a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
323 323 %endif
324 324 %endif
325 325 </ul>
326 326 </li>
327 327 </ul>
328 328 </div>
329 329 <div class="clear"></div>
330 330 </div>
331 331 % if c.rhodecode_db_repo.archived:
332 332 <div class="alert alert-warning text-center">
333 333 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
334 334 </div>
335 335 % endif
336 336 <!--- REPO END CONTEXT BAR -->
337 337
338 338 </%def>
339 339
340 340 <%def name="repo_group_page_title(repo_group_instance)">
341 341 <div class="title-content">
342 342 <div class="title-main">
343 343 ## Repository Group icon
344 344 <i class="icon-folder-close"></i>
345 345
346 346 ## repo name with group name
347 347 ${h.breadcrumb_repo_group_link(repo_group_instance)}
348 348 </div>
349 349
350 350 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
351 351 <div class="repo-group-desc">
352 352 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
353 353 </div>
354 354
355 355 </div>
356 356 </%def>
357 357
358 358 <%def name="repo_group_menu(active=None)">
359 359 <%
360 360 def is_active(selected):
361 361 if selected == active:
362 362 return "active"
363 363
364 is_admin = h.HasPermissionAny('hg.admin')('can create repos index page')
365
366 364 gr_name = c.repo_group.group_name if c.repo_group else None
367 365 # create repositories with write permission on group is set to true
368 366 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
369 367 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
370 368 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
371 369
372 370 %>
373 371
374 372 <!--- REPO GROUP CONTEXT BAR -->
375 373 <div id="context-bar">
376 374 <div class="wrapper">
377 375 <div class="title">
378 376 ${self.repo_group_page_title(c.repo_group)}
379 377 </div>
380 378
381 379 <ul id="context-pages" class="navigation horizontal-list">
382 380 <li class="${is_active('home')}"><a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a></li>
383 % if is_admin or group_admin:
381 % if c.is_super_admin or group_admin:
384 382 <li class="${is_active('settings')}"><a class="menulink" href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}"><div class="menulabel">${_('Group Settings')}</div></a></li>
385 383 % endif
386 384
387 385 <li class="${is_active('options')}">
388 386 <a class="menulink dropdown">
389 387 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
390 388 </a>
391 389 <ul class="submenu">
392 %if is_admin or group_admin or (group_write and create_on_write):
390 %if c.is_super_admin or group_admin or (group_write and create_on_write):
393 391 <li><a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('Add Repository')}</a></li>
394 392 %endif
395 %if is_admin or group_admin:
393 %if c.is_super_admin or group_admin:
396 394 <li><a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}">${_(u'Add Parent Group')}</a></li>
397 395 %endif
398 396 </ul>
399 397 </li>
400 398 </ul>
401 399 </div>
402 400 <div class="clear"></div>
403 401 </div>
404 402
405 403 <!--- REPO GROUP CONTEXT BAR -->
406 404
407 405 </%def>
408 406
409 407
410 408 <%def name="usermenu(active=False)">
411 409 ## USER MENU
412 410 <li id="quick_login_li" class="${'active' if active else ''}">
413 411 % if c.rhodecode_user.username == h.DEFAULT_USER:
414 412 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
415 413 ${gravatar(c.rhodecode_user.email, 20)}
416 414 <span class="user">
417 415 <span>${_('Sign in')}</span>
418 416 </span>
419 417 </a>
420 418 % else:
421 419 ## logged in user
422 420 <a id="quick_login_link" class="menulink childs">
423 421 ${gravatar(c.rhodecode_user.email, 20)}
424 422 <span class="user">
425 423 <span class="menu_link_user">${c.rhodecode_user.username}</span>
426 424 <div class="show_more"></div>
427 425 </span>
428 426 </a>
429 427 ## subnav with menu for logged in user
430 428 <div class="user-menu submenu">
431 429 <div id="quick_login">
432 430 %if c.rhodecode_user.username != h.DEFAULT_USER:
433 431 <div class="">
434 432 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
435 433 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
436 434 <div class="email">${c.rhodecode_user.email}</div>
437 435 </div>
438 436 <div class="">
439 437 <ol class="links">
440 438 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
441 439 % if c.rhodecode_user.personal_repo_group:
442 440 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
443 441 % endif
444 442 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
445 443 ## bookmark-items
446 444 <li class="bookmark-items">
447 445 ${_('Bookmarks')}
448 446 <div class="pull-right">
449 447 <a href="${h.route_path('my_account_bookmarks')}">${_('Manage')}</a>
450 448 </div>
451 449 </li>
452 450 % if not c.bookmark_items:
453 451 <li>
454 452 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
455 453 </li>
456 454 % endif
457 455 % for item in c.bookmark_items:
458 456 <li>
459 457 % if item.repository:
460 458 <div>
461 459 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
462 460 <code>${item.position}</code>
463 461 % if item.repository.repo_type == 'hg':
464 462 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
465 463 % elif item.repository.repo_type == 'git':
466 464 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
467 465 % elif item.repository.repo_type == 'svn':
468 466 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
469 467 % endif
470 468 ${(item.title or h.shorter(item.repository.repo_name, 30))}
471 469 </a>
472 470 </div>
473 471 % elif item.repository_group:
474 472 <div>
475 473 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
476 474 <code>${item.position}</code>
477 475 <i class="icon-folder-close" title="${_('Repository group')}" style="font-size: 16px"></i>
478 476 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
479 477 </a>
480 478 </div>
481 479 % else:
482 480 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
483 481 <code>${item.position}</code>
484 482 ${item.title}
485 483 </a>
486 484 % endif
487 485 </li>
488 486 % endfor
489 487
490 488 <li class="logout">
491 489 ${h.secure_form(h.route_path('logout'), request=request)}
492 490 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
493 491 ${h.end_form()}
494 492 </li>
495 493 </ol>
496 494 </div>
497 495 %endif
498 496 </div>
499 497 </div>
500 498 ## unread counter
501 499 <div class="pill_container">
502 500 <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
503 501 </div>
504 502 % endif
505 503 </li>
506 504 </%def>
507 505
508 506 <%def name="menu_items(active=None)">
509 507 <%
510 508 def is_active(selected):
511 509 if selected == active:
512 510 return "active"
513 511 return ""
514 512 %>
515 513
516 514 <ul id="quick" class="main_nav navigation horizontal-list">
517 515 ## notice box for important system messages
518 516 <li style="display: none">
519 517 <a class="notice-box" href="#openNotice" onclick="showNoticeBox(); return false">
520 518 <div class="menulabel-notice" >
521 519 0
522 520 </div>
523 521 </a>
524 522 </li>
525 523
526 524 ## Main filter
527 525 <li>
528 526 <div class="menulabel main_filter_box">
529 527 <div class="main_filter_input_box">
530 528 <ul class="searchItems">
531 529
532 530 % if c.template_context['search_context']['repo_id']:
533 531 <li class="searchTag searchTagFilter searchTagHidable" >
534 532 ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">
535 533 <span class="tag">
536 534 This repo
537 535 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-delete"></i></a>
538 536 </span>
539 537 ##</a>
540 538 </li>
541 539 % elif c.template_context['search_context']['repo_group_id']:
542 540 <li class="searchTag searchTagFilter searchTagHidable">
543 541 ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">
544 542 <span class="tag">
545 543 This group
546 544 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-delete"></i></a>
547 545 </span>
548 546 ##</a>
549 547 </li>
550 548 % endif
551 549
552 550 <li class="searchTagInput">
553 551 <input class="main_filter_input" id="main_filter" size="15" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" />
554 552 </li>
555 553 <li class="searchTag searchTagHelp">
556 554 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
557 555 </li>
558 556 </ul>
559 557 </div>
560 558 </div>
561 559
562 560 <div id="main_filter_help" style="display: none">
563 561 - Use '/' key to quickly access this field.
564 562
565 563 - Enter a name of repository, or repository group for quick search.
566 564
567 565 - Prefix query to allow special search:
568 566
569 567 user:admin, to search for usernames, always global
570 568
571 569 user_group:devops, to search for user groups, always global
572 570
573 571 commit:efced4, to search for commits, scoped to repositories or groups
574 572
575 573 file:models.py, to search for file paths, scoped to repositories or groups
576 574
577 575 % if c.template_context['search_context']['repo_id']:
578 576 For advanced full text search visit: <a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">repository search</a>
579 577 % elif c.template_context['search_context']['repo_group_id']:
580 578 For advanced full text search visit: <a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">repository group search</a>
581 579 % else:
582 580 For advanced full text search visit: <a href="${h.route_path('search')}">global search</a>
583 581 % endif
584 582 </div>
585 583 </li>
586 584
587 585 ## ROOT MENU
588 586 <li class="${is_active('home')}">
589 587 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
590 588 <div class="menulabel">${_('Home')}</div>
591 589 </a>
592 590 </li>
593 591
594 592 %if c.rhodecode_user.username != h.DEFAULT_USER:
595 593 <li class="${is_active('journal')}">
596 594 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
597 595 <div class="menulabel">${_('Journal')}</div>
598 596 </a>
599 597 </li>
600 598 %else:
601 599 <li class="${is_active('journal')}">
602 600 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
603 601 <div class="menulabel">${_('Public journal')}</div>
604 602 </a>
605 603 </li>
606 604 %endif
607 605
608 606 <li class="${is_active('gists')}">
609 607 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
610 608 <div class="menulabel">${_('Gists')}</div>
611 609 </a>
612 610 </li>
613 611
612 % if c.is_super_admin or c.is_delegated_admin:
614 613 <li class="${is_active('admin')}">
615 614 <a class="menulink childs" title="${_('Admin settings')}" href="${h.route_path('admin_home')}">
616 615 <div class="menulabel">${_('Admin')} </div>
617 616 </a>
618 617 </li>
618 % endif
619 619
620 620 ## render extra user menu
621 621 ${usermenu(active=(active=='my_account'))}
622 622
623 623 % if c.debug_style:
624 624 <li>
625 625 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
626 626 <div class="menulabel">${_('[Style]')}</div>
627 627 </a>
628 628 </li>
629 629 % endif
630 630 </ul>
631 631
632 632 <script type="text/javascript">
633 633 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
634 634
635 635 var formatRepoResult = function(result, container, query, escapeMarkup) {
636 636 return function(data, escapeMarkup) {
637 637 if (!data.repo_id){
638 638 return data.text; // optgroup text Repositories
639 639 }
640 640
641 641 var tmpl = '';
642 642 var repoType = data['repo_type'];
643 643 var repoName = data['text'];
644 644
645 645 if(data && data.type == 'repo'){
646 646 if(repoType === 'hg'){
647 647 tmpl += '<i class="icon-hg"></i> ';
648 648 }
649 649 else if(repoType === 'git'){
650 650 tmpl += '<i class="icon-git"></i> ';
651 651 }
652 652 else if(repoType === 'svn'){
653 653 tmpl += '<i class="icon-svn"></i> ';
654 654 }
655 655 if(data['private']){
656 656 tmpl += '<i class="icon-lock" ></i> ';
657 657 }
658 658 else if(visualShowPublicIcon){
659 659 tmpl += '<i class="icon-unlock-alt"></i> ';
660 660 }
661 661 }
662 662 tmpl += escapeMarkup(repoName);
663 663 return tmpl;
664 664
665 665 }(result, escapeMarkup);
666 666 };
667 667
668 668 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
669 669 return function(data, escapeMarkup) {
670 670 if (!data.repo_group_id){
671 671 return data.text; // optgroup text Repositories
672 672 }
673 673
674 674 var tmpl = '';
675 675 var repoGroupName = data['text'];
676 676
677 677 if(data){
678 678
679 679 tmpl += '<i class="icon-folder-close"></i> ';
680 680
681 681 }
682 682 tmpl += escapeMarkup(repoGroupName);
683 683 return tmpl;
684 684
685 685 }(result, escapeMarkup);
686 686 };
687 687
688 688 var escapeRegExChars = function (value) {
689 689 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
690 690 };
691 691
692 692 var getRepoIcon = function(repo_type) {
693 693 if (repo_type === 'hg') {
694 694 return '<i class="icon-hg"></i> ';
695 695 }
696 696 else if (repo_type === 'git') {
697 697 return '<i class="icon-git"></i> ';
698 698 }
699 699 else if (repo_type === 'svn') {
700 700 return '<i class="icon-svn"></i> ';
701 701 }
702 702 return ''
703 703 };
704 704
705 705 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
706 706
707 707 if (value.split(':').length === 2) {
708 708 value = value.split(':')[1]
709 709 }
710 710
711 711 var searchType = data['type'];
712 712 var valueDisplay = data['value_display'];
713 713
714 714 var pattern = '(' + escapeRegExChars(value) + ')';
715 715
716 716 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
717 717
718 718 // highlight match
719 719 if (searchType != 'text') {
720 720 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
721 721 }
722 722
723 723 var icon = '';
724 724
725 725 if (searchType === 'hint') {
726 726 icon += '<i class="icon-folder-close"></i> ';
727 727 }
728 728 // full text search
729 729 else if (searchType === 'search') {
730 730 icon += '<i class="icon-more"></i> ';
731 731 }
732 732 // repository
733 733 else if (searchType === 'repo') {
734 734
735 735 var repoIcon = getRepoIcon(data['repo_type']);
736 736 icon += repoIcon;
737 737
738 738 if (data['private']) {
739 739 icon += '<i class="icon-lock" ></i> ';
740 740 }
741 741 else if (visualShowPublicIcon) {
742 742 icon += '<i class="icon-unlock-alt"></i> ';
743 743 }
744 744 }
745 745 // repository groups
746 746 else if (searchType === 'repo_group') {
747 747 icon += '<i class="icon-folder-close"></i> ';
748 748 }
749 749 // user group
750 750 else if (searchType === 'user_group') {
751 751 icon += '<i class="icon-group"></i> ';
752 752 }
753 753 // user
754 754 else if (searchType === 'user') {
755 755 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
756 756 }
757 757 // commit
758 758 else if (searchType === 'commit') {
759 759 var repo_data = data['repo_data'];
760 760 var repoIcon = getRepoIcon(repo_data['repository_type']);
761 761 if (repoIcon) {
762 762 icon += repoIcon;
763 763 } else {
764 764 icon += '<i class="icon-tag"></i>';
765 765 }
766 766 }
767 767 // file
768 768 else if (searchType === 'file') {
769 769 var repo_data = data['repo_data'];
770 770 var repoIcon = getRepoIcon(repo_data['repository_type']);
771 771 if (repoIcon) {
772 772 icon += repoIcon;
773 773 } else {
774 774 icon += '<i class="icon-tag"></i>';
775 775 }
776 776 }
777 777 // generic text
778 778 else if (searchType === 'text') {
779 779 icon = '';
780 780 }
781 781
782 782 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
783 783 return tmpl.format(icon, valueDisplay);
784 784 };
785 785
786 786 var handleSelect = function(element, suggestion) {
787 787 if (suggestion.type === "hint") {
788 788 // we skip action
789 789 $('#main_filter').focus();
790 790 }
791 791 else if (suggestion.type === "text") {
792 792 // we skip action
793 793 $('#main_filter').focus();
794 794
795 795 } else {
796 796 window.location = suggestion['url'];
797 797 }
798 798 };
799 799
800 800 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
801 801 if (queryLowerCase.split(':').length === 2) {
802 802 queryLowerCase = queryLowerCase.split(':')[1]
803 803 }
804 804 if (suggestion.type === "text") {
805 805 // special case we don't want to "skip" display for
806 806 return true
807 807 }
808 808 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
809 809 };
810 810
811 811 var cleanContext = {
812 812 repo_view_type: null,
813 813
814 814 repo_id: null,
815 815 repo_name: "",
816 816
817 817 repo_group_id: null,
818 818 repo_group_name: null
819 819 };
820 820 var removeGoToFilter = function () {
821 821 $('.searchTagHidable').hide();
822 822 $('#main_filter').autocomplete(
823 823 'setOptions', {params:{search_context: cleanContext}});
824 824 };
825 825
826 826 $('#main_filter').autocomplete({
827 827 serviceUrl: pyroutes.url('goto_switcher_data'),
828 828 params: {
829 829 "search_context": templateContext.search_context
830 830 },
831 831 minChars:2,
832 832 maxHeight:400,
833 833 deferRequestBy: 300, //miliseconds
834 834 tabDisabled: true,
835 835 autoSelectFirst: false,
836 836 formatResult: autocompleteMainFilterFormatResult,
837 837 lookupFilter: autocompleteMainFilterResult,
838 838 onSelect: function (element, suggestion) {
839 839 handleSelect(element, suggestion);
840 840 return false;
841 841 },
842 842 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
843 843 if (jqXHR !== 'abort') {
844 844 alert("Error during search.\nError code: {0}".format(textStatus));
845 845 window.location = '';
846 846 }
847 847 }
848 848 });
849 849
850 850 showMainFilterBox = function () {
851 851 $('#main_filter_help').toggle();
852 852 };
853 853
854 854 $('#main_filter').on('keydown.autocomplete', function (e) {
855 855
856 856 var BACKSPACE = 8;
857 857 var el = $(e.currentTarget);
858 858 if(e.which === BACKSPACE){
859 859 var inputVal = el.val();
860 860 if (inputVal === ""){
861 861 removeGoToFilter()
862 862 }
863 863 }
864 864 });
865 865
866 866 </script>
867 867 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
868 868 </%def>
869 869
870 870 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
871 871 <div class="modal-dialog">
872 872 <div class="modal-content">
873 873 <div class="modal-header">
874 874 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
875 875 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
876 876 </div>
877 877 <div class="modal-body">
878 878 <div class="block-left">
879 879 <table class="keyboard-mappings">
880 880 <tbody>
881 881 <tr>
882 882 <th></th>
883 883 <th>${_('Site-wide shortcuts')}</th>
884 884 </tr>
885 885 <%
886 886 elems = [
887 887 ('/', 'Use quick search box'),
888 888 ('g h', 'Goto home page'),
889 889 ('g g', 'Goto my private gists page'),
890 890 ('g G', 'Goto my public gists page'),
891 891 ('g 0-9', 'Goto bookmarked items from 0-9'),
892 892 ('n r', 'New repository page'),
893 893 ('n g', 'New gist page'),
894 894 ]
895 895 %>
896 896 %for key, desc in elems:
897 897 <tr>
898 898 <td class="keys">
899 899 <span class="key tag">${key}</span>
900 900 </td>
901 901 <td>${desc}</td>
902 902 </tr>
903 903 %endfor
904 904 </tbody>
905 905 </table>
906 906 </div>
907 907 <div class="block-left">
908 908 <table class="keyboard-mappings">
909 909 <tbody>
910 910 <tr>
911 911 <th></th>
912 912 <th>${_('Repositories')}</th>
913 913 </tr>
914 914 <%
915 915 elems = [
916 916 ('g s', 'Goto summary page'),
917 917 ('g c', 'Goto changelog page'),
918 918 ('g f', 'Goto files page'),
919 919 ('g F', 'Goto files page with file search activated'),
920 920 ('g p', 'Goto pull requests page'),
921 921 ('g o', 'Goto repository settings'),
922 922 ('g O', 'Goto repository permissions settings'),
923 923 ]
924 924 %>
925 925 %for key, desc in elems:
926 926 <tr>
927 927 <td class="keys">
928 928 <span class="key tag">${key}</span>
929 929 </td>
930 930 <td>${desc}</td>
931 931 </tr>
932 932 %endfor
933 933 </tbody>
934 934 </table>
935 935 </div>
936 936 </div>
937 937 <div class="modal-footer">
938 938 </div>
939 939 </div><!-- /.modal-content -->
940 940 </div><!-- /.modal-dialog -->
941 941 </div><!-- /.modal -->
942 942
@@ -1,407 +1,407 b''
1 1 ## -*- coding: utf-8 -*-
2 2 ## usage:
3 3 ## <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
4 4 ## ${comment.comment_block(comment)}
5 5 ##
6 6 <%namespace name="base" file="/base/base.mako"/>
7 7
8 8 <%def name="comment_block(comment, inline=False)">
9 9 <% pr_index_ver = comment.get_index_version(getattr(c, 'versions', [])) %>
10 10 <% latest_ver = len(getattr(c, 'versions', [])) %>
11 11 % if inline:
12 12 <% outdated_at_ver = comment.outdated_at_version(getattr(c, 'at_version_num', None)) %>
13 13 % else:
14 14 <% outdated_at_ver = comment.older_than_version(getattr(c, 'at_version_num', None)) %>
15 15 % endif
16 16
17 17
18 18 <div class="comment
19 19 ${'comment-inline' if inline else 'comment-general'}
20 20 ${'comment-outdated' if outdated_at_ver else 'comment-current'}"
21 21 id="comment-${comment.comment_id}"
22 22 line="${comment.line_no}"
23 23 data-comment-id="${comment.comment_id}"
24 24 data-comment-type="${comment.comment_type}"
25 25 data-comment-line-no="${comment.line_no}"
26 26 data-comment-inline=${h.json.dumps(inline)}
27 27 style="${'display: none;' if outdated_at_ver else ''}">
28 28
29 29 <div class="meta">
30 30 <div class="comment-type-label">
31 31 <div class="comment-label ${comment.comment_type or 'note'}" id="comment-label-${comment.comment_id}" title="line: ${comment.line_no}">
32 32 % if comment.comment_type == 'todo':
33 33 % if comment.resolved:
34 34 <div class="resolved tooltip" title="${_('Resolved by comment #{}').format(comment.resolved.comment_id)}">
35 35 <a href="#comment-${comment.resolved.comment_id}">${comment.comment_type}</a>
36 36 </div>
37 37 % else:
38 38 <div class="resolved tooltip" style="display: none">
39 39 <span>${comment.comment_type}</span>
40 40 </div>
41 41 <div class="resolve tooltip" onclick="return Rhodecode.comments.createResolutionComment(${comment.comment_id});" title="${_('Click to resolve this comment')}">
42 42 ${comment.comment_type}
43 43 </div>
44 44 % endif
45 45 % else:
46 46 % if comment.resolved_comment:
47 47 fix
48 48 % else:
49 49 ${comment.comment_type or 'note'}
50 50 % endif
51 51 % endif
52 52 </div>
53 53 </div>
54 54
55 55 <div class="author ${'author-inline' if inline else 'author-general'}">
56 56 ${base.gravatar_with_user(comment.author.email, 16)}
57 57 </div>
58 58 <div class="date">
59 59 ${h.age_component(comment.modified_at, time_is_local=True)}
60 60 </div>
61 61 % if inline:
62 62 <span></span>
63 63 % else:
64 64 <div class="status-change">
65 65 % if comment.pull_request:
66 66 <a href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id)}">
67 67 % if comment.status_change:
68 68 ${_('pull request #%s') % comment.pull_request.pull_request_id}:
69 69 % else:
70 70 ${_('pull request #%s') % comment.pull_request.pull_request_id}
71 71 % endif
72 72 </a>
73 73 % else:
74 74 % if comment.status_change:
75 75 ${_('Status change on commit')}:
76 76 % endif
77 77 % endif
78 78 </div>
79 79 % endif
80 80
81 81 % if comment.status_change:
82 82 <div class="${'flag_status %s' % comment.status_change[0].status}"></div>
83 83 <div title="${_('Commit status')}" class="changeset-status-lbl">
84 84 ${comment.status_change[0].status_lbl}
85 85 </div>
86 86 % endif
87 87
88 88 % if comment.resolved_comment:
89 89 <a class="has-spacer-before" href="#comment-${comment.resolved_comment.comment_id}" onclick="Rhodecode.comments.scrollToComment($('#comment-${comment.resolved_comment.comment_id}'), 0, ${h.json.dumps(comment.resolved_comment.outdated)})">
90 90 ${_('resolves comment #{}').format(comment.resolved_comment.comment_id)}
91 91 </a>
92 92 % endif
93 93
94 94 <a class="permalink" href="#comment-${comment.comment_id}"> &para;</a>
95 95
96 96 <div class="comment-links-block">
97 97 % if comment.pull_request and comment.pull_request.author.user_id == comment.author.user_id:
98 98 <span class="tag authortag tooltip" title="${_('Pull request author')}">
99 99 ${_('author')}
100 100 </span>
101 101 |
102 102 % endif
103 103 % if inline:
104 104 <div class="pr-version-inline">
105 105 <a href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}">
106 106 % if outdated_at_ver:
107 107 <code class="pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
108 108 outdated ${'v{}'.format(pr_index_ver)} |
109 109 </code>
110 110 % elif pr_index_ver:
111 111 <code class="pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
112 112 ${'v{}'.format(pr_index_ver)} |
113 113 </code>
114 114 % endif
115 115 </a>
116 116 </div>
117 117 % else:
118 118 % if comment.pull_request_version_id and pr_index_ver:
119 119 |
120 120 <div class="pr-version">
121 121 % if comment.outdated:
122 122 <a href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}">
123 123 ${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}
124 124 </a>
125 125 % else:
126 126 <div title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
127 127 <a href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id, version=comment.pull_request_version_id)}">
128 128 <code class="pr-version-num">
129 129 ${'v{}'.format(pr_index_ver)}
130 130 </code>
131 131 </a>
132 132 </div>
133 133 % endif
134 134 </div>
135 135 % endif
136 136 % endif
137 137
138 138 ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
139 139 ## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated
140 140 %if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())):
141 141 ## permissions to delete
142 %if h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id:
142 %if c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id:
143 143 ## TODO: dan: add edit comment here
144 144 <a onclick="return Rhodecode.comments.deleteComment(this);" class="delete-comment"> ${_('Delete')}</a>
145 145 %else:
146 146 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
147 147 %endif
148 148 %else:
149 149 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
150 150 %endif
151 151
152 152 % if outdated_at_ver:
153 153 | <a onclick="return Rhodecode.comments.prevOutdatedComment(this);" class="prev-comment"> ${_('Prev')}</a>
154 154 | <a onclick="return Rhodecode.comments.nextOutdatedComment(this);" class="next-comment"> ${_('Next')}</a>
155 155 % else:
156 156 | <a onclick="return Rhodecode.comments.prevComment(this);" class="prev-comment"> ${_('Prev')}</a>
157 157 | <a onclick="return Rhodecode.comments.nextComment(this);" class="next-comment"> ${_('Next')}</a>
158 158 % endif
159 159
160 160 </div>
161 161 </div>
162 162 <div class="text">
163 163 ${h.render(comment.text, renderer=comment.renderer, mentions=True)}
164 164 </div>
165 165
166 166 </div>
167 167 </%def>
168 168
169 169 ## generate main comments
170 170 <%def name="generate_comments(comments, include_pull_request=False, is_pull_request=False)">
171 171 <div class="general-comments" id="comments">
172 172 %for comment in comments:
173 173 <div id="comment-tr-${comment.comment_id}">
174 174 ## only render comments that are not from pull request, or from
175 175 ## pull request and a status change
176 176 %if not comment.pull_request or (comment.pull_request and comment.status_change) or include_pull_request:
177 177 ${comment_block(comment)}
178 178 %endif
179 179 </div>
180 180 %endfor
181 181 ## to anchor ajax comments
182 182 <div id="injected_page_comments"></div>
183 183 </div>
184 184 </%def>
185 185
186 186
187 187 <%def name="comments(post_url, cur_status, is_pull_request=False, is_compare=False, change_status=True, form_extras=None)">
188 188
189 189 <div class="comments">
190 190 <%
191 191 if is_pull_request:
192 192 placeholder = _('Leave a comment on this Pull Request.')
193 193 elif is_compare:
194 194 placeholder = _('Leave a comment on {} commits in this range.').format(len(form_extras))
195 195 else:
196 196 placeholder = _('Leave a comment on this Commit.')
197 197 %>
198 198
199 199 % if c.rhodecode_user.username != h.DEFAULT_USER:
200 200 <div class="js-template" id="cb-comment-general-form-template">
201 201 ## template generated for injection
202 202 ${comment_form(form_type='general', review_statuses=c.commit_statuses, form_extras=form_extras)}
203 203 </div>
204 204
205 205 <div id="cb-comment-general-form-placeholder" class="comment-form ac">
206 206 ## inject form here
207 207 </div>
208 208 <script type="text/javascript">
209 209 var lineNo = 'general';
210 210 var resolvesCommentId = null;
211 211 var generalCommentForm = Rhodecode.comments.createGeneralComment(
212 212 lineNo, "${placeholder}", resolvesCommentId);
213 213
214 214 // set custom success callback on rangeCommit
215 215 % if is_compare:
216 216 generalCommentForm.setHandleFormSubmit(function(o) {
217 217 var self = generalCommentForm;
218 218
219 219 var text = self.cm.getValue();
220 220 var status = self.getCommentStatus();
221 221 var commentType = self.getCommentType();
222 222
223 223 if (text === "" && !status) {
224 224 return;
225 225 }
226 226
227 227 // we can pick which commits we want to make the comment by
228 228 // selecting them via click on preview pane, this will alter the hidden inputs
229 229 var cherryPicked = $('#changeset_compare_view_content .compare_select.hl').length > 0;
230 230
231 231 var commitIds = [];
232 232 $('#changeset_compare_view_content .compare_select').each(function(el) {
233 233 var commitId = this.id.replace('row-', '');
234 234 if ($(this).hasClass('hl') || !cherryPicked) {
235 235 $("input[data-commit-id='{0}']".format(commitId)).val(commitId);
236 236 commitIds.push(commitId);
237 237 } else {
238 238 $("input[data-commit-id='{0}']".format(commitId)).val('')
239 239 }
240 240 });
241 241
242 242 self.setActionButtonsDisabled(true);
243 243 self.cm.setOption("readOnly", true);
244 244 var postData = {
245 245 'text': text,
246 246 'changeset_status': status,
247 247 'comment_type': commentType,
248 248 'commit_ids': commitIds,
249 249 'csrf_token': CSRF_TOKEN
250 250 };
251 251
252 252 var submitSuccessCallback = function(o) {
253 253 location.reload(true);
254 254 };
255 255 var submitFailCallback = function(){
256 256 self.resetCommentFormState(text)
257 257 };
258 258 self.submitAjaxPOST(
259 259 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
260 260 });
261 261 % endif
262 262
263 263
264 264 </script>
265 265 % else:
266 266 ## form state when not logged in
267 267 <div class="comment-form ac">
268 268
269 269 <div class="comment-area">
270 270 <div class="comment-area-header">
271 271 <ul class="nav-links clearfix">
272 272 <li class="active">
273 273 <a class="disabled" href="#edit-btn" disabled="disabled" onclick="return false">${_('Write')}</a>
274 274 </li>
275 275 <li class="">
276 276 <a class="disabled" href="#preview-btn" disabled="disabled" onclick="return false">${_('Preview')}</a>
277 277 </li>
278 278 </ul>
279 279 </div>
280 280
281 281 <div class="comment-area-write" style="display: block;">
282 282 <div id="edit-container">
283 283 <div style="padding: 40px 0">
284 284 ${_('You need to be logged in to leave comments.')}
285 285 <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a>
286 286 </div>
287 287 </div>
288 288 <div id="preview-container" class="clearfix" style="display: none;">
289 289 <div id="preview-box" class="preview-box"></div>
290 290 </div>
291 291 </div>
292 292
293 293 <div class="comment-area-footer">
294 294 <div class="toolbar">
295 295 <div class="toolbar-text">
296 296 </div>
297 297 </div>
298 298 </div>
299 299 </div>
300 300
301 301 <div class="comment-footer">
302 302 </div>
303 303
304 304 </div>
305 305 % endif
306 306
307 307 <script type="text/javascript">
308 308 bindToggleButtons();
309 309 </script>
310 310 </div>
311 311 </%def>
312 312
313 313
314 314 <%def name="comment_form(form_type, form_id='', lineno_id='{1}', review_statuses=None, form_extras=None)">
315 315 ## comment injected based on assumption that user is logged in
316 316
317 317 <form ${'id="{}"'.format(form_id) if form_id else '' |n} action="#" method="GET">
318 318
319 319 <div class="comment-area">
320 320 <div class="comment-area-header">
321 321 <ul class="nav-links clearfix">
322 322 <li class="active">
323 323 <a href="#edit-btn" tabindex="-1" id="edit-btn_${lineno_id}">${_('Write')}</a>
324 324 </li>
325 325 <li class="">
326 326 <a href="#preview-btn" tabindex="-1" id="preview-btn_${lineno_id}">${_('Preview')}</a>
327 327 </li>
328 328 <li class="pull-right">
329 329 <select class="comment-type" id="comment_type_${lineno_id}" name="comment_type">
330 330 % for val in c.visual.comment_types:
331 331 <option value="${val}">${val.upper()}</option>
332 332 % endfor
333 333 </select>
334 334 </li>
335 335 </ul>
336 336 </div>
337 337
338 338 <div class="comment-area-write" style="display: block;">
339 339 <div id="edit-container_${lineno_id}">
340 340 <textarea id="text_${lineno_id}" name="text" class="comment-block-ta ac-input"></textarea>
341 341 </div>
342 342 <div id="preview-container_${lineno_id}" class="clearfix" style="display: none;">
343 343 <div id="preview-box_${lineno_id}" class="preview-box"></div>
344 344 </div>
345 345 </div>
346 346
347 347 <div class="comment-area-footer">
348 348 <div class="toolbar">
349 349 <div class="toolbar-text">
350 350 ${(_('Comments parsed using %s syntax with %s, and %s actions support.') % (
351 351 ('<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
352 352 ('<span class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user')),
353 353 ('<span class="tooltip" title="%s">`/`</span>' % _('Start typing with / for certain actions to be triggered via text box.'))
354 354 )
355 355 )|n}
356 356 </div>
357 357 </div>
358 358 </div>
359 359 </div>
360 360
361 361 <div class="comment-footer">
362 362
363 363 % if review_statuses:
364 364 <div class="status_box">
365 365 <select id="change_status_${lineno_id}" name="changeset_status">
366 366 <option></option> ## Placeholder
367 367 % for status, lbl in review_statuses:
368 368 <option value="${status}" data-status="${status}">${lbl}</option>
369 369 %if is_pull_request and change_status and status in ('approved', 'rejected'):
370 370 <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option>
371 371 %endif
372 372 % endfor
373 373 </select>
374 374 </div>
375 375 % endif
376 376
377 377 ## inject extra inputs into the form
378 378 % if form_extras and isinstance(form_extras, (list, tuple)):
379 379 <div id="comment_form_extras">
380 380 % for form_ex_el in form_extras:
381 381 ${form_ex_el|n}
382 382 % endfor
383 383 </div>
384 384 % endif
385 385
386 386 <div class="action-buttons">
387 387 ## inline for has a file, and line-number together with cancel hide button.
388 388 % if form_type == 'inline':
389 389 <input type="hidden" name="f_path" value="{0}">
390 390 <input type="hidden" name="line" value="${lineno_id}">
391 391 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
392 392 ${_('Cancel')}
393 393 </button>
394 394 % endif
395 395
396 396 % if form_type != 'inline':
397 397 <div class="action-buttons-extra"></div>
398 398 % endif
399 399
400 400 ${h.submit('save', _('Comment'), class_='btn btn-success comment-button-input')}
401 401
402 402 </div>
403 403 </div>
404 404
405 405 </form>
406 406
407 407 </%def> No newline at end of file
@@ -1,131 +1,124 b''
1 1 <%inherit file="/base/base.mako"/>
2 2
3 3
4 4 <%def name="menu_bar_subnav()">
5 5 % if c.repo_group:
6 6 ${self.repo_group_menu(active='home')}
7 7 % endif
8 8 </%def>
9 9
10 10
11 11 <%def name="main()">
12 12 <div class="box">
13 13 <!-- box / title -->
14 14 <div class="title">
15 15 %if c.rhodecode_user.username != h.DEFAULT_USER:
16 16 <div class="block-right">
17 <%
18 is_admin = h.HasPermissionAny('hg.admin')('can create repos index page')
19 create_repo = h.HasPermissionAny('hg.create.repository')('can create repository index page')
20 create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')('can create repository groups index page')
21 create_user_group = h.HasPermissionAny('hg.usergroup.create.true')('can create user groups index page')
22 %>
23
24 17 %if not c.repo_group:
25 18 ## no repository group context here
26 %if is_admin or create_repo:
19 %if c.is_super_admin or c.can_create_repo:
27 20 <a href="${h.route_path('repo_new')}" class="btn btn-small btn-success btn-primary">${_('Add Repository')}</a>
28 21 %endif
29 22
30 %if is_admin or create_repo_group:
23 %if c.is_super_admin or c.can_create_repo_group:
31 24 <a href="${h.route_path('repo_group_new')}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
32 25 %endif
33 26 %endif
34 27 </div>
35 28 %endif
36 29 </div>
37 30 <!-- end box / title -->
38 31 <div class="table">
39 32 <div id="groups_list_wrap">
40 33 <table id="group_list_table" class="display" style="width: 100%"></table>
41 34 </div>
42 35 </div>
43 36
44 37 <div class="table">
45 38 <div id="repos_list_wrap">
46 39 <table id="repo_list_table" class="display" style="width: 100%"></table>
47 40 </div>
48 41 </div>
49 42
50 43 ## no repository groups and repos present, show something to the users
51 44 % if c.repo_groups_data == '[]' and c.repos_data == '[]':
52 45 <div class="table">
53 46 <h2 class="no-object-border">
54 47 ${_('No repositories or repositories groups exists here.')}
55 48 </h2>
56 49 </div>
57 50 % endif
58 51
59 52 </div>
60 53 <script>
61 54 $(document).ready(function() {
62 55
63 56 // repo group list
64 57 % if c.repo_groups_data != '[]':
65 58 $('#group_list_table').DataTable({
66 59 data: ${c.repo_groups_data|n},
67 60 dom: 'rtp',
68 61 pageLength: ${c.visual.dashboard_items},
69 62 order: [[ 0, "asc" ]],
70 63 columns: [
71 64 { data: {"_": "name",
72 65 "sort": "name_raw"}, title: "${_('Name')}", className: "truncate-wrap td-grid-name" },
73 66 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
74 67 { data: {"_": "desc",
75 68 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
76 69 { data: {"_": "last_change",
77 70 "sort": "last_change_raw",
78 71 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
79 72 { data: {"_": "last_changeset",
80 73 "sort": "last_changeset_raw",
81 74 "type": Number}, title: "", className: "td-hash" },
82 75 { data: {"_": "owner",
83 76 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" }
84 77 ],
85 78 language: {
86 79 paginate: DEFAULT_GRID_PAGINATION,
87 80 emptyTable: _gettext("No repository groups available yet.")
88 81 },
89 82 "drawCallback": function( settings, json ) {
90 83 timeagoActivate();
91 84 quick_repo_menu();
92 85 }
93 86 });
94 87 % endif
95 88
96 89 // repo list
97 90 % if c.repos_data != '[]':
98 91 $('#repo_list_table').DataTable({
99 92 data: ${c.repos_data|n},
100 93 dom: 'rtp',
101 94 order: [[ 0, "asc" ]],
102 95 pageLength: ${c.visual.dashboard_items},
103 96 columns: [
104 97 { data: {"_": "name",
105 98 "sort": "name_raw"}, title: "${_('Name')}", className: "truncate-wrap td-grid-name" },
106 99 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
107 100 { data: {"_": "desc",
108 101 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
109 102 { data: {"_": "last_change",
110 103 "sort": "last_change_raw",
111 104 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
112 105 { data: {"_": "last_changeset",
113 106 "sort": "last_changeset_raw",
114 107 "type": Number}, title: "${_('Commit')}", className: "td-hash" },
115 108 { data: {"_": "owner",
116 109 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" }
117 110 ],
118 111 language: {
119 112 paginate: DEFAULT_GRID_PAGINATION,
120 113 emptyTable: _gettext("No repositories available yet.")
121 114 },
122 115 "drawCallback": function( settings, json ) {
123 116 timeagoActivate();
124 117 quick_repo_menu();
125 118 }
126 119 });
127 120 % endif
128 121
129 122 });
130 123 </script>
131 124 </%def>
@@ -1,231 +1,231 b''
1 1 <%def name="refs_counters(branches, closed_branches, tags, bookmarks)">
2 2 <span class="branchtag tag">
3 3 <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">
4 4 <i class="icon-branch"></i>${_ungettext(
5 5 '%(num)s Branch','%(num)s Branches', len(branches)) % {'num': len(branches)}}</a>
6 6 </span>
7 7
8 8 %if closed_branches:
9 9 <span class="branchtag tag">
10 10 <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">
11 11 <i class="icon-branch"></i>${_ungettext(
12 12 '%(num)s Closed Branch', '%(num)s Closed Branches', len(closed_branches)) % {'num': len(closed_branches)}}</a>
13 13 </span>
14 14 %endif
15 15
16 16 <span class="tagtag tag">
17 17 <a href="${h.route_path('tags_home',repo_name=c.repo_name)}" class="childs">
18 18 <i class="icon-tag"></i>${_ungettext(
19 19 '%(num)s Tag', '%(num)s Tags', len(tags)) % {'num': len(tags)}}</a>
20 20 </span>
21 21
22 22 %if bookmarks:
23 23 <span class="booktag tag">
24 24 <a href="${h.route_path('bookmarks_home',repo_name=c.repo_name)}" class="childs">
25 25 <i class="icon-bookmark"></i>${_ungettext(
26 26 '%(num)s Bookmark', '%(num)s Bookmarks', len(bookmarks)) % {'num': len(bookmarks)}}</a>
27 27 </span>
28 28 %endif
29 29 </%def>
30 30
31 31 <%def name="summary_detail(breadcrumbs_links, show_downloads=True)">
32 32 <% summary = lambda n:{False:'summary-short'}.get(n) %>
33 33
34 34 <div id="summary-menu-stats" class="summary-detail">
35 35 <div class="summary-detail-header">
36 36 <div class="breadcrumbs files_location">
37 37 <h4>
38 38 ${breadcrumbs_links}
39 39 </h4>
40 40 </div>
41 41 <div id="summary_details_expand" class="btn-collapse" data-toggle="summary-details">
42 42 ${_('Show More')}
43 43 </div>
44 44 </div>
45 45
46 46 <div class="fieldset">
47 47
48 48 <div class="left-clone">
49 49 <select id="clone_option" name="clone_option">
50 50 <option value="http" selected="selected">HTTP</option>
51 51 <option value="http_id">HTTP UID</option>
52 52 % if c.ssh_enabled:
53 53 <option value="ssh">SSH</option>
54 54 % endif
55 55 </select>
56 56 </div>
57 57 <div class="right-clone">
58 58 <%
59 59 maybe_disabled = ''
60 60 if h.is_svn_without_proxy(c.rhodecode_db_repo):
61 61 maybe_disabled = 'disabled'
62 62 %>
63 63
64 64 <span id="clone_option_http">
65 65 <input type="text" class="input-monospace clone_url_input" ${maybe_disabled} readonly="readonly" value="${c.clone_repo_url}"/>
66 66 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url}" title="${_('Copy the clone url')}"></i>
67 67 </span>
68 68
69 69 <span style="display: none;" id="clone_option_http_id">
70 70 <input type="text" class="input-monospace clone_url_input" ${maybe_disabled} readonly="readonly" value="${c.clone_repo_url_id}"/>
71 71 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url_id}" title="${_('Copy the clone by id url')}"></i>
72 72 </span>
73 73
74 74 <span style="display: none;" id="clone_option_ssh">
75 75 <input type="text" class="input-monospace clone_url_input" ${maybe_disabled} readonly="readonly" value="${c.clone_repo_url_ssh}"/>
76 76 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url_ssh}" title="${_('Copy the clone by ssh url')}"></i>
77 77 </span>
78 78
79 79 % if maybe_disabled:
80 80 <p class="help-block">${_('SVN Protocol is disabled. To enable it, see the')} <a href="${h.route_url('enterprise_svn_setup')}" target="_blank">${_('documentation here')}</a>.</p>
81 81 % endif
82 82
83 83 </div>
84 84 </div>
85 85
86 86 <div class="fieldset">
87 87 <div class="left-label-summary">
88 88 &nbsp;
89 89 </div>
90 90 <div class="right-content">
91 91 <div class="commit-info">
92 92 <div class="tags">
93 93 <% commit_rev = c.rhodecode_db_repo.changeset_cache.get('revision') %>
94 94 % if c.rhodecode_repo:
95 95 ${refs_counters(
96 96 c.rhodecode_repo.branches,
97 97 c.rhodecode_repo.branches_closed,
98 98 c.rhodecode_repo.tags,
99 99 c.rhodecode_repo.bookmarks)}
100 100 % else:
101 101 ## missing requirements can make c.rhodecode_repo None
102 102 ${refs_counters([], [], [], [])}
103 103 % endif
104 104
105 105 ## commits
106 106 <span class="tag">
107 107 % if commit_rev == -1:
108 108 ${_ungettext('%(num)s Commit', '%(num)s Commits', 0) % {'num': 0}}
109 109 % else:
110 110 <a href="${h.route_path('repo_changelog', repo_name=c.repo_name)}">
111 111 ${_ungettext('%(num)s Commit', '%(num)s Commits', commit_rev) % {'num': commit_rev}}</a>
112 112 % endif
113 113 </span>
114 114
115 115 ## forks
116 116 <span class="tag">
117 117 <a title="${_('Number of Repository Forks')}" href="${h.route_path('repo_forks_show_all', repo_name=c.repo_name)}">
118 118 ${c.repository_forks} ${_ungettext('Fork', 'Forks', c.repository_forks)}</a>
119 119 </span>
120 120
121 121 </div>
122 122 </div>
123 123 </div>
124 124 </div>
125 125
126 126 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
127 127 <div class="left-label-summary">
128 128 ${_('Repository size')}:
129 129 </div>
130 130 <div class="right-content">
131 131 <div class="commit-info">
132 132 <div class="tags">
133 133 ## repo size
134 134 % if commit_rev == -1:
135 135 <span class="stats-bullet">0 B</span>
136 136 % else:
137 137 <span>
138 138 <a href="#showSize" onclick="calculateSize(); $(this).hide(); return false" id="show-repo-size">Show repository size</a>
139 139 </span>
140 140 <span class="stats-bullet" id="repo_size_container" style="display:none">
141 141 ${_('Calculating Repository Size...')}
142 142 </span>
143 143 % endif
144 144 </div>
145 145 </div>
146 146 </div>
147 147 </div>
148 148
149 149 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
150 150 <div class="left-label-summary">
151 151 ${_('Description')}:
152 152 </div>
153 153 <div class="right-content">
154 154 <div class="input ${summary(c.show_stats)}">
155 155 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
156 156 ${dt.repo_desc(c.rhodecode_db_repo.description_safe, c.visual.stylify_metatags)}
157 157 </div>
158 158 </div>
159 159 </div>
160 160
161 161 % if show_downloads:
162 162 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
163 163 <div class="left-label-summary">
164 164 ${_('Downloads')}:
165 165 </div>
166 166 <div class="right-content">
167 167 <div class="input ${summary(c.show_stats)} downloads">
168 168 % if c.rhodecode_repo and len(c.rhodecode_repo.commit_ids) == 0:
169 169 <span class="disabled">
170 170 ${_('There are no downloads yet')}
171 171 </span>
172 172 % elif not c.enable_downloads:
173 173 <span class="disabled">
174 174 ${_('Downloads are disabled for this repository')}.
175 175 </span>
176 % if h.HasPermissionAll('hg.admin')('enable downloads on from summary'):
176 % if c.is_super_admin:
177 177 ${h.link_to(_('Enable downloads'),h.route_path('edit_repo',repo_name=c.repo_name, _anchor='repo_enable_downloads'))}
178 178 % endif
179 179 % else:
180 180 <span class="enabled">
181 181 <a id="archive_link" class="btn btn-small" href="${h.route_path('repo_archivefile',repo_name=c.rhodecode_db_repo.repo_name,fname='tip.zip')}">
182 182 <i class="icon-archive"></i> tip.zip
183 183 ## replaced by some JS on select
184 184 </a>
185 185 </span>
186 186 ${h.hidden('download_options')}
187 187 % endif
188 188 </div>
189 189 </div>
190 190 </div>
191 191 % endif
192 192
193 193 ## Statistics
194 194 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
195 195 <div class="left-label-summary">
196 196 ${_('Statistics')}:
197 197 </div>
198 198 <div class="right-content">
199 199 <div class="input ${summary(c.show_stats)} statistics">
200 200 % if c.show_stats:
201 201 <div id="lang_stats" class="enabled">
202 202 ${_('Calculating Code Statistics...')}
203 203 </div>
204 204 % else:
205 205 <span class="disabled">
206 206 ${_('Statistics are disabled for this repository')}.
207 207 </span>
208 % if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
208 % if c.is_super_admin:
209 209 ${h.link_to(_('Enable statistics'),h.route_path('edit_repo',repo_name=c.repo_name, _anchor='repo_enable_statistics'))}
210 210 % endif
211 211 % endif
212 212 </div>
213 213
214 214 </div>
215 215 </div>
216 216
217 217 </div><!--end summary-detail-->
218 218 </%def>
219 219
220 220 <%def name="summary_stats(gravatar_function)">
221 221 <div class="sidebar-right">
222 222 <div class="summary-detail-header">
223 223 <h4 class="item">
224 224 ${_('Owner')}
225 225 </h4>
226 226 </div>
227 227 <div class="sidebar-right-content">
228 228 ${gravatar_function(c.rhodecode_db_repo.user.email, 16)}
229 229 </div>
230 230 </div><!--end sidebar-right-->
231 231 </%def>
@@ -1,70 +1,70 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <div class="panel panel-default user-profile">
4 4 <div class="panel-heading">
5 5 <h3 class="panel-title">${_('User group profile')}</h3>
6 %if h.HasPermissionAny('hg.admin')():
6 %if c.is_super_admin:
7 7 ${h.link_to(_('Edit'), h.route_path('edit_user_group', user_group_id=c.user_group.users_group_id), class_='panel-edit')}
8 8 %endif
9 9 </div>
10 10
11 11 <div class="panel-body user-profile-content">
12 12
13 13 <div class="fieldset">
14 14 <div class="left-label">
15 15 ${_('Group Name')}:
16 16 </div>
17 17 <div class="right-content">
18 18 ${c.user_group.users_group_name}
19 19 </div>
20 20 </div>
21 21 <div class="fieldset">
22 22 <div class="left-label">
23 23 ${_('Owner')}:
24 24 </div>
25 25 <div class="group_member">
26 26 ${base.gravatar(c.user_group.user.email, 16)}
27 27 <span class="username user">${h.link_to_user(c.user_group.user)}</span>
28 28
29 29 </div>
30 30 </div>
31 31 <div class="fieldset">
32 32 <div class="left-label">
33 33 ${_('Active')}:
34 34 </div>
35 35 <div class="right-content">
36 36 ${c.user_group.users_group_active}
37 37 </div>
38 38 </div>
39 39 % if not c.anonymous:
40 40 <div class="fieldset">
41 41 <div class="left-label">
42 42 ${_('Members')}:
43 43 </div>
44 44 <div class="right-content">
45 45 <table id="group_members_placeholder" class="rctable group_members">
46 46 <th>${_('Username')}</th>
47 47 % if c.group_members:
48 48 % for user in c.group_members:
49 49 <tr>
50 50 <td id="member_user_${user.user_id}" class="td-author">
51 51 <div class="group_member">
52 52 ${base.gravatar(user.email, 16)}
53 53 <span class="username user">${h.link_to(h.person(user), h.route_path('user_edit',user_id=user.user_id))}</span>
54 54 <input type="hidden" name="__start__" value="member:mapping">
55 55 <input type="hidden" name="member_user_id" value="${user.user_id}">
56 56 <input type="hidden" name="type" value="existing" id="member_${user.user_id}">
57 57 <input type="hidden" name="__end__" value="member:mapping">
58 58 </div>
59 59 </td>
60 60 </tr>
61 61 % endfor
62 62 % else:
63 63 <tr><td colspan="2">${_('No members yet')}</td></tr>
64 64 % endif
65 65 </table>
66 66 </div>
67 67 </div>
68 68 % endif
69 69 </div>
70 70 </div> No newline at end of file
@@ -1,58 +1,58 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <div class="panel panel-default user-profile">
4 4 <div class="panel-heading">
5 5 <h3 class="panel-title">${_('User Profile')}</h3>
6 %if h.HasPermissionAny('hg.admin')():
6 %if c.is_super_admin:
7 7 ${h.link_to(_('Edit'), h.route_path('user_edit', user_id=c.user.user_id), class_='panel-edit')}
8 8 %endif
9 9 </div>
10 10
11 11 <div class="panel-body user-profile-content">
12 12 <div class="fieldset">
13 13 <div class="left-label">
14 14 ${_('Photo')}:
15 15 </div>
16 16 <div class="right-content">
17 17 %if c.visual.use_gravatar:
18 18 ${base.gravatar(c.user.email, 100)}
19 19 %else:
20 20 ${base.gravatar(c.user.email, 20)}
21 21 ${_('Avatars are disabled')}
22 22 %endif
23 23 </div>
24 24 </div>
25 25 <div class="fieldset">
26 26 <div class="left-label">
27 27 ${_('Username')}:
28 28 </div>
29 29 <div class="right-content">
30 30 ${c.user.username}
31 31 </div>
32 32 </div>
33 33 <div class="fieldset">
34 34 <div class="left-label">
35 35 ${_('First name')}:
36 36 </div>
37 37 <div class="right-content">
38 38 ${c.user.first_name}
39 39 </div>
40 40 </div>
41 41 <div class="fieldset">
42 42 <div class="left-label">
43 43 ${_('Last name')}:
44 44 </div>
45 45 <div class="right-content">
46 46 ${c.user.last_name}
47 47 </div>
48 48 </div>
49 49 <div class="fieldset">
50 50 <div class="left-label">
51 51 ${_('Email')}:
52 52 </div>
53 53 <div class="right-content">
54 54 ${c.user.email or _('Missing email, please update your user email address.')}
55 55 </div>
56 56 </div>
57 57 </div>
58 58 </div> No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now