##// 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 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import time
21 import time
22 import logging
22 import logging
23 import operator
23 import operator
24
24
25 from pyramid import compat
25 from pyramid import compat
26 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
26 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
27
27
28 from rhodecode.lib import helpers as h, diffs
28 from rhodecode.lib import helpers as h, diffs
29 from rhodecode.lib.utils2 import (
29 from rhodecode.lib.utils2 import (
30 StrictAttributeDict, str2bool, safe_int, datetime_to_time, safe_unicode)
30 StrictAttributeDict, str2bool, safe_int, datetime_to_time, safe_unicode)
31 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
31 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
32 from rhodecode.model import repo
32 from rhodecode.model import repo
33 from rhodecode.model import repo_group
33 from rhodecode.model import repo_group
34 from rhodecode.model import user_group
34 from rhodecode.model import user_group
35 from rhodecode.model import user
35 from rhodecode.model import user
36 from rhodecode.model.db import User
36 from rhodecode.model.db import User
37 from rhodecode.model.scm import ScmModel
37 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.settings import VcsSettingsModel
38 from rhodecode.model.settings import VcsSettingsModel
39
39
40 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
41
41
42
42
43 ADMIN_PREFIX = '/_admin'
43 ADMIN_PREFIX = '/_admin'
44 STATIC_FILE_PREFIX = '/_static'
44 STATIC_FILE_PREFIX = '/_static'
45
45
46 URL_NAME_REQUIREMENTS = {
46 URL_NAME_REQUIREMENTS = {
47 # group name can have a slash in them, but they must not end with a slash
47 # group name can have a slash in them, but they must not end with a slash
48 'group_name': r'.*?[^/]',
48 'group_name': r'.*?[^/]',
49 'repo_group_name': r'.*?[^/]',
49 'repo_group_name': r'.*?[^/]',
50 # repo names can have a slash in them, but they must not end with a slash
50 # repo names can have a slash in them, but they must not end with a slash
51 'repo_name': r'.*?[^/]',
51 'repo_name': r'.*?[^/]',
52 # file path eats up everything at the end
52 # file path eats up everything at the end
53 'f_path': r'.*',
53 'f_path': r'.*',
54 # reference types
54 # reference types
55 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
55 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
56 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
56 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
57 }
57 }
58
58
59
59
60 def add_route_with_slash(config,name, pattern, **kw):
60 def add_route_with_slash(config,name, pattern, **kw):
61 config.add_route(name, pattern, **kw)
61 config.add_route(name, pattern, **kw)
62 if not pattern.endswith('/'):
62 if not pattern.endswith('/'):
63 config.add_route(name + '_slash', pattern + '/', **kw)
63 config.add_route(name + '_slash', pattern + '/', **kw)
64
64
65
65
66 def add_route_requirements(route_path, requirements=None):
66 def add_route_requirements(route_path, requirements=None):
67 """
67 """
68 Adds regex requirements to pyramid routes using a mapping dict
68 Adds regex requirements to pyramid routes using a mapping dict
69 e.g::
69 e.g::
70 add_route_requirements('{repo_name}/settings')
70 add_route_requirements('{repo_name}/settings')
71 """
71 """
72 requirements = requirements or URL_NAME_REQUIREMENTS
72 requirements = requirements or URL_NAME_REQUIREMENTS
73 for key, regex in requirements.items():
73 for key, regex in requirements.items():
74 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
74 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
75 return route_path
75 return route_path
76
76
77
77
78 def get_format_ref_id(repo):
78 def get_format_ref_id(repo):
79 """Returns a `repo` specific reference formatter function"""
79 """Returns a `repo` specific reference formatter function"""
80 if h.is_svn(repo):
80 if h.is_svn(repo):
81 return _format_ref_id_svn
81 return _format_ref_id_svn
82 else:
82 else:
83 return _format_ref_id
83 return _format_ref_id
84
84
85
85
86 def _format_ref_id(name, raw_id):
86 def _format_ref_id(name, raw_id):
87 """Default formatting of a given reference `name`"""
87 """Default formatting of a given reference `name`"""
88 return name
88 return name
89
89
90
90
91 def _format_ref_id_svn(name, raw_id):
91 def _format_ref_id_svn(name, raw_id):
92 """Special way of formatting a reference for Subversion including path"""
92 """Special way of formatting a reference for Subversion including path"""
93 return '%s@%s' % (name, raw_id)
93 return '%s@%s' % (name, raw_id)
94
94
95
95
96 class TemplateArgs(StrictAttributeDict):
96 class TemplateArgs(StrictAttributeDict):
97 pass
97 pass
98
98
99
99
100 class BaseAppView(object):
100 class BaseAppView(object):
101
101
102 def __init__(self, context, request):
102 def __init__(self, context, request):
103 self.request = request
103 self.request = request
104 self.context = context
104 self.context = context
105 self.session = request.session
105 self.session = request.session
106 if not hasattr(request, 'user'):
106 if not hasattr(request, 'user'):
107 # NOTE(marcink): edge case, we ended up in matched route
107 # NOTE(marcink): edge case, we ended up in matched route
108 # but probably of web-app context, e.g API CALL/VCS CALL
108 # but probably of web-app context, e.g API CALL/VCS CALL
109 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
109 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
110 log.warning('Unable to process request `%s` in this scope', request)
110 log.warning('Unable to process request `%s` in this scope', request)
111 raise HTTPBadRequest()
111 raise HTTPBadRequest()
112
112
113 self._rhodecode_user = request.user # auth user
113 self._rhodecode_user = request.user # auth user
114 self._rhodecode_db_user = self._rhodecode_user.get_instance()
114 self._rhodecode_db_user = self._rhodecode_user.get_instance()
115 self._maybe_needs_password_change(
115 self._maybe_needs_password_change(
116 request.matched_route.name, self._rhodecode_db_user)
116 request.matched_route.name, self._rhodecode_db_user)
117
117
118 def _maybe_needs_password_change(self, view_name, user_obj):
118 def _maybe_needs_password_change(self, view_name, user_obj):
119 log.debug('Checking if user %s needs password change on view %s',
119 log.debug('Checking if user %s needs password change on view %s',
120 user_obj, view_name)
120 user_obj, view_name)
121 skip_user_views = [
121 skip_user_views = [
122 'logout', 'login',
122 'logout', 'login',
123 'my_account_password', 'my_account_password_update'
123 'my_account_password', 'my_account_password_update'
124 ]
124 ]
125
125
126 if not user_obj:
126 if not user_obj:
127 return
127 return
128
128
129 if user_obj.username == User.DEFAULT_USER:
129 if user_obj.username == User.DEFAULT_USER:
130 return
130 return
131
131
132 now = time.time()
132 now = time.time()
133 should_change = user_obj.user_data.get('force_password_change')
133 should_change = user_obj.user_data.get('force_password_change')
134 change_after = safe_int(should_change) or 0
134 change_after = safe_int(should_change) or 0
135 if should_change and now > change_after:
135 if should_change and now > change_after:
136 log.debug('User %s requires password change', user_obj)
136 log.debug('User %s requires password change', user_obj)
137 h.flash('You are required to change your password', 'warning',
137 h.flash('You are required to change your password', 'warning',
138 ignore_duplicate=True)
138 ignore_duplicate=True)
139
139
140 if view_name not in skip_user_views:
140 if view_name not in skip_user_views:
141 raise HTTPFound(
141 raise HTTPFound(
142 self.request.route_path('my_account_password'))
142 self.request.route_path('my_account_password'))
143
143
144 def _log_creation_exception(self, e, repo_name):
144 def _log_creation_exception(self, e, repo_name):
145 _ = self.request.translate
145 _ = self.request.translate
146 reason = None
146 reason = None
147 if len(e.args) == 2:
147 if len(e.args) == 2:
148 reason = e.args[1]
148 reason = e.args[1]
149
149
150 if reason == 'INVALID_CERTIFICATE':
150 if reason == 'INVALID_CERTIFICATE':
151 log.exception(
151 log.exception(
152 'Exception creating a repository: invalid certificate')
152 'Exception creating a repository: invalid certificate')
153 msg = (_('Error creating repository %s: invalid certificate')
153 msg = (_('Error creating repository %s: invalid certificate')
154 % repo_name)
154 % repo_name)
155 else:
155 else:
156 log.exception("Exception creating a repository")
156 log.exception("Exception creating a repository")
157 msg = (_('Error creating repository %s')
157 msg = (_('Error creating repository %s')
158 % repo_name)
158 % repo_name)
159 return msg
159 return msg
160
160
161 def _get_local_tmpl_context(self, include_app_defaults=True):
161 def _get_local_tmpl_context(self, include_app_defaults=True):
162 c = TemplateArgs()
162 c = TemplateArgs()
163 c.auth_user = self.request.user
163 c.auth_user = self.request.user
164 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
164 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
165 c.rhodecode_user = self.request.user
165 c.rhodecode_user = self.request.user
166
166
167 if include_app_defaults:
167 if include_app_defaults:
168 from rhodecode.lib.base import attach_context_attributes
168 from rhodecode.lib.base import attach_context_attributes
169 attach_context_attributes(c, self.request, self.request.user.user_id)
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 return c
193 return c
172
194
173 def _get_template_context(self, tmpl_args, **kwargs):
195 def _get_template_context(self, tmpl_args, **kwargs):
174
196
175 local_tmpl_args = {
197 local_tmpl_args = {
176 'defaults': {},
198 'defaults': {},
177 'errors': {},
199 'errors': {},
178 'c': tmpl_args
200 'c': tmpl_args
179 }
201 }
180 local_tmpl_args.update(kwargs)
202 local_tmpl_args.update(kwargs)
181 return local_tmpl_args
203 return local_tmpl_args
182
204
183 def load_default_context(self):
205 def load_default_context(self):
184 """
206 """
185 example:
207 example:
186
208
187 def load_default_context(self):
209 def load_default_context(self):
188 c = self._get_local_tmpl_context()
210 c = self._get_local_tmpl_context()
189 c.custom_var = 'foobar'
211 c.custom_var = 'foobar'
190
212
191 return c
213 return c
192 """
214 """
193 raise NotImplementedError('Needs implementation in view class')
215 raise NotImplementedError('Needs implementation in view class')
194
216
195
217
196 class RepoAppView(BaseAppView):
218 class RepoAppView(BaseAppView):
197
219
198 def __init__(self, context, request):
220 def __init__(self, context, request):
199 super(RepoAppView, self).__init__(context, request)
221 super(RepoAppView, self).__init__(context, request)
200 self.db_repo = request.db_repo
222 self.db_repo = request.db_repo
201 self.db_repo_name = self.db_repo.repo_name
223 self.db_repo_name = self.db_repo.repo_name
202 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
224 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
203
225
204 def _handle_missing_requirements(self, error):
226 def _handle_missing_requirements(self, error):
205 log.error(
227 log.error(
206 'Requirements are missing for repository %s: %s',
228 'Requirements are missing for repository %s: %s',
207 self.db_repo_name, safe_unicode(error))
229 self.db_repo_name, safe_unicode(error))
208
230
209 def _get_local_tmpl_context(self, include_app_defaults=True):
231 def _get_local_tmpl_context(self, include_app_defaults=True):
210 _ = self.request.translate
232 _ = self.request.translate
211 c = super(RepoAppView, self)._get_local_tmpl_context(
233 c = super(RepoAppView, self)._get_local_tmpl_context(
212 include_app_defaults=include_app_defaults)
234 include_app_defaults=include_app_defaults)
213
235
214 # register common vars for this type of view
236 # register common vars for this type of view
215 c.rhodecode_db_repo = self.db_repo
237 c.rhodecode_db_repo = self.db_repo
216 c.repo_name = self.db_repo_name
238 c.repo_name = self.db_repo_name
217 c.repository_pull_requests = self.db_repo_pull_requests
239 c.repository_pull_requests = self.db_repo_pull_requests
218 self.path_filter = PathFilter(None)
240 self.path_filter = PathFilter(None)
219
241
220 c.repository_requirements_missing = {}
242 c.repository_requirements_missing = {}
221 try:
243 try:
222 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
244 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
223 if self.rhodecode_vcs_repo:
245 if self.rhodecode_vcs_repo:
224 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
246 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
225 c.auth_user.username)
247 c.auth_user.username)
226 self.path_filter = PathFilter(path_perms)
248 self.path_filter = PathFilter(path_perms)
227 except RepositoryRequirementError as e:
249 except RepositoryRequirementError as e:
228 c.repository_requirements_missing = {'error': str(e)}
250 c.repository_requirements_missing = {'error': str(e)}
229 self._handle_missing_requirements(e)
251 self._handle_missing_requirements(e)
230 self.rhodecode_vcs_repo = None
252 self.rhodecode_vcs_repo = None
231
253
232 c.path_filter = self.path_filter # used by atom_feed_entry.mako
254 c.path_filter = self.path_filter # used by atom_feed_entry.mako
233
255
234 if self.rhodecode_vcs_repo is None:
256 if self.rhodecode_vcs_repo is None:
235 # unable to fetch this repo as vcs instance, report back to user
257 # unable to fetch this repo as vcs instance, report back to user
236 h.flash(_(
258 h.flash(_(
237 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
259 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
238 "Please check if it exist, or is not damaged.") %
260 "Please check if it exist, or is not damaged.") %
239 {'repo_name': c.repo_name},
261 {'repo_name': c.repo_name},
240 category='error', ignore_duplicate=True)
262 category='error', ignore_duplicate=True)
241 if c.repository_requirements_missing:
263 if c.repository_requirements_missing:
242 route = self.request.matched_route.name
264 route = self.request.matched_route.name
243 if route.startswith(('edit_repo', 'repo_summary')):
265 if route.startswith(('edit_repo', 'repo_summary')):
244 # allow summary and edit repo on missing requirements
266 # allow summary and edit repo on missing requirements
245 return c
267 return c
246
268
247 raise HTTPFound(
269 raise HTTPFound(
248 h.route_path('repo_summary', repo_name=self.db_repo_name))
270 h.route_path('repo_summary', repo_name=self.db_repo_name))
249
271
250 else: # redirect if we don't show missing requirements
272 else: # redirect if we don't show missing requirements
251 raise HTTPFound(h.route_path('home'))
273 raise HTTPFound(h.route_path('home'))
252
274
253 c.has_origin_repo_read_perm = False
275 c.has_origin_repo_read_perm = False
254 if self.db_repo.fork:
276 if self.db_repo.fork:
255 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
277 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
256 'repository.write', 'repository.read', 'repository.admin')(
278 'repository.write', 'repository.read', 'repository.admin')(
257 self.db_repo.fork.repo_name, 'summary fork link')
279 self.db_repo.fork.repo_name, 'summary fork link')
258
280
259 return c
281 return c
260
282
261 def _get_f_path_unchecked(self, matchdict, default=None):
283 def _get_f_path_unchecked(self, matchdict, default=None):
262 """
284 """
263 Should only be used by redirects, everything else should call _get_f_path
285 Should only be used by redirects, everything else should call _get_f_path
264 """
286 """
265 f_path = matchdict.get('f_path')
287 f_path = matchdict.get('f_path')
266 if f_path:
288 if f_path:
267 # fix for multiple initial slashes that causes errors for GIT
289 # fix for multiple initial slashes that causes errors for GIT
268 return f_path.lstrip('/')
290 return f_path.lstrip('/')
269
291
270 return default
292 return default
271
293
272 def _get_f_path(self, matchdict, default=None):
294 def _get_f_path(self, matchdict, default=None):
273 f_path_match = self._get_f_path_unchecked(matchdict, default)
295 f_path_match = self._get_f_path_unchecked(matchdict, default)
274 return self.path_filter.assert_path_permissions(f_path_match)
296 return self.path_filter.assert_path_permissions(f_path_match)
275
297
276 def _get_general_setting(self, target_repo, settings_key, default=False):
298 def _get_general_setting(self, target_repo, settings_key, default=False):
277 settings_model = VcsSettingsModel(repo=target_repo)
299 settings_model = VcsSettingsModel(repo=target_repo)
278 settings = settings_model.get_general_settings()
300 settings = settings_model.get_general_settings()
279 return settings.get(settings_key, default)
301 return settings.get(settings_key, default)
280
302
281 def get_recache_flag(self):
303 def get_recache_flag(self):
282 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
304 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
283 flag_val = self.request.GET.get(flag_name)
305 flag_val = self.request.GET.get(flag_name)
284 if str2bool(flag_val):
306 if str2bool(flag_val):
285 return True
307 return True
286 return False
308 return False
287
309
288
310
289 class PathFilter(object):
311 class PathFilter(object):
290
312
291 # Expects and instance of BasePathPermissionChecker or None
313 # Expects and instance of BasePathPermissionChecker or None
292 def __init__(self, permission_checker):
314 def __init__(self, permission_checker):
293 self.permission_checker = permission_checker
315 self.permission_checker = permission_checker
294
316
295 def assert_path_permissions(self, path):
317 def assert_path_permissions(self, path):
296 if path and self.permission_checker and not self.permission_checker.has_access(path):
318 if path and self.permission_checker and not self.permission_checker.has_access(path):
297 raise HTTPForbidden()
319 raise HTTPForbidden()
298 return path
320 return path
299
321
300 def filter_patchset(self, patchset):
322 def filter_patchset(self, patchset):
301 if not self.permission_checker or not patchset:
323 if not self.permission_checker or not patchset:
302 return patchset, False
324 return patchset, False
303 had_filtered = False
325 had_filtered = False
304 filtered_patchset = []
326 filtered_patchset = []
305 for patch in patchset:
327 for patch in patchset:
306 filename = patch.get('filename', None)
328 filename = patch.get('filename', None)
307 if not filename or self.permission_checker.has_access(filename):
329 if not filename or self.permission_checker.has_access(filename):
308 filtered_patchset.append(patch)
330 filtered_patchset.append(patch)
309 else:
331 else:
310 had_filtered = True
332 had_filtered = True
311 if had_filtered:
333 if had_filtered:
312 if isinstance(patchset, diffs.LimitedDiffContainer):
334 if isinstance(patchset, diffs.LimitedDiffContainer):
313 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
335 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
314 return filtered_patchset, True
336 return filtered_patchset, True
315 else:
337 else:
316 return patchset, False
338 return patchset, False
317
339
318 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
340 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
319 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
341 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
320 result = diffset.render_patchset(
342 result = diffset.render_patchset(
321 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
343 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
322 result.has_hidden_changes = has_hidden_changes
344 result.has_hidden_changes = has_hidden_changes
323 return result
345 return result
324
346
325 def get_raw_patch(self, diff_processor):
347 def get_raw_patch(self, diff_processor):
326 if self.permission_checker is None:
348 if self.permission_checker is None:
327 return diff_processor.as_raw()
349 return diff_processor.as_raw()
328 elif self.permission_checker.has_full_access:
350 elif self.permission_checker.has_full_access:
329 return diff_processor.as_raw()
351 return diff_processor.as_raw()
330 else:
352 else:
331 return '# Repository has user-specific filters, raw patch generation is disabled.'
353 return '# Repository has user-specific filters, raw patch generation is disabled.'
332
354
333 @property
355 @property
334 def is_enabled(self):
356 def is_enabled(self):
335 return self.permission_checker is not None
357 return self.permission_checker is not None
336
358
337
359
338 class RepoGroupAppView(BaseAppView):
360 class RepoGroupAppView(BaseAppView):
339 def __init__(self, context, request):
361 def __init__(self, context, request):
340 super(RepoGroupAppView, self).__init__(context, request)
362 super(RepoGroupAppView, self).__init__(context, request)
341 self.db_repo_group = request.db_repo_group
363 self.db_repo_group = request.db_repo_group
342 self.db_repo_group_name = self.db_repo_group.group_name
364 self.db_repo_group_name = self.db_repo_group.group_name
343
365
344 def _get_local_tmpl_context(self, include_app_defaults=True):
366 def _get_local_tmpl_context(self, include_app_defaults=True):
345 _ = self.request.translate
367 _ = self.request.translate
346 c = super(RepoGroupAppView, self)._get_local_tmpl_context(
368 c = super(RepoGroupAppView, self)._get_local_tmpl_context(
347 include_app_defaults=include_app_defaults)
369 include_app_defaults=include_app_defaults)
348 c.repo_group = self.db_repo_group
370 c.repo_group = self.db_repo_group
349 return c
371 return c
350
372
351 def _revoke_perms_on_yourself(self, form_result):
373 def _revoke_perms_on_yourself(self, form_result):
352 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
374 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
353 form_result['perm_updates'])
375 form_result['perm_updates'])
354 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
376 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
355 form_result['perm_additions'])
377 form_result['perm_additions'])
356 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
378 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
357 form_result['perm_deletions'])
379 form_result['perm_deletions'])
358 admin_perm = 'group.admin'
380 admin_perm = 'group.admin'
359 if _updates and _updates[0][1] != admin_perm or \
381 if _updates and _updates[0][1] != admin_perm or \
360 _additions and _additions[0][1] != admin_perm or \
382 _additions and _additions[0][1] != admin_perm or \
361 _deletions and _deletions[0][1] != admin_perm:
383 _deletions and _deletions[0][1] != admin_perm:
362 return True
384 return True
363 return False
385 return False
364
386
365
387
366 class UserGroupAppView(BaseAppView):
388 class UserGroupAppView(BaseAppView):
367 def __init__(self, context, request):
389 def __init__(self, context, request):
368 super(UserGroupAppView, self).__init__(context, request)
390 super(UserGroupAppView, self).__init__(context, request)
369 self.db_user_group = request.db_user_group
391 self.db_user_group = request.db_user_group
370 self.db_user_group_name = self.db_user_group.users_group_name
392 self.db_user_group_name = self.db_user_group.users_group_name
371
393
372
394
373 class UserAppView(BaseAppView):
395 class UserAppView(BaseAppView):
374 def __init__(self, context, request):
396 def __init__(self, context, request):
375 super(UserAppView, self).__init__(context, request)
397 super(UserAppView, self).__init__(context, request)
376 self.db_user = request.db_user
398 self.db_user = request.db_user
377 self.db_user_id = self.db_user.user_id
399 self.db_user_id = self.db_user.user_id
378
400
379 _ = self.request.translate
401 _ = self.request.translate
380 if not request.db_user_supports_default:
402 if not request.db_user_supports_default:
381 if self.db_user.username == User.DEFAULT_USER:
403 if self.db_user.username == User.DEFAULT_USER:
382 h.flash(_("Editing user `{}` is disabled.".format(
404 h.flash(_("Editing user `{}` is disabled.".format(
383 User.DEFAULT_USER)), category='warning')
405 User.DEFAULT_USER)), category='warning')
384 raise HTTPFound(h.route_path('users'))
406 raise HTTPFound(h.route_path('users'))
385
407
386
408
387 class DataGridAppView(object):
409 class DataGridAppView(object):
388 """
410 """
389 Common class to have re-usable grid rendering components
411 Common class to have re-usable grid rendering components
390 """
412 """
391
413
392 def _extract_ordering(self, request, column_map=None):
414 def _extract_ordering(self, request, column_map=None):
393 column_map = column_map or {}
415 column_map = column_map or {}
394 column_index = safe_int(request.GET.get('order[0][column]'))
416 column_index = safe_int(request.GET.get('order[0][column]'))
395 order_dir = request.GET.get(
417 order_dir = request.GET.get(
396 'order[0][dir]', 'desc')
418 'order[0][dir]', 'desc')
397 order_by = request.GET.get(
419 order_by = request.GET.get(
398 'columns[%s][data][sort]' % column_index, 'name_raw')
420 'columns[%s][data][sort]' % column_index, 'name_raw')
399
421
400 # translate datatable to DB columns
422 # translate datatable to DB columns
401 order_by = column_map.get(order_by) or order_by
423 order_by = column_map.get(order_by) or order_by
402
424
403 search_q = request.GET.get('search[value]')
425 search_q = request.GET.get('search[value]')
404 return search_q, order_by, order_dir
426 return search_q, order_by, order_dir
405
427
406 def _extract_chunk(self, request):
428 def _extract_chunk(self, request):
407 start = safe_int(request.GET.get('start'), 0)
429 start = safe_int(request.GET.get('start'), 0)
408 length = safe_int(request.GET.get('length'), 25)
430 length = safe_int(request.GET.get('length'), 25)
409 draw = safe_int(request.GET.get('draw'))
431 draw = safe_int(request.GET.get('draw'))
410 return draw, start, length
432 return draw, start, length
411
433
412 def _get_order_col(self, order_by, model):
434 def _get_order_col(self, order_by, model):
413 if isinstance(order_by, compat.string_types):
435 if isinstance(order_by, compat.string_types):
414 try:
436 try:
415 return operator.attrgetter(order_by)(model)
437 return operator.attrgetter(order_by)(model)
416 except AttributeError:
438 except AttributeError:
417 return None
439 return None
418 else:
440 else:
419 return order_by
441 return order_by
420
442
421
443
422 class BaseReferencesView(RepoAppView):
444 class BaseReferencesView(RepoAppView):
423 """
445 """
424 Base for reference view for branches, tags and bookmarks.
446 Base for reference view for branches, tags and bookmarks.
425 """
447 """
426 def load_default_context(self):
448 def load_default_context(self):
427 c = self._get_local_tmpl_context()
449 c = self._get_local_tmpl_context()
428
450
429
451
430 return c
452 return c
431
453
432 def load_refs_context(self, ref_items, partials_template):
454 def load_refs_context(self, ref_items, partials_template):
433 _render = self.request.get_partial_renderer(partials_template)
455 _render = self.request.get_partial_renderer(partials_template)
434 pre_load = ["author", "date", "message"]
456 pre_load = ["author", "date", "message"]
435
457
436 is_svn = h.is_svn(self.rhodecode_vcs_repo)
458 is_svn = h.is_svn(self.rhodecode_vcs_repo)
437 is_hg = h.is_hg(self.rhodecode_vcs_repo)
459 is_hg = h.is_hg(self.rhodecode_vcs_repo)
438
460
439 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
461 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
440
462
441 closed_refs = {}
463 closed_refs = {}
442 if is_hg:
464 if is_hg:
443 closed_refs = self.rhodecode_vcs_repo.branches_closed
465 closed_refs = self.rhodecode_vcs_repo.branches_closed
444
466
445 data = []
467 data = []
446 for ref_name, commit_id in ref_items:
468 for ref_name, commit_id in ref_items:
447 commit = self.rhodecode_vcs_repo.get_commit(
469 commit = self.rhodecode_vcs_repo.get_commit(
448 commit_id=commit_id, pre_load=pre_load)
470 commit_id=commit_id, pre_load=pre_load)
449 closed = ref_name in closed_refs
471 closed = ref_name in closed_refs
450
472
451 # TODO: johbo: Unify generation of reference links
473 # TODO: johbo: Unify generation of reference links
452 use_commit_id = '/' in ref_name or is_svn
474 use_commit_id = '/' in ref_name or is_svn
453
475
454 if use_commit_id:
476 if use_commit_id:
455 files_url = h.route_path(
477 files_url = h.route_path(
456 'repo_files',
478 'repo_files',
457 repo_name=self.db_repo_name,
479 repo_name=self.db_repo_name,
458 f_path=ref_name if is_svn else '',
480 f_path=ref_name if is_svn else '',
459 commit_id=commit_id)
481 commit_id=commit_id)
460
482
461 else:
483 else:
462 files_url = h.route_path(
484 files_url = h.route_path(
463 'repo_files',
485 'repo_files',
464 repo_name=self.db_repo_name,
486 repo_name=self.db_repo_name,
465 f_path=ref_name if is_svn else '',
487 f_path=ref_name if is_svn else '',
466 commit_id=ref_name,
488 commit_id=ref_name,
467 _query=dict(at=ref_name))
489 _query=dict(at=ref_name))
468
490
469 data.append({
491 data.append({
470 "name": _render('name', ref_name, files_url, closed),
492 "name": _render('name', ref_name, files_url, closed),
471 "name_raw": ref_name,
493 "name_raw": ref_name,
472 "date": _render('date', commit.date),
494 "date": _render('date', commit.date),
473 "date_raw": datetime_to_time(commit.date),
495 "date_raw": datetime_to_time(commit.date),
474 "author": _render('author', commit.author),
496 "author": _render('author', commit.author),
475 "commit": _render(
497 "commit": _render(
476 'commit', commit.message, commit.raw_id, commit.idx),
498 'commit', commit.message, commit.raw_id, commit.idx),
477 "commit_raw": commit.idx,
499 "commit_raw": commit.idx,
478 "compare": _render(
500 "compare": _render(
479 'compare', format_ref_id(ref_name, commit.raw_id)),
501 'compare', format_ref_id(ref_name, commit.raw_id)),
480 })
502 })
481
503
482 return data
504 return data
483
505
484
506
485 class RepoRoutePredicate(object):
507 class RepoRoutePredicate(object):
486 def __init__(self, val, config):
508 def __init__(self, val, config):
487 self.val = val
509 self.val = val
488
510
489 def text(self):
511 def text(self):
490 return 'repo_route = %s' % self.val
512 return 'repo_route = %s' % self.val
491
513
492 phash = text
514 phash = text
493
515
494 def __call__(self, info, request):
516 def __call__(self, info, request):
495 if hasattr(request, 'vcs_call'):
517 if hasattr(request, 'vcs_call'):
496 # skip vcs calls
518 # skip vcs calls
497 return
519 return
498
520
499 repo_name = info['match']['repo_name']
521 repo_name = info['match']['repo_name']
500 repo_model = repo.RepoModel()
522 repo_model = repo.RepoModel()
501
523
502 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
524 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
503
525
504 def redirect_if_creating(route_info, db_repo):
526 def redirect_if_creating(route_info, db_repo):
505 skip_views = ['edit_repo_advanced_delete']
527 skip_views = ['edit_repo_advanced_delete']
506 route = route_info['route']
528 route = route_info['route']
507 # we should skip delete view so we can actually "remove" repositories
529 # we should skip delete view so we can actually "remove" repositories
508 # if they get stuck in creating state.
530 # if they get stuck in creating state.
509 if route.name in skip_views:
531 if route.name in skip_views:
510 return
532 return
511
533
512 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
534 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
513 repo_creating_url = request.route_path(
535 repo_creating_url = request.route_path(
514 'repo_creating', repo_name=db_repo.repo_name)
536 'repo_creating', repo_name=db_repo.repo_name)
515 raise HTTPFound(repo_creating_url)
537 raise HTTPFound(repo_creating_url)
516
538
517 if by_name_match:
539 if by_name_match:
518 # register this as request object we can re-use later
540 # register this as request object we can re-use later
519 request.db_repo = by_name_match
541 request.db_repo = by_name_match
520 redirect_if_creating(info, by_name_match)
542 redirect_if_creating(info, by_name_match)
521 return True
543 return True
522
544
523 by_id_match = repo_model.get_repo_by_id(repo_name)
545 by_id_match = repo_model.get_repo_by_id(repo_name)
524 if by_id_match:
546 if by_id_match:
525 request.db_repo = by_id_match
547 request.db_repo = by_id_match
526 redirect_if_creating(info, by_id_match)
548 redirect_if_creating(info, by_id_match)
527 return True
549 return True
528
550
529 return False
551 return False
530
552
531
553
532 class RepoForbidArchivedRoutePredicate(object):
554 class RepoForbidArchivedRoutePredicate(object):
533 def __init__(self, val, config):
555 def __init__(self, val, config):
534 self.val = val
556 self.val = val
535
557
536 def text(self):
558 def text(self):
537 return 'repo_forbid_archived = %s' % self.val
559 return 'repo_forbid_archived = %s' % self.val
538
560
539 phash = text
561 phash = text
540
562
541 def __call__(self, info, request):
563 def __call__(self, info, request):
542 _ = request.translate
564 _ = request.translate
543 rhodecode_db_repo = request.db_repo
565 rhodecode_db_repo = request.db_repo
544
566
545 log.debug(
567 log.debug(
546 '%s checking if archived flag for repo for %s',
568 '%s checking if archived flag for repo for %s',
547 self.__class__.__name__, rhodecode_db_repo.repo_name)
569 self.__class__.__name__, rhodecode_db_repo.repo_name)
548
570
549 if rhodecode_db_repo.archived:
571 if rhodecode_db_repo.archived:
550 log.warning('Current view is not supported for archived repo:%s',
572 log.warning('Current view is not supported for archived repo:%s',
551 rhodecode_db_repo.repo_name)
573 rhodecode_db_repo.repo_name)
552
574
553 h.flash(
575 h.flash(
554 h.literal(_('Action not supported for archived repository.')),
576 h.literal(_('Action not supported for archived repository.')),
555 category='warning')
577 category='warning')
556 summary_url = request.route_path(
578 summary_url = request.route_path(
557 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
579 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
558 raise HTTPFound(summary_url)
580 raise HTTPFound(summary_url)
559 return True
581 return True
560
582
561
583
562 class RepoTypeRoutePredicate(object):
584 class RepoTypeRoutePredicate(object):
563 def __init__(self, val, config):
585 def __init__(self, val, config):
564 self.val = val or ['hg', 'git', 'svn']
586 self.val = val or ['hg', 'git', 'svn']
565
587
566 def text(self):
588 def text(self):
567 return 'repo_accepted_type = %s' % self.val
589 return 'repo_accepted_type = %s' % self.val
568
590
569 phash = text
591 phash = text
570
592
571 def __call__(self, info, request):
593 def __call__(self, info, request):
572 if hasattr(request, 'vcs_call'):
594 if hasattr(request, 'vcs_call'):
573 # skip vcs calls
595 # skip vcs calls
574 return
596 return
575
597
576 rhodecode_db_repo = request.db_repo
598 rhodecode_db_repo = request.db_repo
577
599
578 log.debug(
600 log.debug(
579 '%s checking repo type for %s in %s',
601 '%s checking repo type for %s in %s',
580 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
602 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
581
603
582 if rhodecode_db_repo.repo_type in self.val:
604 if rhodecode_db_repo.repo_type in self.val:
583 return True
605 return True
584 else:
606 else:
585 log.warning('Current view is not supported for repo type:%s',
607 log.warning('Current view is not supported for repo type:%s',
586 rhodecode_db_repo.repo_type)
608 rhodecode_db_repo.repo_type)
587 return False
609 return False
588
610
589
611
590 class RepoGroupRoutePredicate(object):
612 class RepoGroupRoutePredicate(object):
591 def __init__(self, val, config):
613 def __init__(self, val, config):
592 self.val = val
614 self.val = val
593
615
594 def text(self):
616 def text(self):
595 return 'repo_group_route = %s' % self.val
617 return 'repo_group_route = %s' % self.val
596
618
597 phash = text
619 phash = text
598
620
599 def __call__(self, info, request):
621 def __call__(self, info, request):
600 if hasattr(request, 'vcs_call'):
622 if hasattr(request, 'vcs_call'):
601 # skip vcs calls
623 # skip vcs calls
602 return
624 return
603
625
604 repo_group_name = info['match']['repo_group_name']
626 repo_group_name = info['match']['repo_group_name']
605 repo_group_model = repo_group.RepoGroupModel()
627 repo_group_model = repo_group.RepoGroupModel()
606 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
628 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
607
629
608 if by_name_match:
630 if by_name_match:
609 # register this as request object we can re-use later
631 # register this as request object we can re-use later
610 request.db_repo_group = by_name_match
632 request.db_repo_group = by_name_match
611 return True
633 return True
612
634
613 return False
635 return False
614
636
615
637
616 class UserGroupRoutePredicate(object):
638 class UserGroupRoutePredicate(object):
617 def __init__(self, val, config):
639 def __init__(self, val, config):
618 self.val = val
640 self.val = val
619
641
620 def text(self):
642 def text(self):
621 return 'user_group_route = %s' % self.val
643 return 'user_group_route = %s' % self.val
622
644
623 phash = text
645 phash = text
624
646
625 def __call__(self, info, request):
647 def __call__(self, info, request):
626 if hasattr(request, 'vcs_call'):
648 if hasattr(request, 'vcs_call'):
627 # skip vcs calls
649 # skip vcs calls
628 return
650 return
629
651
630 user_group_id = info['match']['user_group_id']
652 user_group_id = info['match']['user_group_id']
631 user_group_model = user_group.UserGroup()
653 user_group_model = user_group.UserGroup()
632 by_id_match = user_group_model.get(user_group_id, cache=False)
654 by_id_match = user_group_model.get(user_group_id, cache=False)
633
655
634 if by_id_match:
656 if by_id_match:
635 # register this as request object we can re-use later
657 # register this as request object we can re-use later
636 request.db_user_group = by_id_match
658 request.db_user_group = by_id_match
637 return True
659 return True
638
660
639 return False
661 return False
640
662
641
663
642 class UserRoutePredicateBase(object):
664 class UserRoutePredicateBase(object):
643 supports_default = None
665 supports_default = None
644
666
645 def __init__(self, val, config):
667 def __init__(self, val, config):
646 self.val = val
668 self.val = val
647
669
648 def text(self):
670 def text(self):
649 raise NotImplementedError()
671 raise NotImplementedError()
650
672
651 def __call__(self, info, request):
673 def __call__(self, info, request):
652 if hasattr(request, 'vcs_call'):
674 if hasattr(request, 'vcs_call'):
653 # skip vcs calls
675 # skip vcs calls
654 return
676 return
655
677
656 user_id = info['match']['user_id']
678 user_id = info['match']['user_id']
657 user_model = user.User()
679 user_model = user.User()
658 by_id_match = user_model.get(user_id, cache=False)
680 by_id_match = user_model.get(user_id, cache=False)
659
681
660 if by_id_match:
682 if by_id_match:
661 # register this as request object we can re-use later
683 # register this as request object we can re-use later
662 request.db_user = by_id_match
684 request.db_user = by_id_match
663 request.db_user_supports_default = self.supports_default
685 request.db_user_supports_default = self.supports_default
664 return True
686 return True
665
687
666 return False
688 return False
667
689
668
690
669 class UserRoutePredicate(UserRoutePredicateBase):
691 class UserRoutePredicate(UserRoutePredicateBase):
670 supports_default = False
692 supports_default = False
671
693
672 def text(self):
694 def text(self):
673 return 'user_route = %s' % self.val
695 return 'user_route = %s' % self.val
674
696
675 phash = text
697 phash = text
676
698
677
699
678 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
700 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
679 supports_default = True
701 supports_default = True
680
702
681 def text(self):
703 def text(self):
682 return 'user_with_default_route = %s' % self.val
704 return 'user_with_default_route = %s' % self.val
683
705
684 phash = text
706 phash = text
685
707
686
708
687 def includeme(config):
709 def includeme(config):
688 config.add_route_predicate(
710 config.add_route_predicate(
689 'repo_route', RepoRoutePredicate)
711 'repo_route', RepoRoutePredicate)
690 config.add_route_predicate(
712 config.add_route_predicate(
691 'repo_accepted_types', RepoTypeRoutePredicate)
713 'repo_accepted_types', RepoTypeRoutePredicate)
692 config.add_route_predicate(
714 config.add_route_predicate(
693 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
715 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
694 config.add_route_predicate(
716 config.add_route_predicate(
695 'repo_group_route', RepoGroupRoutePredicate)
717 'repo_group_route', RepoGroupRoutePredicate)
696 config.add_route_predicate(
718 config.add_route_predicate(
697 'user_group_route', UserGroupRoutePredicate)
719 'user_group_route', UserGroupRoutePredicate)
698 config.add_route_predicate(
720 config.add_route_predicate(
699 'user_route_with_default', UserRouteWithDefaultPredicate)
721 'user_route_with_default', UserRouteWithDefaultPredicate)
700 config.add_route_predicate(
722 config.add_route_predicate(
701 'user_route', UserRoutePredicate)
723 'user_route', UserRoutePredicate)
@@ -1,69 +1,72 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from pyramid.httpexceptions import HTTPFound
23 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25
25
26 from rhodecode.apps._base import BaseAppView
26 from rhodecode.apps._base import BaseAppView
27 from rhodecode.lib import helpers as h
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 from rhodecode.model.db import PullRequest
29 from rhodecode.model.db import PullRequest
30
30
31
31
32 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
33
33
34
34
35 class AdminMainView(BaseAppView):
35 class AdminMainView(BaseAppView):
36 def load_default_context(self):
36 def load_default_context(self):
37 c = self._get_local_tmpl_context()
37 c = self._get_local_tmpl_context()
38 return c
38 return c
39
39
40 @LoginRequired()
40 @LoginRequired()
41 @HasPermissionAllDecorator('hg.admin')
41 @NotAnonymous()
42 @view_config(
42 @view_config(
43 route_name='admin_home', request_method='GET',
43 route_name='admin_home', request_method='GET',
44 renderer='rhodecode:templates/admin/main.mako')
44 renderer='rhodecode:templates/admin/main.mako')
45 def admin_main(self):
45 def admin_main(self):
46 c = self.load_default_context()
46 c = self.load_default_context()
47 c.active = 'admin'
47 c.active = 'admin'
48
49 if not (c.is_super_admin or c.is_delegated_admin):
50 raise HTTPNotFound()
51
48 return self._get_template_context(c)
52 return self._get_template_context(c)
49
53
50 @LoginRequired()
54 @LoginRequired()
51 @view_config(route_name='pull_requests_global_0', request_method='GET')
55 @view_config(route_name='pull_requests_global_0', request_method='GET')
52 @view_config(route_name='pull_requests_global_1', request_method='GET')
56 @view_config(route_name='pull_requests_global_1', request_method='GET')
53 @view_config(route_name='pull_requests_global', request_method='GET')
57 @view_config(route_name='pull_requests_global', request_method='GET')
54 def pull_requests(self):
58 def pull_requests(self):
55 """
59 """
56 Global redirect for Pull Requests
60 Global redirect for Pull Requests
57
61 pull_request_id: id of pull requests in the system
58 :param pull_request_id: id of pull requests in the system
59 """
62 """
60
63
61 pull_request = PullRequest.get_or_404(
64 pull_request = PullRequest.get_or_404(
62 self.request.matchdict['pull_request_id'])
65 self.request.matchdict['pull_request_id'])
63 pull_request_id = pull_request.pull_request_id
66 pull_request_id = pull_request.pull_request_id
64
67
65 repo_name = pull_request.target_repo.repo_name
68 repo_name = pull_request.target_repo.repo_name
66
69
67 raise HTTPFound(
70 raise HTTPFound(
68 h.route_path('pullrequest_show', repo_name=repo_name,
71 h.route_path('pullrequest_show', repo_name=repo_name,
69 pull_request_id=pull_request_id))
72 pull_request_id=pull_request_id))
@@ -1,2356 +1,2352 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 authentication and permission libraries
22 authentication and permission libraries
23 """
23 """
24
24
25 import os
25 import os
26 import time
26 import time
27 import inspect
27 import inspect
28 import collections
28 import collections
29 import fnmatch
29 import fnmatch
30 import hashlib
30 import hashlib
31 import itertools
31 import itertools
32 import logging
32 import logging
33 import random
33 import random
34 import traceback
34 import traceback
35 from functools import wraps
35 from functools import wraps
36
36
37 import ipaddress
37 import ipaddress
38
38
39 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
39 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
40 from sqlalchemy.orm.exc import ObjectDeletedError
40 from sqlalchemy.orm.exc import ObjectDeletedError
41 from sqlalchemy.orm import joinedload
41 from sqlalchemy.orm import joinedload
42 from zope.cachedescriptors.property import Lazy as LazyProperty
42 from zope.cachedescriptors.property import Lazy as LazyProperty
43
43
44 import rhodecode
44 import rhodecode
45 from rhodecode.model import meta
45 from rhodecode.model import meta
46 from rhodecode.model.meta import Session
46 from rhodecode.model.meta import Session
47 from rhodecode.model.user import UserModel
47 from rhodecode.model.user import UserModel
48 from rhodecode.model.db import (
48 from rhodecode.model.db import (
49 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
49 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
50 UserIpMap, UserApiKeys, RepoGroup, UserGroup)
50 UserIpMap, UserApiKeys, RepoGroup, UserGroup)
51 from rhodecode.lib import rc_cache
51 from rhodecode.lib import rc_cache
52 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5, safe_int, sha1
52 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5, safe_int, sha1
53 from rhodecode.lib.utils import (
53 from rhodecode.lib.utils import (
54 get_repo_slug, get_repo_group_slug, get_user_group_slug)
54 get_repo_slug, get_repo_group_slug, get_user_group_slug)
55 from rhodecode.lib.caching_query import FromCache
55 from rhodecode.lib.caching_query import FromCache
56
56
57
57
58 if rhodecode.is_unix:
58 if rhodecode.is_unix:
59 import bcrypt
59 import bcrypt
60
60
61 log = logging.getLogger(__name__)
61 log = logging.getLogger(__name__)
62
62
63 csrf_token_key = "csrf_token"
63 csrf_token_key = "csrf_token"
64
64
65
65
66 class PasswordGenerator(object):
66 class PasswordGenerator(object):
67 """
67 """
68 This is a simple class for generating password from different sets of
68 This is a simple class for generating password from different sets of
69 characters
69 characters
70 usage::
70 usage::
71 passwd_gen = PasswordGenerator()
71 passwd_gen = PasswordGenerator()
72 #print 8-letter password containing only big and small letters
72 #print 8-letter password containing only big and small letters
73 of alphabet
73 of alphabet
74 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
74 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
75 """
75 """
76 ALPHABETS_NUM = r'''1234567890'''
76 ALPHABETS_NUM = r'''1234567890'''
77 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
77 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
78 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
78 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
79 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
79 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
80 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
80 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
81 + ALPHABETS_NUM + ALPHABETS_SPECIAL
81 + ALPHABETS_NUM + ALPHABETS_SPECIAL
82 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
82 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
83 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
83 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
84 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
84 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
85 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
85 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
86
86
87 def __init__(self, passwd=''):
87 def __init__(self, passwd=''):
88 self.passwd = passwd
88 self.passwd = passwd
89
89
90 def gen_password(self, length, type_=None):
90 def gen_password(self, length, type_=None):
91 if type_ is None:
91 if type_ is None:
92 type_ = self.ALPHABETS_FULL
92 type_ = self.ALPHABETS_FULL
93 self.passwd = ''.join([random.choice(type_) for _ in range(length)])
93 self.passwd = ''.join([random.choice(type_) for _ in range(length)])
94 return self.passwd
94 return self.passwd
95
95
96
96
97 class _RhodeCodeCryptoBase(object):
97 class _RhodeCodeCryptoBase(object):
98 ENC_PREF = None
98 ENC_PREF = None
99
99
100 def hash_create(self, str_):
100 def hash_create(self, str_):
101 """
101 """
102 hash the string using
102 hash the string using
103
103
104 :param str_: password to hash
104 :param str_: password to hash
105 """
105 """
106 raise NotImplementedError
106 raise NotImplementedError
107
107
108 def hash_check_with_upgrade(self, password, hashed):
108 def hash_check_with_upgrade(self, password, hashed):
109 """
109 """
110 Returns tuple in which first element is boolean that states that
110 Returns tuple in which first element is boolean that states that
111 given password matches it's hashed version, and the second is new hash
111 given password matches it's hashed version, and the second is new hash
112 of the password, in case this password should be migrated to new
112 of the password, in case this password should be migrated to new
113 cipher.
113 cipher.
114 """
114 """
115 checked_hash = self.hash_check(password, hashed)
115 checked_hash = self.hash_check(password, hashed)
116 return checked_hash, None
116 return checked_hash, None
117
117
118 def hash_check(self, password, hashed):
118 def hash_check(self, password, hashed):
119 """
119 """
120 Checks matching password with it's hashed value.
120 Checks matching password with it's hashed value.
121
121
122 :param password: password
122 :param password: password
123 :param hashed: password in hashed form
123 :param hashed: password in hashed form
124 """
124 """
125 raise NotImplementedError
125 raise NotImplementedError
126
126
127 def _assert_bytes(self, value):
127 def _assert_bytes(self, value):
128 """
128 """
129 Passing in an `unicode` object can lead to hard to detect issues
129 Passing in an `unicode` object can lead to hard to detect issues
130 if passwords contain non-ascii characters. Doing a type check
130 if passwords contain non-ascii characters. Doing a type check
131 during runtime, so that such mistakes are detected early on.
131 during runtime, so that such mistakes are detected early on.
132 """
132 """
133 if not isinstance(value, str):
133 if not isinstance(value, str):
134 raise TypeError(
134 raise TypeError(
135 "Bytestring required as input, got %r." % (value, ))
135 "Bytestring required as input, got %r." % (value, ))
136
136
137
137
138 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
138 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
139 ENC_PREF = ('$2a$10', '$2b$10')
139 ENC_PREF = ('$2a$10', '$2b$10')
140
140
141 def hash_create(self, str_):
141 def hash_create(self, str_):
142 self._assert_bytes(str_)
142 self._assert_bytes(str_)
143 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
143 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
144
144
145 def hash_check_with_upgrade(self, password, hashed):
145 def hash_check_with_upgrade(self, password, hashed):
146 """
146 """
147 Returns tuple in which first element is boolean that states that
147 Returns tuple in which first element is boolean that states that
148 given password matches it's hashed version, and the second is new hash
148 given password matches it's hashed version, and the second is new hash
149 of the password, in case this password should be migrated to new
149 of the password, in case this password should be migrated to new
150 cipher.
150 cipher.
151
151
152 This implements special upgrade logic which works like that:
152 This implements special upgrade logic which works like that:
153 - check if the given password == bcrypted hash, if yes then we
153 - check if the given password == bcrypted hash, if yes then we
154 properly used password and it was already in bcrypt. Proceed
154 properly used password and it was already in bcrypt. Proceed
155 without any changes
155 without any changes
156 - if bcrypt hash check is not working try with sha256. If hash compare
156 - if bcrypt hash check is not working try with sha256. If hash compare
157 is ok, it means we using correct but old hashed password. indicate
157 is ok, it means we using correct but old hashed password. indicate
158 hash change and proceed
158 hash change and proceed
159 """
159 """
160
160
161 new_hash = None
161 new_hash = None
162
162
163 # regular pw check
163 # regular pw check
164 password_match_bcrypt = self.hash_check(password, hashed)
164 password_match_bcrypt = self.hash_check(password, hashed)
165
165
166 # now we want to know if the password was maybe from sha256
166 # now we want to know if the password was maybe from sha256
167 # basically calling _RhodeCodeCryptoSha256().hash_check()
167 # basically calling _RhodeCodeCryptoSha256().hash_check()
168 if not password_match_bcrypt:
168 if not password_match_bcrypt:
169 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
169 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
170 new_hash = self.hash_create(password) # make new bcrypt hash
170 new_hash = self.hash_create(password) # make new bcrypt hash
171 password_match_bcrypt = True
171 password_match_bcrypt = True
172
172
173 return password_match_bcrypt, new_hash
173 return password_match_bcrypt, new_hash
174
174
175 def hash_check(self, password, hashed):
175 def hash_check(self, password, hashed):
176 """
176 """
177 Checks matching password with it's hashed value.
177 Checks matching password with it's hashed value.
178
178
179 :param password: password
179 :param password: password
180 :param hashed: password in hashed form
180 :param hashed: password in hashed form
181 """
181 """
182 self._assert_bytes(password)
182 self._assert_bytes(password)
183 try:
183 try:
184 return bcrypt.hashpw(password, hashed) == hashed
184 return bcrypt.hashpw(password, hashed) == hashed
185 except ValueError as e:
185 except ValueError as e:
186 # we're having a invalid salt here probably, we should not crash
186 # we're having a invalid salt here probably, we should not crash
187 # just return with False as it would be a wrong password.
187 # just return with False as it would be a wrong password.
188 log.debug('Failed to check password hash using bcrypt %s',
188 log.debug('Failed to check password hash using bcrypt %s',
189 safe_str(e))
189 safe_str(e))
190
190
191 return False
191 return False
192
192
193
193
194 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
194 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
195 ENC_PREF = '_'
195 ENC_PREF = '_'
196
196
197 def hash_create(self, str_):
197 def hash_create(self, str_):
198 self._assert_bytes(str_)
198 self._assert_bytes(str_)
199 return hashlib.sha256(str_).hexdigest()
199 return hashlib.sha256(str_).hexdigest()
200
200
201 def hash_check(self, password, hashed):
201 def hash_check(self, password, hashed):
202 """
202 """
203 Checks matching password with it's hashed value.
203 Checks matching password with it's hashed value.
204
204
205 :param password: password
205 :param password: password
206 :param hashed: password in hashed form
206 :param hashed: password in hashed form
207 """
207 """
208 self._assert_bytes(password)
208 self._assert_bytes(password)
209 return hashlib.sha256(password).hexdigest() == hashed
209 return hashlib.sha256(password).hexdigest() == hashed
210
210
211
211
212 class _RhodeCodeCryptoTest(_RhodeCodeCryptoBase):
212 class _RhodeCodeCryptoTest(_RhodeCodeCryptoBase):
213 ENC_PREF = '_'
213 ENC_PREF = '_'
214
214
215 def hash_create(self, str_):
215 def hash_create(self, str_):
216 self._assert_bytes(str_)
216 self._assert_bytes(str_)
217 return sha1(str_)
217 return sha1(str_)
218
218
219 def hash_check(self, password, hashed):
219 def hash_check(self, password, hashed):
220 """
220 """
221 Checks matching password with it's hashed value.
221 Checks matching password with it's hashed value.
222
222
223 :param password: password
223 :param password: password
224 :param hashed: password in hashed form
224 :param hashed: password in hashed form
225 """
225 """
226 self._assert_bytes(password)
226 self._assert_bytes(password)
227 return sha1(password) == hashed
227 return sha1(password) == hashed
228
228
229
229
230 def crypto_backend():
230 def crypto_backend():
231 """
231 """
232 Return the matching crypto backend.
232 Return the matching crypto backend.
233
233
234 Selection is based on if we run tests or not, we pick sha1-test backend to run
234 Selection is based on if we run tests or not, we pick sha1-test backend to run
235 tests faster since BCRYPT is expensive to calculate
235 tests faster since BCRYPT is expensive to calculate
236 """
236 """
237 if rhodecode.is_test:
237 if rhodecode.is_test:
238 RhodeCodeCrypto = _RhodeCodeCryptoTest()
238 RhodeCodeCrypto = _RhodeCodeCryptoTest()
239 else:
239 else:
240 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
240 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
241
241
242 return RhodeCodeCrypto
242 return RhodeCodeCrypto
243
243
244
244
245 def get_crypt_password(password):
245 def get_crypt_password(password):
246 """
246 """
247 Create the hash of `password` with the active crypto backend.
247 Create the hash of `password` with the active crypto backend.
248
248
249 :param password: The cleartext password.
249 :param password: The cleartext password.
250 :type password: unicode
250 :type password: unicode
251 """
251 """
252 password = safe_str(password)
252 password = safe_str(password)
253 return crypto_backend().hash_create(password)
253 return crypto_backend().hash_create(password)
254
254
255
255
256 def check_password(password, hashed):
256 def check_password(password, hashed):
257 """
257 """
258 Check if the value in `password` matches the hash in `hashed`.
258 Check if the value in `password` matches the hash in `hashed`.
259
259
260 :param password: The cleartext password.
260 :param password: The cleartext password.
261 :type password: unicode
261 :type password: unicode
262
262
263 :param hashed: The expected hashed version of the password.
263 :param hashed: The expected hashed version of the password.
264 :type hashed: The hash has to be passed in in text representation.
264 :type hashed: The hash has to be passed in in text representation.
265 """
265 """
266 password = safe_str(password)
266 password = safe_str(password)
267 return crypto_backend().hash_check(password, hashed)
267 return crypto_backend().hash_check(password, hashed)
268
268
269
269
270 def generate_auth_token(data, salt=None):
270 def generate_auth_token(data, salt=None):
271 """
271 """
272 Generates API KEY from given string
272 Generates API KEY from given string
273 """
273 """
274
274
275 if salt is None:
275 if salt is None:
276 salt = os.urandom(16)
276 salt = os.urandom(16)
277 return hashlib.sha1(safe_str(data) + salt).hexdigest()
277 return hashlib.sha1(safe_str(data) + salt).hexdigest()
278
278
279
279
280 def get_came_from(request):
280 def get_came_from(request):
281 """
281 """
282 get query_string+path from request sanitized after removing auth_token
282 get query_string+path from request sanitized after removing auth_token
283 """
283 """
284 _req = request
284 _req = request
285
285
286 path = _req.path
286 path = _req.path
287 if 'auth_token' in _req.GET:
287 if 'auth_token' in _req.GET:
288 # sanitize the request and remove auth_token for redirection
288 # sanitize the request and remove auth_token for redirection
289 _req.GET.pop('auth_token')
289 _req.GET.pop('auth_token')
290 qs = _req.query_string
290 qs = _req.query_string
291 if qs:
291 if qs:
292 path += '?' + qs
292 path += '?' + qs
293
293
294 return path
294 return path
295
295
296
296
297 class CookieStoreWrapper(object):
297 class CookieStoreWrapper(object):
298
298
299 def __init__(self, cookie_store):
299 def __init__(self, cookie_store):
300 self.cookie_store = cookie_store
300 self.cookie_store = cookie_store
301
301
302 def __repr__(self):
302 def __repr__(self):
303 return 'CookieStore<%s>' % (self.cookie_store)
303 return 'CookieStore<%s>' % (self.cookie_store)
304
304
305 def get(self, key, other=None):
305 def get(self, key, other=None):
306 if isinstance(self.cookie_store, dict):
306 if isinstance(self.cookie_store, dict):
307 return self.cookie_store.get(key, other)
307 return self.cookie_store.get(key, other)
308 elif isinstance(self.cookie_store, AuthUser):
308 elif isinstance(self.cookie_store, AuthUser):
309 return self.cookie_store.__dict__.get(key, other)
309 return self.cookie_store.__dict__.get(key, other)
310
310
311
311
312 def _cached_perms_data(user_id, scope, user_is_admin,
312 def _cached_perms_data(user_id, scope, user_is_admin,
313 user_inherit_default_permissions, explicit, algo,
313 user_inherit_default_permissions, explicit, algo,
314 calculate_super_admin):
314 calculate_super_admin):
315
315
316 permissions = PermissionCalculator(
316 permissions = PermissionCalculator(
317 user_id, scope, user_is_admin, user_inherit_default_permissions,
317 user_id, scope, user_is_admin, user_inherit_default_permissions,
318 explicit, algo, calculate_super_admin)
318 explicit, algo, calculate_super_admin)
319 return permissions.calculate()
319 return permissions.calculate()
320
320
321
321
322 class PermOrigin(object):
322 class PermOrigin(object):
323 SUPER_ADMIN = 'superadmin'
323 SUPER_ADMIN = 'superadmin'
324 ARCHIVED = 'archived'
324 ARCHIVED = 'archived'
325
325
326 REPO_USER = 'user:%s'
326 REPO_USER = 'user:%s'
327 REPO_USERGROUP = 'usergroup:%s'
327 REPO_USERGROUP = 'usergroup:%s'
328 REPO_OWNER = 'repo.owner'
328 REPO_OWNER = 'repo.owner'
329 REPO_DEFAULT = 'repo.default'
329 REPO_DEFAULT = 'repo.default'
330 REPO_DEFAULT_NO_INHERIT = 'repo.default.no.inherit'
330 REPO_DEFAULT_NO_INHERIT = 'repo.default.no.inherit'
331 REPO_PRIVATE = 'repo.private'
331 REPO_PRIVATE = 'repo.private'
332
332
333 REPOGROUP_USER = 'user:%s'
333 REPOGROUP_USER = 'user:%s'
334 REPOGROUP_USERGROUP = 'usergroup:%s'
334 REPOGROUP_USERGROUP = 'usergroup:%s'
335 REPOGROUP_OWNER = 'group.owner'
335 REPOGROUP_OWNER = 'group.owner'
336 REPOGROUP_DEFAULT = 'group.default'
336 REPOGROUP_DEFAULT = 'group.default'
337 REPOGROUP_DEFAULT_NO_INHERIT = 'group.default.no.inherit'
337 REPOGROUP_DEFAULT_NO_INHERIT = 'group.default.no.inherit'
338
338
339 USERGROUP_USER = 'user:%s'
339 USERGROUP_USER = 'user:%s'
340 USERGROUP_USERGROUP = 'usergroup:%s'
340 USERGROUP_USERGROUP = 'usergroup:%s'
341 USERGROUP_OWNER = 'usergroup.owner'
341 USERGROUP_OWNER = 'usergroup.owner'
342 USERGROUP_DEFAULT = 'usergroup.default'
342 USERGROUP_DEFAULT = 'usergroup.default'
343 USERGROUP_DEFAULT_NO_INHERIT = 'usergroup.default.no.inherit'
343 USERGROUP_DEFAULT_NO_INHERIT = 'usergroup.default.no.inherit'
344
344
345
345
346 class PermOriginDict(dict):
346 class PermOriginDict(dict):
347 """
347 """
348 A special dict used for tracking permissions along with their origins.
348 A special dict used for tracking permissions along with their origins.
349
349
350 `__setitem__` has been overridden to expect a tuple(perm, origin)
350 `__setitem__` has been overridden to expect a tuple(perm, origin)
351 `__getitem__` will return only the perm
351 `__getitem__` will return only the perm
352 `.perm_origin_stack` will return the stack of (perm, origin) set per key
352 `.perm_origin_stack` will return the stack of (perm, origin) set per key
353
353
354 >>> perms = PermOriginDict()
354 >>> perms = PermOriginDict()
355 >>> perms['resource'] = 'read', 'default'
355 >>> perms['resource'] = 'read', 'default'
356 >>> perms['resource']
356 >>> perms['resource']
357 'read'
357 'read'
358 >>> perms['resource'] = 'write', 'admin'
358 >>> perms['resource'] = 'write', 'admin'
359 >>> perms['resource']
359 >>> perms['resource']
360 'write'
360 'write'
361 >>> perms.perm_origin_stack
361 >>> perms.perm_origin_stack
362 {'resource': [('read', 'default'), ('write', 'admin')]}
362 {'resource': [('read', 'default'), ('write', 'admin')]}
363 """
363 """
364
364
365 def __init__(self, *args, **kw):
365 def __init__(self, *args, **kw):
366 dict.__init__(self, *args, **kw)
366 dict.__init__(self, *args, **kw)
367 self.perm_origin_stack = collections.OrderedDict()
367 self.perm_origin_stack = collections.OrderedDict()
368
368
369 def __setitem__(self, key, (perm, origin)):
369 def __setitem__(self, key, (perm, origin)):
370 self.perm_origin_stack.setdefault(key, []).append(
370 self.perm_origin_stack.setdefault(key, []).append(
371 (perm, origin))
371 (perm, origin))
372 dict.__setitem__(self, key, perm)
372 dict.__setitem__(self, key, perm)
373
373
374
374
375 class BranchPermOriginDict(PermOriginDict):
375 class BranchPermOriginDict(PermOriginDict):
376 """
376 """
377 Dedicated branch permissions dict, with tracking of patterns and origins.
377 Dedicated branch permissions dict, with tracking of patterns and origins.
378
378
379 >>> perms = BranchPermOriginDict()
379 >>> perms = BranchPermOriginDict()
380 >>> perms['resource'] = '*pattern', 'read', 'default'
380 >>> perms['resource'] = '*pattern', 'read', 'default'
381 >>> perms['resource']
381 >>> perms['resource']
382 {'*pattern': 'read'}
382 {'*pattern': 'read'}
383 >>> perms['resource'] = '*pattern', 'write', 'admin'
383 >>> perms['resource'] = '*pattern', 'write', 'admin'
384 >>> perms['resource']
384 >>> perms['resource']
385 {'*pattern': 'write'}
385 {'*pattern': 'write'}
386 >>> perms.perm_origin_stack
386 >>> perms.perm_origin_stack
387 {'resource': {'*pattern': [('read', 'default'), ('write', 'admin')]}}
387 {'resource': {'*pattern': [('read', 'default'), ('write', 'admin')]}}
388 """
388 """
389 def __setitem__(self, key, (pattern, perm, origin)):
389 def __setitem__(self, key, (pattern, perm, origin)):
390
390
391 self.perm_origin_stack.setdefault(key, {}) \
391 self.perm_origin_stack.setdefault(key, {}) \
392 .setdefault(pattern, []).append((perm, origin))
392 .setdefault(pattern, []).append((perm, origin))
393
393
394 if key in self:
394 if key in self:
395 self[key].__setitem__(pattern, perm)
395 self[key].__setitem__(pattern, perm)
396 else:
396 else:
397 patterns = collections.OrderedDict()
397 patterns = collections.OrderedDict()
398 patterns[pattern] = perm
398 patterns[pattern] = perm
399 dict.__setitem__(self, key, patterns)
399 dict.__setitem__(self, key, patterns)
400
400
401
401
402 class PermissionCalculator(object):
402 class PermissionCalculator(object):
403
403
404 def __init__(
404 def __init__(
405 self, user_id, scope, user_is_admin,
405 self, user_id, scope, user_is_admin,
406 user_inherit_default_permissions, explicit, algo,
406 user_inherit_default_permissions, explicit, algo,
407 calculate_super_admin_as_user=False):
407 calculate_super_admin_as_user=False):
408
408
409 self.user_id = user_id
409 self.user_id = user_id
410 self.user_is_admin = user_is_admin
410 self.user_is_admin = user_is_admin
411 self.inherit_default_permissions = user_inherit_default_permissions
411 self.inherit_default_permissions = user_inherit_default_permissions
412 self.explicit = explicit
412 self.explicit = explicit
413 self.algo = algo
413 self.algo = algo
414 self.calculate_super_admin_as_user = calculate_super_admin_as_user
414 self.calculate_super_admin_as_user = calculate_super_admin_as_user
415
415
416 scope = scope or {}
416 scope = scope or {}
417 self.scope_repo_id = scope.get('repo_id')
417 self.scope_repo_id = scope.get('repo_id')
418 self.scope_repo_group_id = scope.get('repo_group_id')
418 self.scope_repo_group_id = scope.get('repo_group_id')
419 self.scope_user_group_id = scope.get('user_group_id')
419 self.scope_user_group_id = scope.get('user_group_id')
420
420
421 self.default_user_id = User.get_default_user(cache=True).user_id
421 self.default_user_id = User.get_default_user(cache=True).user_id
422
422
423 self.permissions_repositories = PermOriginDict()
423 self.permissions_repositories = PermOriginDict()
424 self.permissions_repository_groups = PermOriginDict()
424 self.permissions_repository_groups = PermOriginDict()
425 self.permissions_user_groups = PermOriginDict()
425 self.permissions_user_groups = PermOriginDict()
426 self.permissions_repository_branches = BranchPermOriginDict()
426 self.permissions_repository_branches = BranchPermOriginDict()
427 self.permissions_global = set()
427 self.permissions_global = set()
428
428
429 self.default_repo_perms = Permission.get_default_repo_perms(
429 self.default_repo_perms = Permission.get_default_repo_perms(
430 self.default_user_id, self.scope_repo_id)
430 self.default_user_id, self.scope_repo_id)
431 self.default_repo_groups_perms = Permission.get_default_group_perms(
431 self.default_repo_groups_perms = Permission.get_default_group_perms(
432 self.default_user_id, self.scope_repo_group_id)
432 self.default_user_id, self.scope_repo_group_id)
433 self.default_user_group_perms = \
433 self.default_user_group_perms = \
434 Permission.get_default_user_group_perms(
434 Permission.get_default_user_group_perms(
435 self.default_user_id, self.scope_user_group_id)
435 self.default_user_id, self.scope_user_group_id)
436
436
437 # default branch perms
437 # default branch perms
438 self.default_branch_repo_perms = \
438 self.default_branch_repo_perms = \
439 Permission.get_default_repo_branch_perms(
439 Permission.get_default_repo_branch_perms(
440 self.default_user_id, self.scope_repo_id)
440 self.default_user_id, self.scope_repo_id)
441
441
442 def calculate(self):
442 def calculate(self):
443 if self.user_is_admin and not self.calculate_super_admin_as_user:
443 if self.user_is_admin and not self.calculate_super_admin_as_user:
444 return self._calculate_admin_permissions()
444 return self._calculate_admin_permissions()
445
445
446 self._calculate_global_default_permissions()
446 self._calculate_global_default_permissions()
447 self._calculate_global_permissions()
447 self._calculate_global_permissions()
448 self._calculate_default_permissions()
448 self._calculate_default_permissions()
449 self._calculate_repository_permissions()
449 self._calculate_repository_permissions()
450 self._calculate_repository_branch_permissions()
450 self._calculate_repository_branch_permissions()
451 self._calculate_repository_group_permissions()
451 self._calculate_repository_group_permissions()
452 self._calculate_user_group_permissions()
452 self._calculate_user_group_permissions()
453 return self._permission_structure()
453 return self._permission_structure()
454
454
455 def _calculate_admin_permissions(self):
455 def _calculate_admin_permissions(self):
456 """
456 """
457 admin user have all default rights for repositories
457 admin user have all default rights for repositories
458 and groups set to admin
458 and groups set to admin
459 """
459 """
460 self.permissions_global.add('hg.admin')
460 self.permissions_global.add('hg.admin')
461 self.permissions_global.add('hg.create.write_on_repogroup.true')
461 self.permissions_global.add('hg.create.write_on_repogroup.true')
462
462
463 # repositories
463 # repositories
464 for perm in self.default_repo_perms:
464 for perm in self.default_repo_perms:
465 r_k = perm.UserRepoToPerm.repository.repo_name
465 r_k = perm.UserRepoToPerm.repository.repo_name
466 archived = perm.UserRepoToPerm.repository.archived
466 archived = perm.UserRepoToPerm.repository.archived
467 p = 'repository.admin'
467 p = 'repository.admin'
468 self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN
468 self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN
469 # special case for archived repositories, which we block still even for
469 # special case for archived repositories, which we block still even for
470 # super admins
470 # super admins
471 if archived:
471 if archived:
472 p = 'repository.read'
472 p = 'repository.read'
473 self.permissions_repositories[r_k] = p, PermOrigin.ARCHIVED
473 self.permissions_repositories[r_k] = p, PermOrigin.ARCHIVED
474
474
475 # repository groups
475 # repository groups
476 for perm in self.default_repo_groups_perms:
476 for perm in self.default_repo_groups_perms:
477 rg_k = perm.UserRepoGroupToPerm.group.group_name
477 rg_k = perm.UserRepoGroupToPerm.group.group_name
478 p = 'group.admin'
478 p = 'group.admin'
479 self.permissions_repository_groups[rg_k] = p, PermOrigin.SUPER_ADMIN
479 self.permissions_repository_groups[rg_k] = p, PermOrigin.SUPER_ADMIN
480
480
481 # user groups
481 # user groups
482 for perm in self.default_user_group_perms:
482 for perm in self.default_user_group_perms:
483 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
483 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
484 p = 'usergroup.admin'
484 p = 'usergroup.admin'
485 self.permissions_user_groups[u_k] = p, PermOrigin.SUPER_ADMIN
485 self.permissions_user_groups[u_k] = p, PermOrigin.SUPER_ADMIN
486
486
487 # branch permissions
487 # branch permissions
488 # since super-admin also can have custom rule permissions
488 # since super-admin also can have custom rule permissions
489 # we *always* need to calculate those inherited from default, and also explicit
489 # we *always* need to calculate those inherited from default, and also explicit
490 self._calculate_default_permissions_repository_branches(
490 self._calculate_default_permissions_repository_branches(
491 user_inherit_object_permissions=False)
491 user_inherit_object_permissions=False)
492 self._calculate_repository_branch_permissions()
492 self._calculate_repository_branch_permissions()
493
493
494 return self._permission_structure()
494 return self._permission_structure()
495
495
496 def _calculate_global_default_permissions(self):
496 def _calculate_global_default_permissions(self):
497 """
497 """
498 global permissions taken from the default user
498 global permissions taken from the default user
499 """
499 """
500 default_global_perms = UserToPerm.query()\
500 default_global_perms = UserToPerm.query()\
501 .filter(UserToPerm.user_id == self.default_user_id)\
501 .filter(UserToPerm.user_id == self.default_user_id)\
502 .options(joinedload(UserToPerm.permission))
502 .options(joinedload(UserToPerm.permission))
503
503
504 for perm in default_global_perms:
504 for perm in default_global_perms:
505 self.permissions_global.add(perm.permission.permission_name)
505 self.permissions_global.add(perm.permission.permission_name)
506
506
507 if self.user_is_admin:
507 if self.user_is_admin:
508 self.permissions_global.add('hg.admin')
508 self.permissions_global.add('hg.admin')
509 self.permissions_global.add('hg.create.write_on_repogroup.true')
509 self.permissions_global.add('hg.create.write_on_repogroup.true')
510
510
511 def _calculate_global_permissions(self):
511 def _calculate_global_permissions(self):
512 """
512 """
513 Set global system permissions with user permissions or permissions
513 Set global system permissions with user permissions or permissions
514 taken from the user groups of the current user.
514 taken from the user groups of the current user.
515
515
516 The permissions include repo creating, repo group creating, forking
516 The permissions include repo creating, repo group creating, forking
517 etc.
517 etc.
518 """
518 """
519
519
520 # now we read the defined permissions and overwrite what we have set
520 # now we read the defined permissions and overwrite what we have set
521 # before those can be configured from groups or users explicitly.
521 # before those can be configured from groups or users explicitly.
522
522
523 # In case we want to extend this list we should make sure
523 # In case we want to extend this list we should make sure
524 # this is in sync with User.DEFAULT_USER_PERMISSIONS definitions
524 # this is in sync with User.DEFAULT_USER_PERMISSIONS definitions
525 _configurable = frozenset([
525 _configurable = frozenset([
526 'hg.fork.none', 'hg.fork.repository',
526 'hg.fork.none', 'hg.fork.repository',
527 'hg.create.none', 'hg.create.repository',
527 'hg.create.none', 'hg.create.repository',
528 'hg.usergroup.create.false', 'hg.usergroup.create.true',
528 'hg.usergroup.create.false', 'hg.usergroup.create.true',
529 'hg.repogroup.create.false', 'hg.repogroup.create.true',
529 'hg.repogroup.create.false', 'hg.repogroup.create.true',
530 'hg.create.write_on_repogroup.false', 'hg.create.write_on_repogroup.true',
530 'hg.create.write_on_repogroup.false', 'hg.create.write_on_repogroup.true',
531 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
531 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
532 ])
532 ])
533
533
534 # USER GROUPS comes first user group global permissions
534 # USER GROUPS comes first user group global permissions
535 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
535 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
536 .options(joinedload(UserGroupToPerm.permission))\
536 .options(joinedload(UserGroupToPerm.permission))\
537 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
537 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
538 UserGroupMember.users_group_id))\
538 UserGroupMember.users_group_id))\
539 .filter(UserGroupMember.user_id == self.user_id)\
539 .filter(UserGroupMember.user_id == self.user_id)\
540 .order_by(UserGroupToPerm.users_group_id)\
540 .order_by(UserGroupToPerm.users_group_id)\
541 .all()
541 .all()
542
542
543 # need to group here by groups since user can be in more than
543 # need to group here by groups since user can be in more than
544 # one group, so we get all groups
544 # one group, so we get all groups
545 _explicit_grouped_perms = [
545 _explicit_grouped_perms = [
546 [x, list(y)] for x, y in
546 [x, list(y)] for x, y in
547 itertools.groupby(user_perms_from_users_groups,
547 itertools.groupby(user_perms_from_users_groups,
548 lambda _x: _x.users_group)]
548 lambda _x: _x.users_group)]
549
549
550 for gr, perms in _explicit_grouped_perms:
550 for gr, perms in _explicit_grouped_perms:
551 # since user can be in multiple groups iterate over them and
551 # since user can be in multiple groups iterate over them and
552 # select the lowest permissions first (more explicit)
552 # select the lowest permissions first (more explicit)
553 # TODO(marcink): do this^^
553 # TODO(marcink): do this^^
554
554
555 # group doesn't inherit default permissions so we actually set them
555 # group doesn't inherit default permissions so we actually set them
556 if not gr.inherit_default_permissions:
556 if not gr.inherit_default_permissions:
557 # NEED TO IGNORE all previously set configurable permissions
557 # NEED TO IGNORE all previously set configurable permissions
558 # and replace them with explicitly set from this user
558 # and replace them with explicitly set from this user
559 # group permissions
559 # group permissions
560 self.permissions_global = self.permissions_global.difference(
560 self.permissions_global = self.permissions_global.difference(
561 _configurable)
561 _configurable)
562 for perm in perms:
562 for perm in perms:
563 self.permissions_global.add(perm.permission.permission_name)
563 self.permissions_global.add(perm.permission.permission_name)
564
564
565 # user explicit global permissions
565 # user explicit global permissions
566 user_perms = Session().query(UserToPerm)\
566 user_perms = Session().query(UserToPerm)\
567 .options(joinedload(UserToPerm.permission))\
567 .options(joinedload(UserToPerm.permission))\
568 .filter(UserToPerm.user_id == self.user_id).all()
568 .filter(UserToPerm.user_id == self.user_id).all()
569
569
570 if not self.inherit_default_permissions:
570 if not self.inherit_default_permissions:
571 # NEED TO IGNORE all configurable permissions and
571 # NEED TO IGNORE all configurable permissions and
572 # replace them with explicitly set from this user permissions
572 # replace them with explicitly set from this user permissions
573 self.permissions_global = self.permissions_global.difference(
573 self.permissions_global = self.permissions_global.difference(
574 _configurable)
574 _configurable)
575 for perm in user_perms:
575 for perm in user_perms:
576 self.permissions_global.add(perm.permission.permission_name)
576 self.permissions_global.add(perm.permission.permission_name)
577
577
578 def _calculate_default_permissions_repositories(self, user_inherit_object_permissions):
578 def _calculate_default_permissions_repositories(self, user_inherit_object_permissions):
579 for perm in self.default_repo_perms:
579 for perm in self.default_repo_perms:
580 r_k = perm.UserRepoToPerm.repository.repo_name
580 r_k = perm.UserRepoToPerm.repository.repo_name
581 archived = perm.UserRepoToPerm.repository.archived
581 archived = perm.UserRepoToPerm.repository.archived
582 p = perm.Permission.permission_name
582 p = perm.Permission.permission_name
583 o = PermOrigin.REPO_DEFAULT
583 o = PermOrigin.REPO_DEFAULT
584 self.permissions_repositories[r_k] = p, o
584 self.permissions_repositories[r_k] = p, o
585
585
586 # if we decide this user isn't inheriting permissions from
586 # if we decide this user isn't inheriting permissions from
587 # default user we set him to .none so only explicit
587 # default user we set him to .none so only explicit
588 # permissions work
588 # permissions work
589 if not user_inherit_object_permissions:
589 if not user_inherit_object_permissions:
590 p = 'repository.none'
590 p = 'repository.none'
591 o = PermOrigin.REPO_DEFAULT_NO_INHERIT
591 o = PermOrigin.REPO_DEFAULT_NO_INHERIT
592 self.permissions_repositories[r_k] = p, o
592 self.permissions_repositories[r_k] = p, o
593
593
594 if perm.Repository.private and not (
594 if perm.Repository.private and not (
595 perm.Repository.user_id == self.user_id):
595 perm.Repository.user_id == self.user_id):
596 # disable defaults for private repos,
596 # disable defaults for private repos,
597 p = 'repository.none'
597 p = 'repository.none'
598 o = PermOrigin.REPO_PRIVATE
598 o = PermOrigin.REPO_PRIVATE
599 self.permissions_repositories[r_k] = p, o
599 self.permissions_repositories[r_k] = p, o
600
600
601 elif perm.Repository.user_id == self.user_id:
601 elif perm.Repository.user_id == self.user_id:
602 # set admin if owner
602 # set admin if owner
603 p = 'repository.admin'
603 p = 'repository.admin'
604 o = PermOrigin.REPO_OWNER
604 o = PermOrigin.REPO_OWNER
605 self.permissions_repositories[r_k] = p, o
605 self.permissions_repositories[r_k] = p, o
606
606
607 if self.user_is_admin:
607 if self.user_is_admin:
608 p = 'repository.admin'
608 p = 'repository.admin'
609 o = PermOrigin.SUPER_ADMIN
609 o = PermOrigin.SUPER_ADMIN
610 self.permissions_repositories[r_k] = p, o
610 self.permissions_repositories[r_k] = p, o
611
611
612 # finally in case of archived repositories, we downgrade higher
612 # finally in case of archived repositories, we downgrade higher
613 # permissions to read
613 # permissions to read
614 if archived:
614 if archived:
615 current_perm = self.permissions_repositories[r_k]
615 current_perm = self.permissions_repositories[r_k]
616 if current_perm in ['repository.write', 'repository.admin']:
616 if current_perm in ['repository.write', 'repository.admin']:
617 p = 'repository.read'
617 p = 'repository.read'
618 o = PermOrigin.ARCHIVED
618 o = PermOrigin.ARCHIVED
619 self.permissions_repositories[r_k] = p, o
619 self.permissions_repositories[r_k] = p, o
620
620
621 def _calculate_default_permissions_repository_branches(self, user_inherit_object_permissions):
621 def _calculate_default_permissions_repository_branches(self, user_inherit_object_permissions):
622 for perm in self.default_branch_repo_perms:
622 for perm in self.default_branch_repo_perms:
623
623
624 r_k = perm.UserRepoToPerm.repository.repo_name
624 r_k = perm.UserRepoToPerm.repository.repo_name
625 p = perm.Permission.permission_name
625 p = perm.Permission.permission_name
626 pattern = perm.UserToRepoBranchPermission.branch_pattern
626 pattern = perm.UserToRepoBranchPermission.branch_pattern
627 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
627 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
628
628
629 if not self.explicit:
629 if not self.explicit:
630 cur_perm = self.permissions_repository_branches.get(r_k)
630 cur_perm = self.permissions_repository_branches.get(r_k)
631 if cur_perm:
631 if cur_perm:
632 cur_perm = cur_perm[pattern]
632 cur_perm = cur_perm[pattern]
633 cur_perm = cur_perm or 'branch.none'
633 cur_perm = cur_perm or 'branch.none'
634
634
635 p = self._choose_permission(p, cur_perm)
635 p = self._choose_permission(p, cur_perm)
636
636
637 # NOTE(marcink): register all pattern/perm instances in this
637 # NOTE(marcink): register all pattern/perm instances in this
638 # special dict that aggregates entries
638 # special dict that aggregates entries
639 self.permissions_repository_branches[r_k] = pattern, p, o
639 self.permissions_repository_branches[r_k] = pattern, p, o
640
640
641 def _calculate_default_permissions_repository_groups(self, user_inherit_object_permissions):
641 def _calculate_default_permissions_repository_groups(self, user_inherit_object_permissions):
642 for perm in self.default_repo_groups_perms:
642 for perm in self.default_repo_groups_perms:
643 rg_k = perm.UserRepoGroupToPerm.group.group_name
643 rg_k = perm.UserRepoGroupToPerm.group.group_name
644 p = perm.Permission.permission_name
644 p = perm.Permission.permission_name
645 o = PermOrigin.REPOGROUP_DEFAULT
645 o = PermOrigin.REPOGROUP_DEFAULT
646 self.permissions_repository_groups[rg_k] = p, o
646 self.permissions_repository_groups[rg_k] = p, o
647
647
648 # if we decide this user isn't inheriting permissions from default
648 # if we decide this user isn't inheriting permissions from default
649 # user we set him to .none so only explicit permissions work
649 # user we set him to .none so only explicit permissions work
650 if not user_inherit_object_permissions:
650 if not user_inherit_object_permissions:
651 p = 'group.none'
651 p = 'group.none'
652 o = PermOrigin.REPOGROUP_DEFAULT_NO_INHERIT
652 o = PermOrigin.REPOGROUP_DEFAULT_NO_INHERIT
653 self.permissions_repository_groups[rg_k] = p, o
653 self.permissions_repository_groups[rg_k] = p, o
654
654
655 if perm.RepoGroup.user_id == self.user_id:
655 if perm.RepoGroup.user_id == self.user_id:
656 # set admin if owner
656 # set admin if owner
657 p = 'group.admin'
657 p = 'group.admin'
658 o = PermOrigin.REPOGROUP_OWNER
658 o = PermOrigin.REPOGROUP_OWNER
659 self.permissions_repository_groups[rg_k] = p, o
659 self.permissions_repository_groups[rg_k] = p, o
660
660
661 if self.user_is_admin:
661 if self.user_is_admin:
662 p = 'group.admin'
662 p = 'group.admin'
663 o = PermOrigin.SUPER_ADMIN
663 o = PermOrigin.SUPER_ADMIN
664 self.permissions_repository_groups[rg_k] = p, o
664 self.permissions_repository_groups[rg_k] = p, o
665
665
666 def _calculate_default_permissions_user_groups(self, user_inherit_object_permissions):
666 def _calculate_default_permissions_user_groups(self, user_inherit_object_permissions):
667 for perm in self.default_user_group_perms:
667 for perm in self.default_user_group_perms:
668 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
668 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
669 p = perm.Permission.permission_name
669 p = perm.Permission.permission_name
670 o = PermOrigin.USERGROUP_DEFAULT
670 o = PermOrigin.USERGROUP_DEFAULT
671 self.permissions_user_groups[u_k] = p, o
671 self.permissions_user_groups[u_k] = p, o
672
672
673 # if we decide this user isn't inheriting permissions from default
673 # if we decide this user isn't inheriting permissions from default
674 # user we set him to .none so only explicit permissions work
674 # user we set him to .none so only explicit permissions work
675 if not user_inherit_object_permissions:
675 if not user_inherit_object_permissions:
676 p = 'usergroup.none'
676 p = 'usergroup.none'
677 o = PermOrigin.USERGROUP_DEFAULT_NO_INHERIT
677 o = PermOrigin.USERGROUP_DEFAULT_NO_INHERIT
678 self.permissions_user_groups[u_k] = p, o
678 self.permissions_user_groups[u_k] = p, o
679
679
680 if perm.UserGroup.user_id == self.user_id:
680 if perm.UserGroup.user_id == self.user_id:
681 # set admin if owner
681 # set admin if owner
682 p = 'usergroup.admin'
682 p = 'usergroup.admin'
683 o = PermOrigin.USERGROUP_OWNER
683 o = PermOrigin.USERGROUP_OWNER
684 self.permissions_user_groups[u_k] = p, o
684 self.permissions_user_groups[u_k] = p, o
685
685
686 if self.user_is_admin:
686 if self.user_is_admin:
687 p = 'usergroup.admin'
687 p = 'usergroup.admin'
688 o = PermOrigin.SUPER_ADMIN
688 o = PermOrigin.SUPER_ADMIN
689 self.permissions_user_groups[u_k] = p, o
689 self.permissions_user_groups[u_k] = p, o
690
690
691 def _calculate_default_permissions(self):
691 def _calculate_default_permissions(self):
692 """
692 """
693 Set default user permissions for repositories, repository branches,
693 Set default user permissions for repositories, repository branches,
694 repository groups, user groups taken from the default user.
694 repository groups, user groups taken from the default user.
695
695
696 Calculate inheritance of object permissions based on what we have now
696 Calculate inheritance of object permissions based on what we have now
697 in GLOBAL permissions. We check if .false is in GLOBAL since this is
697 in GLOBAL permissions. We check if .false is in GLOBAL since this is
698 explicitly set. Inherit is the opposite of .false being there.
698 explicitly set. Inherit is the opposite of .false being there.
699
699
700 .. note::
700 .. note::
701
701
702 the syntax is little bit odd but what we need to check here is
702 the syntax is little bit odd but what we need to check here is
703 the opposite of .false permission being in the list so even for
703 the opposite of .false permission being in the list so even for
704 inconsistent state when both .true/.false is there
704 inconsistent state when both .true/.false is there
705 .false is more important
705 .false is more important
706
706
707 """
707 """
708 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
708 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
709 in self.permissions_global)
709 in self.permissions_global)
710
710
711 # default permissions inherited from `default` user permissions
711 # default permissions inherited from `default` user permissions
712 self._calculate_default_permissions_repositories(
712 self._calculate_default_permissions_repositories(
713 user_inherit_object_permissions)
713 user_inherit_object_permissions)
714
714
715 self._calculate_default_permissions_repository_branches(
715 self._calculate_default_permissions_repository_branches(
716 user_inherit_object_permissions)
716 user_inherit_object_permissions)
717
717
718 self._calculate_default_permissions_repository_groups(
718 self._calculate_default_permissions_repository_groups(
719 user_inherit_object_permissions)
719 user_inherit_object_permissions)
720
720
721 self._calculate_default_permissions_user_groups(
721 self._calculate_default_permissions_user_groups(
722 user_inherit_object_permissions)
722 user_inherit_object_permissions)
723
723
724 def _calculate_repository_permissions(self):
724 def _calculate_repository_permissions(self):
725 """
725 """
726 Repository permissions for the current user.
726 Repository permissions for the current user.
727
727
728 Check if the user is part of user groups for this repository and
728 Check if the user is part of user groups for this repository and
729 fill in the permission from it. `_choose_permission` decides of which
729 fill in the permission from it. `_choose_permission` decides of which
730 permission should be selected based on selected method.
730 permission should be selected based on selected method.
731 """
731 """
732
732
733 # user group for repositories permissions
733 # user group for repositories permissions
734 user_repo_perms_from_user_group = Permission\
734 user_repo_perms_from_user_group = Permission\
735 .get_default_repo_perms_from_user_group(
735 .get_default_repo_perms_from_user_group(
736 self.user_id, self.scope_repo_id)
736 self.user_id, self.scope_repo_id)
737
737
738 multiple_counter = collections.defaultdict(int)
738 multiple_counter = collections.defaultdict(int)
739 for perm in user_repo_perms_from_user_group:
739 for perm in user_repo_perms_from_user_group:
740 r_k = perm.UserGroupRepoToPerm.repository.repo_name
740 r_k = perm.UserGroupRepoToPerm.repository.repo_name
741 multiple_counter[r_k] += 1
741 multiple_counter[r_k] += 1
742 p = perm.Permission.permission_name
742 p = perm.Permission.permission_name
743 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
743 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
744 .users_group.users_group_name
744 .users_group.users_group_name
745
745
746 if multiple_counter[r_k] > 1:
746 if multiple_counter[r_k] > 1:
747 cur_perm = self.permissions_repositories[r_k]
747 cur_perm = self.permissions_repositories[r_k]
748 p = self._choose_permission(p, cur_perm)
748 p = self._choose_permission(p, cur_perm)
749
749
750 self.permissions_repositories[r_k] = p, o
750 self.permissions_repositories[r_k] = p, o
751
751
752 if perm.Repository.user_id == self.user_id:
752 if perm.Repository.user_id == self.user_id:
753 # set admin if owner
753 # set admin if owner
754 p = 'repository.admin'
754 p = 'repository.admin'
755 o = PermOrigin.REPO_OWNER
755 o = PermOrigin.REPO_OWNER
756 self.permissions_repositories[r_k] = p, o
756 self.permissions_repositories[r_k] = p, o
757
757
758 if self.user_is_admin:
758 if self.user_is_admin:
759 p = 'repository.admin'
759 p = 'repository.admin'
760 o = PermOrigin.SUPER_ADMIN
760 o = PermOrigin.SUPER_ADMIN
761 self.permissions_repositories[r_k] = p, o
761 self.permissions_repositories[r_k] = p, o
762
762
763 # user explicit permissions for repositories, overrides any specified
763 # user explicit permissions for repositories, overrides any specified
764 # by the group permission
764 # by the group permission
765 user_repo_perms = Permission.get_default_repo_perms(
765 user_repo_perms = Permission.get_default_repo_perms(
766 self.user_id, self.scope_repo_id)
766 self.user_id, self.scope_repo_id)
767 for perm in user_repo_perms:
767 for perm in user_repo_perms:
768 r_k = perm.UserRepoToPerm.repository.repo_name
768 r_k = perm.UserRepoToPerm.repository.repo_name
769 p = perm.Permission.permission_name
769 p = perm.Permission.permission_name
770 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
770 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
771
771
772 if not self.explicit:
772 if not self.explicit:
773 cur_perm = self.permissions_repositories.get(
773 cur_perm = self.permissions_repositories.get(
774 r_k, 'repository.none')
774 r_k, 'repository.none')
775 p = self._choose_permission(p, cur_perm)
775 p = self._choose_permission(p, cur_perm)
776
776
777 self.permissions_repositories[r_k] = p, o
777 self.permissions_repositories[r_k] = p, o
778
778
779 if perm.Repository.user_id == self.user_id:
779 if perm.Repository.user_id == self.user_id:
780 # set admin if owner
780 # set admin if owner
781 p = 'repository.admin'
781 p = 'repository.admin'
782 o = PermOrigin.REPO_OWNER
782 o = PermOrigin.REPO_OWNER
783 self.permissions_repositories[r_k] = p, o
783 self.permissions_repositories[r_k] = p, o
784
784
785 if self.user_is_admin:
785 if self.user_is_admin:
786 p = 'repository.admin'
786 p = 'repository.admin'
787 o = PermOrigin.SUPER_ADMIN
787 o = PermOrigin.SUPER_ADMIN
788 self.permissions_repositories[r_k] = p, o
788 self.permissions_repositories[r_k] = p, o
789
789
790 def _calculate_repository_branch_permissions(self):
790 def _calculate_repository_branch_permissions(self):
791 # user group for repositories permissions
791 # user group for repositories permissions
792 user_repo_branch_perms_from_user_group = Permission\
792 user_repo_branch_perms_from_user_group = Permission\
793 .get_default_repo_branch_perms_from_user_group(
793 .get_default_repo_branch_perms_from_user_group(
794 self.user_id, self.scope_repo_id)
794 self.user_id, self.scope_repo_id)
795
795
796 multiple_counter = collections.defaultdict(int)
796 multiple_counter = collections.defaultdict(int)
797 for perm in user_repo_branch_perms_from_user_group:
797 for perm in user_repo_branch_perms_from_user_group:
798 r_k = perm.UserGroupRepoToPerm.repository.repo_name
798 r_k = perm.UserGroupRepoToPerm.repository.repo_name
799 p = perm.Permission.permission_name
799 p = perm.Permission.permission_name
800 pattern = perm.UserGroupToRepoBranchPermission.branch_pattern
800 pattern = perm.UserGroupToRepoBranchPermission.branch_pattern
801 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
801 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
802 .users_group.users_group_name
802 .users_group.users_group_name
803
803
804 multiple_counter[r_k] += 1
804 multiple_counter[r_k] += 1
805 if multiple_counter[r_k] > 1:
805 if multiple_counter[r_k] > 1:
806 cur_perm = self.permissions_repository_branches[r_k][pattern]
806 cur_perm = self.permissions_repository_branches[r_k][pattern]
807 p = self._choose_permission(p, cur_perm)
807 p = self._choose_permission(p, cur_perm)
808
808
809 self.permissions_repository_branches[r_k] = pattern, p, o
809 self.permissions_repository_branches[r_k] = pattern, p, o
810
810
811 # user explicit branch permissions for repositories, overrides
811 # user explicit branch permissions for repositories, overrides
812 # any specified by the group permission
812 # any specified by the group permission
813 user_repo_branch_perms = Permission.get_default_repo_branch_perms(
813 user_repo_branch_perms = Permission.get_default_repo_branch_perms(
814 self.user_id, self.scope_repo_id)
814 self.user_id, self.scope_repo_id)
815
815
816 for perm in user_repo_branch_perms:
816 for perm in user_repo_branch_perms:
817
817
818 r_k = perm.UserRepoToPerm.repository.repo_name
818 r_k = perm.UserRepoToPerm.repository.repo_name
819 p = perm.Permission.permission_name
819 p = perm.Permission.permission_name
820 pattern = perm.UserToRepoBranchPermission.branch_pattern
820 pattern = perm.UserToRepoBranchPermission.branch_pattern
821 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
821 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
822
822
823 if not self.explicit:
823 if not self.explicit:
824 cur_perm = self.permissions_repository_branches.get(r_k)
824 cur_perm = self.permissions_repository_branches.get(r_k)
825 if cur_perm:
825 if cur_perm:
826 cur_perm = cur_perm[pattern]
826 cur_perm = cur_perm[pattern]
827 cur_perm = cur_perm or 'branch.none'
827 cur_perm = cur_perm or 'branch.none'
828 p = self._choose_permission(p, cur_perm)
828 p = self._choose_permission(p, cur_perm)
829
829
830 # NOTE(marcink): register all pattern/perm instances in this
830 # NOTE(marcink): register all pattern/perm instances in this
831 # special dict that aggregates entries
831 # special dict that aggregates entries
832 self.permissions_repository_branches[r_k] = pattern, p, o
832 self.permissions_repository_branches[r_k] = pattern, p, o
833
833
834 def _calculate_repository_group_permissions(self):
834 def _calculate_repository_group_permissions(self):
835 """
835 """
836 Repository group permissions for the current user.
836 Repository group permissions for the current user.
837
837
838 Check if the user is part of user groups for repository groups and
838 Check if the user is part of user groups for repository groups and
839 fill in the permissions from it. `_choose_permission` decides of which
839 fill in the permissions from it. `_choose_permission` decides of which
840 permission should be selected based on selected method.
840 permission should be selected based on selected method.
841 """
841 """
842 # user group for repo groups permissions
842 # user group for repo groups permissions
843 user_repo_group_perms_from_user_group = Permission\
843 user_repo_group_perms_from_user_group = Permission\
844 .get_default_group_perms_from_user_group(
844 .get_default_group_perms_from_user_group(
845 self.user_id, self.scope_repo_group_id)
845 self.user_id, self.scope_repo_group_id)
846
846
847 multiple_counter = collections.defaultdict(int)
847 multiple_counter = collections.defaultdict(int)
848 for perm in user_repo_group_perms_from_user_group:
848 for perm in user_repo_group_perms_from_user_group:
849 rg_k = perm.UserGroupRepoGroupToPerm.group.group_name
849 rg_k = perm.UserGroupRepoGroupToPerm.group.group_name
850 multiple_counter[rg_k] += 1
850 multiple_counter[rg_k] += 1
851 o = PermOrigin.REPOGROUP_USERGROUP % perm.UserGroupRepoGroupToPerm\
851 o = PermOrigin.REPOGROUP_USERGROUP % perm.UserGroupRepoGroupToPerm\
852 .users_group.users_group_name
852 .users_group.users_group_name
853 p = perm.Permission.permission_name
853 p = perm.Permission.permission_name
854
854
855 if multiple_counter[rg_k] > 1:
855 if multiple_counter[rg_k] > 1:
856 cur_perm = self.permissions_repository_groups[rg_k]
856 cur_perm = self.permissions_repository_groups[rg_k]
857 p = self._choose_permission(p, cur_perm)
857 p = self._choose_permission(p, cur_perm)
858 self.permissions_repository_groups[rg_k] = p, o
858 self.permissions_repository_groups[rg_k] = p, o
859
859
860 if perm.RepoGroup.user_id == self.user_id:
860 if perm.RepoGroup.user_id == self.user_id:
861 # set admin if owner, even for member of other user group
861 # set admin if owner, even for member of other user group
862 p = 'group.admin'
862 p = 'group.admin'
863 o = PermOrigin.REPOGROUP_OWNER
863 o = PermOrigin.REPOGROUP_OWNER
864 self.permissions_repository_groups[rg_k] = p, o
864 self.permissions_repository_groups[rg_k] = p, o
865
865
866 if self.user_is_admin:
866 if self.user_is_admin:
867 p = 'group.admin'
867 p = 'group.admin'
868 o = PermOrigin.SUPER_ADMIN
868 o = PermOrigin.SUPER_ADMIN
869 self.permissions_repository_groups[rg_k] = p, o
869 self.permissions_repository_groups[rg_k] = p, o
870
870
871 # user explicit permissions for repository groups
871 # user explicit permissions for repository groups
872 user_repo_groups_perms = Permission.get_default_group_perms(
872 user_repo_groups_perms = Permission.get_default_group_perms(
873 self.user_id, self.scope_repo_group_id)
873 self.user_id, self.scope_repo_group_id)
874 for perm in user_repo_groups_perms:
874 for perm in user_repo_groups_perms:
875 rg_k = perm.UserRepoGroupToPerm.group.group_name
875 rg_k = perm.UserRepoGroupToPerm.group.group_name
876 o = PermOrigin.REPOGROUP_USER % perm.UserRepoGroupToPerm\
876 o = PermOrigin.REPOGROUP_USER % perm.UserRepoGroupToPerm\
877 .user.username
877 .user.username
878 p = perm.Permission.permission_name
878 p = perm.Permission.permission_name
879
879
880 if not self.explicit:
880 if not self.explicit:
881 cur_perm = self.permissions_repository_groups.get(rg_k, 'group.none')
881 cur_perm = self.permissions_repository_groups.get(rg_k, 'group.none')
882 p = self._choose_permission(p, cur_perm)
882 p = self._choose_permission(p, cur_perm)
883
883
884 self.permissions_repository_groups[rg_k] = p, o
884 self.permissions_repository_groups[rg_k] = p, o
885
885
886 if perm.RepoGroup.user_id == self.user_id:
886 if perm.RepoGroup.user_id == self.user_id:
887 # set admin if owner
887 # set admin if owner
888 p = 'group.admin'
888 p = 'group.admin'
889 o = PermOrigin.REPOGROUP_OWNER
889 o = PermOrigin.REPOGROUP_OWNER
890 self.permissions_repository_groups[rg_k] = p, o
890 self.permissions_repository_groups[rg_k] = p, o
891
891
892 if self.user_is_admin:
892 if self.user_is_admin:
893 p = 'group.admin'
893 p = 'group.admin'
894 o = PermOrigin.SUPER_ADMIN
894 o = PermOrigin.SUPER_ADMIN
895 self.permissions_repository_groups[rg_k] = p, o
895 self.permissions_repository_groups[rg_k] = p, o
896
896
897 def _calculate_user_group_permissions(self):
897 def _calculate_user_group_permissions(self):
898 """
898 """
899 User group permissions for the current user.
899 User group permissions for the current user.
900 """
900 """
901 # user group for user group permissions
901 # user group for user group permissions
902 user_group_from_user_group = Permission\
902 user_group_from_user_group = Permission\
903 .get_default_user_group_perms_from_user_group(
903 .get_default_user_group_perms_from_user_group(
904 self.user_id, self.scope_user_group_id)
904 self.user_id, self.scope_user_group_id)
905
905
906 multiple_counter = collections.defaultdict(int)
906 multiple_counter = collections.defaultdict(int)
907 for perm in user_group_from_user_group:
907 for perm in user_group_from_user_group:
908 ug_k = perm.UserGroupUserGroupToPerm\
908 ug_k = perm.UserGroupUserGroupToPerm\
909 .target_user_group.users_group_name
909 .target_user_group.users_group_name
910 multiple_counter[ug_k] += 1
910 multiple_counter[ug_k] += 1
911 o = PermOrigin.USERGROUP_USERGROUP % perm.UserGroupUserGroupToPerm\
911 o = PermOrigin.USERGROUP_USERGROUP % perm.UserGroupUserGroupToPerm\
912 .user_group.users_group_name
912 .user_group.users_group_name
913 p = perm.Permission.permission_name
913 p = perm.Permission.permission_name
914
914
915 if multiple_counter[ug_k] > 1:
915 if multiple_counter[ug_k] > 1:
916 cur_perm = self.permissions_user_groups[ug_k]
916 cur_perm = self.permissions_user_groups[ug_k]
917 p = self._choose_permission(p, cur_perm)
917 p = self._choose_permission(p, cur_perm)
918
918
919 self.permissions_user_groups[ug_k] = p, o
919 self.permissions_user_groups[ug_k] = p, o
920
920
921 if perm.UserGroup.user_id == self.user_id:
921 if perm.UserGroup.user_id == self.user_id:
922 # set admin if owner, even for member of other user group
922 # set admin if owner, even for member of other user group
923 p = 'usergroup.admin'
923 p = 'usergroup.admin'
924 o = PermOrigin.USERGROUP_OWNER
924 o = PermOrigin.USERGROUP_OWNER
925 self.permissions_user_groups[ug_k] = p, o
925 self.permissions_user_groups[ug_k] = p, o
926
926
927 if self.user_is_admin:
927 if self.user_is_admin:
928 p = 'usergroup.admin'
928 p = 'usergroup.admin'
929 o = PermOrigin.SUPER_ADMIN
929 o = PermOrigin.SUPER_ADMIN
930 self.permissions_user_groups[ug_k] = p, o
930 self.permissions_user_groups[ug_k] = p, o
931
931
932 # user explicit permission for user groups
932 # user explicit permission for user groups
933 user_user_groups_perms = Permission.get_default_user_group_perms(
933 user_user_groups_perms = Permission.get_default_user_group_perms(
934 self.user_id, self.scope_user_group_id)
934 self.user_id, self.scope_user_group_id)
935 for perm in user_user_groups_perms:
935 for perm in user_user_groups_perms:
936 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
936 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
937 o = PermOrigin.USERGROUP_USER % perm.UserUserGroupToPerm\
937 o = PermOrigin.USERGROUP_USER % perm.UserUserGroupToPerm\
938 .user.username
938 .user.username
939 p = perm.Permission.permission_name
939 p = perm.Permission.permission_name
940
940
941 if not self.explicit:
941 if not self.explicit:
942 cur_perm = self.permissions_user_groups.get(ug_k, 'usergroup.none')
942 cur_perm = self.permissions_user_groups.get(ug_k, 'usergroup.none')
943 p = self._choose_permission(p, cur_perm)
943 p = self._choose_permission(p, cur_perm)
944
944
945 self.permissions_user_groups[ug_k] = p, o
945 self.permissions_user_groups[ug_k] = p, o
946
946
947 if perm.UserGroup.user_id == self.user_id:
947 if perm.UserGroup.user_id == self.user_id:
948 # set admin if owner
948 # set admin if owner
949 p = 'usergroup.admin'
949 p = 'usergroup.admin'
950 o = PermOrigin.USERGROUP_OWNER
950 o = PermOrigin.USERGROUP_OWNER
951 self.permissions_user_groups[ug_k] = p, o
951 self.permissions_user_groups[ug_k] = p, o
952
952
953 if self.user_is_admin:
953 if self.user_is_admin:
954 p = 'usergroup.admin'
954 p = 'usergroup.admin'
955 o = PermOrigin.SUPER_ADMIN
955 o = PermOrigin.SUPER_ADMIN
956 self.permissions_user_groups[ug_k] = p, o
956 self.permissions_user_groups[ug_k] = p, o
957
957
958 def _choose_permission(self, new_perm, cur_perm):
958 def _choose_permission(self, new_perm, cur_perm):
959 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
959 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
960 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
960 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
961 if self.algo == 'higherwin':
961 if self.algo == 'higherwin':
962 if new_perm_val > cur_perm_val:
962 if new_perm_val > cur_perm_val:
963 return new_perm
963 return new_perm
964 return cur_perm
964 return cur_perm
965 elif self.algo == 'lowerwin':
965 elif self.algo == 'lowerwin':
966 if new_perm_val < cur_perm_val:
966 if new_perm_val < cur_perm_val:
967 return new_perm
967 return new_perm
968 return cur_perm
968 return cur_perm
969
969
970 def _permission_structure(self):
970 def _permission_structure(self):
971 return {
971 return {
972 'global': self.permissions_global,
972 'global': self.permissions_global,
973 'repositories': self.permissions_repositories,
973 'repositories': self.permissions_repositories,
974 'repository_branches': self.permissions_repository_branches,
974 'repository_branches': self.permissions_repository_branches,
975 'repositories_groups': self.permissions_repository_groups,
975 'repositories_groups': self.permissions_repository_groups,
976 'user_groups': self.permissions_user_groups,
976 'user_groups': self.permissions_user_groups,
977 }
977 }
978
978
979
979
980 def allowed_auth_token_access(view_name, auth_token, whitelist=None):
980 def allowed_auth_token_access(view_name, auth_token, whitelist=None):
981 """
981 """
982 Check if given controller_name is in whitelist of auth token access
982 Check if given controller_name is in whitelist of auth token access
983 """
983 """
984 if not whitelist:
984 if not whitelist:
985 from rhodecode import CONFIG
985 from rhodecode import CONFIG
986 whitelist = aslist(
986 whitelist = aslist(
987 CONFIG.get('api_access_controllers_whitelist'), sep=',')
987 CONFIG.get('api_access_controllers_whitelist'), sep=',')
988 # backward compat translation
988 # backward compat translation
989 compat = {
989 compat = {
990 # old controller, new VIEW
990 # old controller, new VIEW
991 'ChangesetController:*': 'RepoCommitsView:*',
991 'ChangesetController:*': 'RepoCommitsView:*',
992 'ChangesetController:changeset_patch': 'RepoCommitsView:repo_commit_patch',
992 'ChangesetController:changeset_patch': 'RepoCommitsView:repo_commit_patch',
993 'ChangesetController:changeset_raw': 'RepoCommitsView:repo_commit_raw',
993 'ChangesetController:changeset_raw': 'RepoCommitsView:repo_commit_raw',
994 'FilesController:raw': 'RepoCommitsView:repo_commit_raw',
994 'FilesController:raw': 'RepoCommitsView:repo_commit_raw',
995 'FilesController:archivefile': 'RepoFilesView:repo_archivefile',
995 'FilesController:archivefile': 'RepoFilesView:repo_archivefile',
996 'GistsController:*': 'GistView:*',
996 'GistsController:*': 'GistView:*',
997 }
997 }
998
998
999 log.debug(
999 log.debug(
1000 'Allowed views for AUTH TOKEN access: %s', whitelist)
1000 'Allowed views for AUTH TOKEN access: %s', whitelist)
1001 auth_token_access_valid = False
1001 auth_token_access_valid = False
1002
1002
1003 for entry in whitelist:
1003 for entry in whitelist:
1004 token_match = True
1004 token_match = True
1005 if entry in compat:
1005 if entry in compat:
1006 # translate from old Controllers to Pyramid Views
1006 # translate from old Controllers to Pyramid Views
1007 entry = compat[entry]
1007 entry = compat[entry]
1008
1008
1009 if '@' in entry:
1009 if '@' in entry:
1010 # specific AuthToken
1010 # specific AuthToken
1011 entry, allowed_token = entry.split('@', 1)
1011 entry, allowed_token = entry.split('@', 1)
1012 token_match = auth_token == allowed_token
1012 token_match = auth_token == allowed_token
1013
1013
1014 if fnmatch.fnmatch(view_name, entry) and token_match:
1014 if fnmatch.fnmatch(view_name, entry) and token_match:
1015 auth_token_access_valid = True
1015 auth_token_access_valid = True
1016 break
1016 break
1017
1017
1018 if auth_token_access_valid:
1018 if auth_token_access_valid:
1019 log.debug('view: `%s` matches entry in whitelist: %s',
1019 log.debug('view: `%s` matches entry in whitelist: %s',
1020 view_name, whitelist)
1020 view_name, whitelist)
1021
1021
1022 else:
1022 else:
1023 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
1023 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
1024 % (view_name, whitelist))
1024 % (view_name, whitelist))
1025 if auth_token:
1025 if auth_token:
1026 # if we use auth token key and don't have access it's a warning
1026 # if we use auth token key and don't have access it's a warning
1027 log.warning(msg)
1027 log.warning(msg)
1028 else:
1028 else:
1029 log.debug(msg)
1029 log.debug(msg)
1030
1030
1031 return auth_token_access_valid
1031 return auth_token_access_valid
1032
1032
1033
1033
1034 class AuthUser(object):
1034 class AuthUser(object):
1035 """
1035 """
1036 A simple object that handles all attributes of user in RhodeCode
1036 A simple object that handles all attributes of user in RhodeCode
1037
1037
1038 It does lookup based on API key,given user, or user present in session
1038 It does lookup based on API key,given user, or user present in session
1039 Then it fills all required information for such user. It also checks if
1039 Then it fills all required information for such user. It also checks if
1040 anonymous access is enabled and if so, it returns default user as logged in
1040 anonymous access is enabled and if so, it returns default user as logged in
1041 """
1041 """
1042 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
1042 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
1043
1043
1044 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
1044 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
1045
1045
1046 self.user_id = user_id
1046 self.user_id = user_id
1047 self._api_key = api_key
1047 self._api_key = api_key
1048
1048
1049 self.api_key = None
1049 self.api_key = None
1050 self.username = username
1050 self.username = username
1051 self.ip_addr = ip_addr
1051 self.ip_addr = ip_addr
1052 self.name = ''
1052 self.name = ''
1053 self.lastname = ''
1053 self.lastname = ''
1054 self.first_name = ''
1054 self.first_name = ''
1055 self.last_name = ''
1055 self.last_name = ''
1056 self.email = ''
1056 self.email = ''
1057 self.is_authenticated = False
1057 self.is_authenticated = False
1058 self.admin = False
1058 self.admin = False
1059 self.inherit_default_permissions = False
1059 self.inherit_default_permissions = False
1060 self.password = ''
1060 self.password = ''
1061
1061
1062 self.anonymous_user = None # propagated on propagate_data
1062 self.anonymous_user = None # propagated on propagate_data
1063 self.propagate_data()
1063 self.propagate_data()
1064 self._instance = None
1064 self._instance = None
1065 self._permissions_scoped_cache = {} # used to bind scoped calculation
1065 self._permissions_scoped_cache = {} # used to bind scoped calculation
1066
1066
1067 @LazyProperty
1067 @LazyProperty
1068 def permissions(self):
1068 def permissions(self):
1069 return self.get_perms(user=self, cache=None)
1069 return self.get_perms(user=self, cache=None)
1070
1070
1071 @LazyProperty
1071 @LazyProperty
1072 def permissions_safe(self):
1072 def permissions_safe(self):
1073 """
1073 """
1074 Filtered permissions excluding not allowed repositories
1074 Filtered permissions excluding not allowed repositories
1075 """
1075 """
1076 perms = self.get_perms(user=self, cache=None)
1076 perms = self.get_perms(user=self, cache=None)
1077
1077
1078 perms['repositories'] = {
1078 perms['repositories'] = {
1079 k: v for k, v in perms['repositories'].items()
1079 k: v for k, v in perms['repositories'].items()
1080 if v != 'repository.none'}
1080 if v != 'repository.none'}
1081 perms['repositories_groups'] = {
1081 perms['repositories_groups'] = {
1082 k: v for k, v in perms['repositories_groups'].items()
1082 k: v for k, v in perms['repositories_groups'].items()
1083 if v != 'group.none'}
1083 if v != 'group.none'}
1084 perms['user_groups'] = {
1084 perms['user_groups'] = {
1085 k: v for k, v in perms['user_groups'].items()
1085 k: v for k, v in perms['user_groups'].items()
1086 if v != 'usergroup.none'}
1086 if v != 'usergroup.none'}
1087 perms['repository_branches'] = {
1087 perms['repository_branches'] = {
1088 k: v for k, v in perms['repository_branches'].iteritems()
1088 k: v for k, v in perms['repository_branches'].iteritems()
1089 if v != 'branch.none'}
1089 if v != 'branch.none'}
1090 return perms
1090 return perms
1091
1091
1092 @LazyProperty
1092 @LazyProperty
1093 def permissions_full_details(self):
1093 def permissions_full_details(self):
1094 return self.get_perms(
1094 return self.get_perms(
1095 user=self, cache=None, calculate_super_admin=True)
1095 user=self, cache=None, calculate_super_admin=True)
1096
1096
1097 def permissions_with_scope(self, scope):
1097 def permissions_with_scope(self, scope):
1098 """
1098 """
1099 Call the get_perms function with scoped data. The scope in that function
1099 Call the get_perms function with scoped data. The scope in that function
1100 narrows the SQL calls to the given ID of objects resulting in fetching
1100 narrows the SQL calls to the given ID of objects resulting in fetching
1101 Just particular permission we want to obtain. If scope is an empty dict
1101 Just particular permission we want to obtain. If scope is an empty dict
1102 then it basically narrows the scope to GLOBAL permissions only.
1102 then it basically narrows the scope to GLOBAL permissions only.
1103
1103
1104 :param scope: dict
1104 :param scope: dict
1105 """
1105 """
1106 if 'repo_name' in scope:
1106 if 'repo_name' in scope:
1107 obj = Repository.get_by_repo_name(scope['repo_name'])
1107 obj = Repository.get_by_repo_name(scope['repo_name'])
1108 if obj:
1108 if obj:
1109 scope['repo_id'] = obj.repo_id
1109 scope['repo_id'] = obj.repo_id
1110 _scope = collections.OrderedDict()
1110 _scope = collections.OrderedDict()
1111 _scope['repo_id'] = -1
1111 _scope['repo_id'] = -1
1112 _scope['user_group_id'] = -1
1112 _scope['user_group_id'] = -1
1113 _scope['repo_group_id'] = -1
1113 _scope['repo_group_id'] = -1
1114
1114
1115 for k in sorted(scope.keys()):
1115 for k in sorted(scope.keys()):
1116 _scope[k] = scope[k]
1116 _scope[k] = scope[k]
1117
1117
1118 # store in cache to mimic how the @LazyProperty works,
1118 # store in cache to mimic how the @LazyProperty works,
1119 # the difference here is that we use the unique key calculated
1119 # the difference here is that we use the unique key calculated
1120 # from params and values
1120 # from params and values
1121 return self.get_perms(user=self, cache=None, scope=_scope)
1121 return self.get_perms(user=self, cache=None, scope=_scope)
1122
1122
1123 def get_instance(self):
1123 def get_instance(self):
1124 return User.get(self.user_id)
1124 return User.get(self.user_id)
1125
1125
1126 def propagate_data(self):
1126 def propagate_data(self):
1127 """
1127 """
1128 Fills in user data and propagates values to this instance. Maps fetched
1128 Fills in user data and propagates values to this instance. Maps fetched
1129 user attributes to this class instance attributes
1129 user attributes to this class instance attributes
1130 """
1130 """
1131 log.debug('AuthUser: starting data propagation for new potential user')
1131 log.debug('AuthUser: starting data propagation for new potential user')
1132 user_model = UserModel()
1132 user_model = UserModel()
1133 anon_user = self.anonymous_user = User.get_default_user(cache=True)
1133 anon_user = self.anonymous_user = User.get_default_user(cache=True)
1134 is_user_loaded = False
1134 is_user_loaded = False
1135
1135
1136 # lookup by userid
1136 # lookup by userid
1137 if self.user_id is not None and self.user_id != anon_user.user_id:
1137 if self.user_id is not None and self.user_id != anon_user.user_id:
1138 log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id)
1138 log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id)
1139 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
1139 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
1140
1140
1141 # try go get user by api key
1141 # try go get user by api key
1142 elif self._api_key and self._api_key != anon_user.api_key:
1142 elif self._api_key and self._api_key != anon_user.api_key:
1143 log.debug('Trying Auth User lookup by API KEY: `%s`', self._api_key)
1143 log.debug('Trying Auth User lookup by API KEY: `%s`', self._api_key)
1144 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
1144 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
1145
1145
1146 # lookup by username
1146 # lookup by username
1147 elif self.username:
1147 elif self.username:
1148 log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username)
1148 log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username)
1149 is_user_loaded = user_model.fill_data(self, username=self.username)
1149 is_user_loaded = user_model.fill_data(self, username=self.username)
1150 else:
1150 else:
1151 log.debug('No data in %s that could been used to log in', self)
1151 log.debug('No data in %s that could been used to log in', self)
1152
1152
1153 if not is_user_loaded:
1153 if not is_user_loaded:
1154 log.debug(
1154 log.debug(
1155 'Failed to load user. Fallback to default user %s', anon_user)
1155 'Failed to load user. Fallback to default user %s', anon_user)
1156 # if we cannot authenticate user try anonymous
1156 # if we cannot authenticate user try anonymous
1157 if anon_user.active:
1157 if anon_user.active:
1158 log.debug('default user is active, using it as a session user')
1158 log.debug('default user is active, using it as a session user')
1159 user_model.fill_data(self, user_id=anon_user.user_id)
1159 user_model.fill_data(self, user_id=anon_user.user_id)
1160 # then we set this user is logged in
1160 # then we set this user is logged in
1161 self.is_authenticated = True
1161 self.is_authenticated = True
1162 else:
1162 else:
1163 log.debug('default user is NOT active')
1163 log.debug('default user is NOT active')
1164 # in case of disabled anonymous user we reset some of the
1164 # in case of disabled anonymous user we reset some of the
1165 # parameters so such user is "corrupted", skipping the fill_data
1165 # parameters so such user is "corrupted", skipping the fill_data
1166 for attr in ['user_id', 'username', 'admin', 'active']:
1166 for attr in ['user_id', 'username', 'admin', 'active']:
1167 setattr(self, attr, None)
1167 setattr(self, attr, None)
1168 self.is_authenticated = False
1168 self.is_authenticated = False
1169
1169
1170 if not self.username:
1170 if not self.username:
1171 self.username = 'None'
1171 self.username = 'None'
1172
1172
1173 log.debug('AuthUser: propagated user is now %s', self)
1173 log.debug('AuthUser: propagated user is now %s', self)
1174
1174
1175 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
1175 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
1176 calculate_super_admin=False, cache=None):
1176 calculate_super_admin=False, cache=None):
1177 """
1177 """
1178 Fills user permission attribute with permissions taken from database
1178 Fills user permission attribute with permissions taken from database
1179 works for permissions given for repositories, and for permissions that
1179 works for permissions given for repositories, and for permissions that
1180 are granted to groups
1180 are granted to groups
1181
1181
1182 :param user: instance of User object from database
1182 :param user: instance of User object from database
1183 :param explicit: In case there are permissions both for user and a group
1183 :param explicit: In case there are permissions both for user and a group
1184 that user is part of, explicit flag will defiine if user will
1184 that user is part of, explicit flag will defiine if user will
1185 explicitly override permissions from group, if it's False it will
1185 explicitly override permissions from group, if it's False it will
1186 make decision based on the algo
1186 make decision based on the algo
1187 :param algo: algorithm to decide what permission should be choose if
1187 :param algo: algorithm to decide what permission should be choose if
1188 it's multiple defined, eg user in two different groups. It also
1188 it's multiple defined, eg user in two different groups. It also
1189 decides if explicit flag is turned off how to specify the permission
1189 decides if explicit flag is turned off how to specify the permission
1190 for case when user is in a group + have defined separate permission
1190 for case when user is in a group + have defined separate permission
1191 :param calculate_super_admin: calculate permissions for super-admin in the
1191 :param calculate_super_admin: calculate permissions for super-admin in the
1192 same way as for regular user without speedups
1192 same way as for regular user without speedups
1193 :param cache: Use caching for calculation, None = let the cache backend decide
1193 :param cache: Use caching for calculation, None = let the cache backend decide
1194 """
1194 """
1195 user_id = user.user_id
1195 user_id = user.user_id
1196 user_is_admin = user.is_admin
1196 user_is_admin = user.is_admin
1197
1197
1198 # inheritance of global permissions like create repo/fork repo etc
1198 # inheritance of global permissions like create repo/fork repo etc
1199 user_inherit_default_permissions = user.inherit_default_permissions
1199 user_inherit_default_permissions = user.inherit_default_permissions
1200
1200
1201 cache_seconds = safe_int(
1201 cache_seconds = safe_int(
1202 rhodecode.CONFIG.get('rc_cache.cache_perms.expiration_time'))
1202 rhodecode.CONFIG.get('rc_cache.cache_perms.expiration_time'))
1203
1203
1204 if cache is None:
1204 if cache is None:
1205 # let the backend cache decide
1205 # let the backend cache decide
1206 cache_on = cache_seconds > 0
1206 cache_on = cache_seconds > 0
1207 else:
1207 else:
1208 cache_on = cache
1208 cache_on = cache
1209
1209
1210 log.debug(
1210 log.debug(
1211 'Computing PERMISSION tree for user %s scope `%s` '
1211 'Computing PERMISSION tree for user %s scope `%s` '
1212 'with caching: %s[TTL: %ss]', user, scope, cache_on, cache_seconds or 0)
1212 'with caching: %s[TTL: %ss]', user, scope, cache_on, cache_seconds or 0)
1213
1213
1214 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
1214 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
1215 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1215 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1216
1216
1217 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
1217 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
1218 condition=cache_on)
1218 condition=cache_on)
1219 def compute_perm_tree(cache_name,
1219 def compute_perm_tree(cache_name,
1220 user_id, scope, user_is_admin,user_inherit_default_permissions,
1220 user_id, scope, user_is_admin,user_inherit_default_permissions,
1221 explicit, algo, calculate_super_admin):
1221 explicit, algo, calculate_super_admin):
1222 return _cached_perms_data(
1222 return _cached_perms_data(
1223 user_id, scope, user_is_admin, user_inherit_default_permissions,
1223 user_id, scope, user_is_admin, user_inherit_default_permissions,
1224 explicit, algo, calculate_super_admin)
1224 explicit, algo, calculate_super_admin)
1225
1225
1226 start = time.time()
1226 start = time.time()
1227 result = compute_perm_tree(
1227 result = compute_perm_tree(
1228 'permissions', user_id, scope, user_is_admin,
1228 'permissions', user_id, scope, user_is_admin,
1229 user_inherit_default_permissions, explicit, algo,
1229 user_inherit_default_permissions, explicit, algo,
1230 calculate_super_admin)
1230 calculate_super_admin)
1231
1231
1232 result_repr = []
1232 result_repr = []
1233 for k in result:
1233 for k in result:
1234 result_repr.append((k, len(result[k])))
1234 result_repr.append((k, len(result[k])))
1235 total = time.time() - start
1235 total = time.time() - start
1236 log.debug('PERMISSION tree for user %s computed in %.3fs: %s',
1236 log.debug('PERMISSION tree for user %s computed in %.3fs: %s',
1237 user, total, result_repr)
1237 user, total, result_repr)
1238
1238
1239 return result
1239 return result
1240
1240
1241 @property
1241 @property
1242 def is_default(self):
1242 def is_default(self):
1243 return self.username == User.DEFAULT_USER
1243 return self.username == User.DEFAULT_USER
1244
1244
1245 @property
1245 @property
1246 def is_admin(self):
1246 def is_admin(self):
1247 return self.admin
1247 return self.admin
1248
1248
1249 @property
1249 @property
1250 def is_user_object(self):
1250 def is_user_object(self):
1251 return self.user_id is not None
1251 return self.user_id is not None
1252
1252
1253 @property
1253 @property
1254 def repositories_admin(self):
1254 def repositories_admin(self):
1255 """
1255 """
1256 Returns list of repositories you're an admin of
1256 Returns list of repositories you're an admin of
1257 """
1257 """
1258 return [
1258 return [
1259 x[0] for x in self.permissions['repositories'].items()
1259 x[0] for x in self.permissions['repositories'].items()
1260 if x[1] == 'repository.admin']
1260 if x[1] == 'repository.admin']
1261
1261
1262 @property
1262 @property
1263 def repository_groups_admin(self):
1263 def repository_groups_admin(self):
1264 """
1264 """
1265 Returns list of repository groups you're an admin of
1265 Returns list of repository groups you're an admin of
1266 """
1266 """
1267 return [
1267 return [
1268 x[0] for x in self.permissions['repositories_groups'].items()
1268 x[0] for x in self.permissions['repositories_groups'].items()
1269 if x[1] == 'group.admin']
1269 if x[1] == 'group.admin']
1270
1270
1271 @property
1271 @property
1272 def user_groups_admin(self):
1272 def user_groups_admin(self):
1273 """
1273 """
1274 Returns list of user groups you're an admin of
1274 Returns list of user groups you're an admin of
1275 """
1275 """
1276 return [
1276 return [
1277 x[0] for x in self.permissions['user_groups'].items()
1277 x[0] for x in self.permissions['user_groups'].items()
1278 if x[1] == 'usergroup.admin']
1278 if x[1] == 'usergroup.admin']
1279
1279
1280 def repo_acl_ids(self, perms=None, name_filter=None, cache=False):
1280 def repo_acl_ids(self, perms=None, name_filter=None, cache=False):
1281 """
1281 """
1282 Returns list of repository ids that user have access to based on given
1282 Returns list of repository ids that user have access to based on given
1283 perms. The cache flag should be only used in cases that are used for
1283 perms. The cache flag should be only used in cases that are used for
1284 display purposes, NOT IN ANY CASE for permission checks.
1284 display purposes, NOT IN ANY CASE for permission checks.
1285 """
1285 """
1286 from rhodecode.model.scm import RepoList
1286 from rhodecode.model.scm import RepoList
1287 if not perms:
1287 if not perms:
1288 perms = [
1288 perms = [
1289 'repository.read', 'repository.write', 'repository.admin']
1289 'repository.read', 'repository.write', 'repository.admin']
1290
1290
1291 def _cached_repo_acl(user_id, perm_def, _name_filter):
1291 def _cached_repo_acl(user_id, perm_def, _name_filter):
1292 qry = Repository.query()
1292 qry = Repository.query()
1293 if _name_filter:
1293 if _name_filter:
1294 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1294 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1295 qry = qry.filter(
1295 qry = qry.filter(
1296 Repository.repo_name.ilike(ilike_expression))
1296 Repository.repo_name.ilike(ilike_expression))
1297
1297
1298 return [x.repo_id for x in
1298 return [x.repo_id for x in
1299 RepoList(qry, perm_set=perm_def)]
1299 RepoList(qry, perm_set=perm_def)]
1300
1300
1301 return _cached_repo_acl(self.user_id, perms, name_filter)
1301 return _cached_repo_acl(self.user_id, perms, name_filter)
1302
1302
1303 def repo_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1303 def repo_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1304 """
1304 """
1305 Returns list of repository group ids that user have access to based on given
1305 Returns list of repository group ids that user have access to based on given
1306 perms. The cache flag should be only used in cases that are used for
1306 perms. The cache flag should be only used in cases that are used for
1307 display purposes, NOT IN ANY CASE for permission checks.
1307 display purposes, NOT IN ANY CASE for permission checks.
1308 """
1308 """
1309 from rhodecode.model.scm import RepoGroupList
1309 from rhodecode.model.scm import RepoGroupList
1310 if not perms:
1310 if not perms:
1311 perms = [
1311 perms = [
1312 'group.read', 'group.write', 'group.admin']
1312 'group.read', 'group.write', 'group.admin']
1313
1313
1314 def _cached_repo_group_acl(user_id, perm_def, _name_filter):
1314 def _cached_repo_group_acl(user_id, perm_def, _name_filter):
1315 qry = RepoGroup.query()
1315 qry = RepoGroup.query()
1316 if _name_filter:
1316 if _name_filter:
1317 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1317 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1318 qry = qry.filter(
1318 qry = qry.filter(
1319 RepoGroup.group_name.ilike(ilike_expression))
1319 RepoGroup.group_name.ilike(ilike_expression))
1320
1320
1321 return [x.group_id for x in
1321 return [x.group_id for x in
1322 RepoGroupList(qry, perm_set=perm_def)]
1322 RepoGroupList(qry, perm_set=perm_def)]
1323
1323
1324 return _cached_repo_group_acl(self.user_id, perms, name_filter)
1324 return _cached_repo_group_acl(self.user_id, perms, name_filter)
1325
1325
1326 def user_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1326 def user_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1327 """
1327 """
1328 Returns list of user group ids that user have access to based on given
1328 Returns list of user group ids that user have access to based on given
1329 perms. The cache flag should be only used in cases that are used for
1329 perms. The cache flag should be only used in cases that are used for
1330 display purposes, NOT IN ANY CASE for permission checks.
1330 display purposes, NOT IN ANY CASE for permission checks.
1331 """
1331 """
1332 from rhodecode.model.scm import UserGroupList
1332 from rhodecode.model.scm import UserGroupList
1333 if not perms:
1333 if not perms:
1334 perms = [
1334 perms = [
1335 'usergroup.read', 'usergroup.write', 'usergroup.admin']
1335 'usergroup.read', 'usergroup.write', 'usergroup.admin']
1336
1336
1337 def _cached_user_group_acl(user_id, perm_def, name_filter):
1337 def _cached_user_group_acl(user_id, perm_def, name_filter):
1338 qry = UserGroup.query()
1338 qry = UserGroup.query()
1339 if name_filter:
1339 if name_filter:
1340 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1340 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1341 qry = qry.filter(
1341 qry = qry.filter(
1342 UserGroup.users_group_name.ilike(ilike_expression))
1342 UserGroup.users_group_name.ilike(ilike_expression))
1343
1343
1344 return [x.users_group_id for x in
1344 return [x.users_group_id for x in
1345 UserGroupList(qry, perm_set=perm_def)]
1345 UserGroupList(qry, perm_set=perm_def)]
1346
1346
1347 return _cached_user_group_acl(self.user_id, perms, name_filter)
1347 return _cached_user_group_acl(self.user_id, perms, name_filter)
1348
1348
1349 @property
1349 @property
1350 def ip_allowed(self):
1350 def ip_allowed(self):
1351 """
1351 """
1352 Checks if ip_addr used in constructor is allowed from defined list of
1352 Checks if ip_addr used in constructor is allowed from defined list of
1353 allowed ip_addresses for user
1353 allowed ip_addresses for user
1354
1354
1355 :returns: boolean, True if ip is in allowed ip range
1355 :returns: boolean, True if ip is in allowed ip range
1356 """
1356 """
1357 # check IP
1357 # check IP
1358 inherit = self.inherit_default_permissions
1358 inherit = self.inherit_default_permissions
1359 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
1359 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
1360 inherit_from_default=inherit)
1360 inherit_from_default=inherit)
1361 @property
1361 @property
1362 def personal_repo_group(self):
1362 def personal_repo_group(self):
1363 return RepoGroup.get_user_personal_repo_group(self.user_id)
1363 return RepoGroup.get_user_personal_repo_group(self.user_id)
1364
1364
1365 @LazyProperty
1365 @LazyProperty
1366 def feed_token(self):
1366 def feed_token(self):
1367 return self.get_instance().feed_token
1367 return self.get_instance().feed_token
1368
1368
1369 @classmethod
1369 @classmethod
1370 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
1370 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
1371 allowed_ips = AuthUser.get_allowed_ips(
1371 allowed_ips = AuthUser.get_allowed_ips(
1372 user_id, cache=True, inherit_from_default=inherit_from_default)
1372 user_id, cache=True, inherit_from_default=inherit_from_default)
1373 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
1373 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
1374 log.debug('IP:%s for user %s is in range of %s',
1374 log.debug('IP:%s for user %s is in range of %s',
1375 ip_addr, user_id, allowed_ips)
1375 ip_addr, user_id, allowed_ips)
1376 return True
1376 return True
1377 else:
1377 else:
1378 log.info('Access for IP:%s forbidden for user %s, '
1378 log.info('Access for IP:%s forbidden for user %s, '
1379 'not in %s', ip_addr, user_id, allowed_ips)
1379 'not in %s', ip_addr, user_id, allowed_ips)
1380 return False
1380 return False
1381
1381
1382 def get_branch_permissions(self, repo_name, perms=None):
1382 def get_branch_permissions(self, repo_name, perms=None):
1383 perms = perms or self.permissions_with_scope({'repo_name': repo_name})
1383 perms = perms or self.permissions_with_scope({'repo_name': repo_name})
1384 branch_perms = perms.get('repository_branches', {})
1384 branch_perms = perms.get('repository_branches', {})
1385 if not branch_perms:
1385 if not branch_perms:
1386 return {}
1386 return {}
1387 repo_branch_perms = branch_perms.get(repo_name)
1387 repo_branch_perms = branch_perms.get(repo_name)
1388 return repo_branch_perms or {}
1388 return repo_branch_perms or {}
1389
1389
1390 def get_rule_and_branch_permission(self, repo_name, branch_name):
1390 def get_rule_and_branch_permission(self, repo_name, branch_name):
1391 """
1391 """
1392 Check if this AuthUser has defined any permissions for branches. If any of
1392 Check if this AuthUser has defined any permissions for branches. If any of
1393 the rules match in order, we return the matching permissions
1393 the rules match in order, we return the matching permissions
1394 """
1394 """
1395
1395
1396 rule = default_perm = ''
1396 rule = default_perm = ''
1397
1397
1398 repo_branch_perms = self.get_branch_permissions(repo_name=repo_name)
1398 repo_branch_perms = self.get_branch_permissions(repo_name=repo_name)
1399 if not repo_branch_perms:
1399 if not repo_branch_perms:
1400 return rule, default_perm
1400 return rule, default_perm
1401
1401
1402 # now calculate the permissions
1402 # now calculate the permissions
1403 for pattern, branch_perm in repo_branch_perms.items():
1403 for pattern, branch_perm in repo_branch_perms.items():
1404 if fnmatch.fnmatch(branch_name, pattern):
1404 if fnmatch.fnmatch(branch_name, pattern):
1405 rule = '`{}`=>{}'.format(pattern, branch_perm)
1405 rule = '`{}`=>{}'.format(pattern, branch_perm)
1406 return rule, branch_perm
1406 return rule, branch_perm
1407
1407
1408 return rule, default_perm
1408 return rule, default_perm
1409
1409
1410 def __repr__(self):
1410 def __repr__(self):
1411 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1411 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1412 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1412 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1413
1413
1414 def set_authenticated(self, authenticated=True):
1414 def set_authenticated(self, authenticated=True):
1415 if self.user_id != self.anonymous_user.user_id:
1415 if self.user_id != self.anonymous_user.user_id:
1416 self.is_authenticated = authenticated
1416 self.is_authenticated = authenticated
1417
1417
1418 def get_cookie_store(self):
1418 def get_cookie_store(self):
1419 return {
1419 return {
1420 'username': self.username,
1420 'username': self.username,
1421 'password': md5(self.password or ''),
1421 'password': md5(self.password or ''),
1422 'user_id': self.user_id,
1422 'user_id': self.user_id,
1423 'is_authenticated': self.is_authenticated
1423 'is_authenticated': self.is_authenticated
1424 }
1424 }
1425
1425
1426 @classmethod
1426 @classmethod
1427 def from_cookie_store(cls, cookie_store):
1427 def from_cookie_store(cls, cookie_store):
1428 """
1428 """
1429 Creates AuthUser from a cookie store
1429 Creates AuthUser from a cookie store
1430
1430
1431 :param cls:
1431 :param cls:
1432 :param cookie_store:
1432 :param cookie_store:
1433 """
1433 """
1434 user_id = cookie_store.get('user_id')
1434 user_id = cookie_store.get('user_id')
1435 username = cookie_store.get('username')
1435 username = cookie_store.get('username')
1436 api_key = cookie_store.get('api_key')
1436 api_key = cookie_store.get('api_key')
1437 return AuthUser(user_id, api_key, username)
1437 return AuthUser(user_id, api_key, username)
1438
1438
1439 @classmethod
1439 @classmethod
1440 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1440 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1441 _set = set()
1441 _set = set()
1442
1442
1443 if inherit_from_default:
1443 if inherit_from_default:
1444 def_user_id = User.get_default_user(cache=True).user_id
1444 def_user_id = User.get_default_user(cache=True).user_id
1445 default_ips = UserIpMap.query().filter(UserIpMap.user_id == def_user_id)
1445 default_ips = UserIpMap.query().filter(UserIpMap.user_id == def_user_id)
1446 if cache:
1446 if cache:
1447 default_ips = default_ips.options(
1447 default_ips = default_ips.options(
1448 FromCache("sql_cache_short", "get_user_ips_default"))
1448 FromCache("sql_cache_short", "get_user_ips_default"))
1449
1449
1450 # populate from default user
1450 # populate from default user
1451 for ip in default_ips:
1451 for ip in default_ips:
1452 try:
1452 try:
1453 _set.add(ip.ip_addr)
1453 _set.add(ip.ip_addr)
1454 except ObjectDeletedError:
1454 except ObjectDeletedError:
1455 # since we use heavy caching sometimes it happens that
1455 # since we use heavy caching sometimes it happens that
1456 # we get deleted objects here, we just skip them
1456 # we get deleted objects here, we just skip them
1457 pass
1457 pass
1458
1458
1459 # NOTE:(marcink) we don't want to load any rules for empty
1459 # NOTE:(marcink) we don't want to load any rules for empty
1460 # user_id which is the case of access of non logged users when anonymous
1460 # user_id which is the case of access of non logged users when anonymous
1461 # access is disabled
1461 # access is disabled
1462 user_ips = []
1462 user_ips = []
1463 if user_id:
1463 if user_id:
1464 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1464 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1465 if cache:
1465 if cache:
1466 user_ips = user_ips.options(
1466 user_ips = user_ips.options(
1467 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1467 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1468
1468
1469 for ip in user_ips:
1469 for ip in user_ips:
1470 try:
1470 try:
1471 _set.add(ip.ip_addr)
1471 _set.add(ip.ip_addr)
1472 except ObjectDeletedError:
1472 except ObjectDeletedError:
1473 # since we use heavy caching sometimes it happens that we get
1473 # since we use heavy caching sometimes it happens that we get
1474 # deleted objects here, we just skip them
1474 # deleted objects here, we just skip them
1475 pass
1475 pass
1476 return _set or {ip for ip in ['0.0.0.0/0', '::/0']}
1476 return _set or {ip for ip in ['0.0.0.0/0', '::/0']}
1477
1477
1478
1478
1479 def set_available_permissions(settings):
1479 def set_available_permissions(settings):
1480 """
1480 """
1481 This function will propagate pyramid settings with all available defined
1481 This function will propagate pyramid settings with all available defined
1482 permission given in db. We don't want to check each time from db for new
1482 permission given in db. We don't want to check each time from db for new
1483 permissions since adding a new permission also requires application restart
1483 permissions since adding a new permission also requires application restart
1484 ie. to decorate new views with the newly created permission
1484 ie. to decorate new views with the newly created permission
1485
1485
1486 :param settings: current pyramid registry.settings
1486 :param settings: current pyramid registry.settings
1487
1487
1488 """
1488 """
1489 log.debug('auth: getting information about all available permissions')
1489 log.debug('auth: getting information about all available permissions')
1490 try:
1490 try:
1491 sa = meta.Session
1491 sa = meta.Session
1492 all_perms = sa.query(Permission).all()
1492 all_perms = sa.query(Permission).all()
1493 settings.setdefault('available_permissions',
1493 settings.setdefault('available_permissions',
1494 [x.permission_name for x in all_perms])
1494 [x.permission_name for x in all_perms])
1495 log.debug('auth: set available permissions')
1495 log.debug('auth: set available permissions')
1496 except Exception:
1496 except Exception:
1497 log.exception('Failed to fetch permissions from the database.')
1497 log.exception('Failed to fetch permissions from the database.')
1498 raise
1498 raise
1499
1499
1500
1500
1501 def get_csrf_token(session, force_new=False, save_if_missing=True):
1501 def get_csrf_token(session, force_new=False, save_if_missing=True):
1502 """
1502 """
1503 Return the current authentication token, creating one if one doesn't
1503 Return the current authentication token, creating one if one doesn't
1504 already exist and the save_if_missing flag is present.
1504 already exist and the save_if_missing flag is present.
1505
1505
1506 :param session: pass in the pyramid session, else we use the global ones
1506 :param session: pass in the pyramid session, else we use the global ones
1507 :param force_new: force to re-generate the token and store it in session
1507 :param force_new: force to re-generate the token and store it in session
1508 :param save_if_missing: save the newly generated token if it's missing in
1508 :param save_if_missing: save the newly generated token if it's missing in
1509 session
1509 session
1510 """
1510 """
1511 # NOTE(marcink): probably should be replaced with below one from pyramid 1.9
1511 # NOTE(marcink): probably should be replaced with below one from pyramid 1.9
1512 # from pyramid.csrf import get_csrf_token
1512 # from pyramid.csrf import get_csrf_token
1513
1513
1514 if (csrf_token_key not in session and save_if_missing) or force_new:
1514 if (csrf_token_key not in session and save_if_missing) or force_new:
1515 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1515 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1516 session[csrf_token_key] = token
1516 session[csrf_token_key] = token
1517 if hasattr(session, 'save'):
1517 if hasattr(session, 'save'):
1518 session.save()
1518 session.save()
1519 return session.get(csrf_token_key)
1519 return session.get(csrf_token_key)
1520
1520
1521
1521
1522 def get_request(perm_class_instance):
1522 def get_request(perm_class_instance):
1523 from pyramid.threadlocal import get_current_request
1523 from pyramid.threadlocal import get_current_request
1524 pyramid_request = get_current_request()
1524 pyramid_request = get_current_request()
1525 return pyramid_request
1525 return pyramid_request
1526
1526
1527
1527
1528 # CHECK DECORATORS
1528 # CHECK DECORATORS
1529 class CSRFRequired(object):
1529 class CSRFRequired(object):
1530 """
1530 """
1531 Decorator for authenticating a form
1531 Decorator for authenticating a form
1532
1532
1533 This decorator uses an authorization token stored in the client's
1533 This decorator uses an authorization token stored in the client's
1534 session for prevention of certain Cross-site request forgery (CSRF)
1534 session for prevention of certain Cross-site request forgery (CSRF)
1535 attacks (See
1535 attacks (See
1536 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1536 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1537 information).
1537 information).
1538
1538
1539 For use with the ``webhelpers.secure_form`` helper functions.
1539 For use with the ``webhelpers.secure_form`` helper functions.
1540
1540
1541 """
1541 """
1542 def __init__(self, token=csrf_token_key, header='X-CSRF-Token',
1542 def __init__(self, token=csrf_token_key, header='X-CSRF-Token',
1543 except_methods=None):
1543 except_methods=None):
1544 self.token = token
1544 self.token = token
1545 self.header = header
1545 self.header = header
1546 self.except_methods = except_methods or []
1546 self.except_methods = except_methods or []
1547
1547
1548 def __call__(self, func):
1548 def __call__(self, func):
1549 return get_cython_compat_decorator(self.__wrapper, func)
1549 return get_cython_compat_decorator(self.__wrapper, func)
1550
1550
1551 def _get_csrf(self, _request):
1551 def _get_csrf(self, _request):
1552 return _request.POST.get(self.token, _request.headers.get(self.header))
1552 return _request.POST.get(self.token, _request.headers.get(self.header))
1553
1553
1554 def check_csrf(self, _request, cur_token):
1554 def check_csrf(self, _request, cur_token):
1555 supplied_token = self._get_csrf(_request)
1555 supplied_token = self._get_csrf(_request)
1556 return supplied_token and supplied_token == cur_token
1556 return supplied_token and supplied_token == cur_token
1557
1557
1558 def _get_request(self):
1558 def _get_request(self):
1559 return get_request(self)
1559 return get_request(self)
1560
1560
1561 def __wrapper(self, func, *fargs, **fkwargs):
1561 def __wrapper(self, func, *fargs, **fkwargs):
1562 request = self._get_request()
1562 request = self._get_request()
1563
1563
1564 if request.method in self.except_methods:
1564 if request.method in self.except_methods:
1565 return func(*fargs, **fkwargs)
1565 return func(*fargs, **fkwargs)
1566
1566
1567 cur_token = get_csrf_token(request.session, save_if_missing=False)
1567 cur_token = get_csrf_token(request.session, save_if_missing=False)
1568 if self.check_csrf(request, cur_token):
1568 if self.check_csrf(request, cur_token):
1569 if request.POST.get(self.token):
1569 if request.POST.get(self.token):
1570 del request.POST[self.token]
1570 del request.POST[self.token]
1571 return func(*fargs, **fkwargs)
1571 return func(*fargs, **fkwargs)
1572 else:
1572 else:
1573 reason = 'token-missing'
1573 reason = 'token-missing'
1574 supplied_token = self._get_csrf(request)
1574 supplied_token = self._get_csrf(request)
1575 if supplied_token and cur_token != supplied_token:
1575 if supplied_token and cur_token != supplied_token:
1576 reason = 'token-mismatch [%s:%s]' % (
1576 reason = 'token-mismatch [%s:%s]' % (
1577 cur_token or ''[:6], supplied_token or ''[:6])
1577 cur_token or ''[:6], supplied_token or ''[:6])
1578
1578
1579 csrf_message = \
1579 csrf_message = \
1580 ("Cross-site request forgery detected, request denied. See "
1580 ("Cross-site request forgery detected, request denied. See "
1581 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1581 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1582 "more information.")
1582 "more information.")
1583 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1583 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1584 'REMOTE_ADDR:%s, HEADERS:%s' % (
1584 'REMOTE_ADDR:%s, HEADERS:%s' % (
1585 request, reason, request.remote_addr, request.headers))
1585 request, reason, request.remote_addr, request.headers))
1586
1586
1587 raise HTTPForbidden(explanation=csrf_message)
1587 raise HTTPForbidden(explanation=csrf_message)
1588
1588
1589
1589
1590 class LoginRequired(object):
1590 class LoginRequired(object):
1591 """
1591 """
1592 Must be logged in to execute this function else
1592 Must be logged in to execute this function else
1593 redirect to login page
1593 redirect to login page
1594
1594
1595 :param api_access: if enabled this checks only for valid auth token
1595 :param api_access: if enabled this checks only for valid auth token
1596 and grants access based on valid token
1596 and grants access based on valid token
1597 """
1597 """
1598 def __init__(self, auth_token_access=None):
1598 def __init__(self, auth_token_access=None):
1599 self.auth_token_access = auth_token_access
1599 self.auth_token_access = auth_token_access
1600
1600
1601 def __call__(self, func):
1601 def __call__(self, func):
1602 return get_cython_compat_decorator(self.__wrapper, func)
1602 return get_cython_compat_decorator(self.__wrapper, func)
1603
1603
1604 def _get_request(self):
1604 def _get_request(self):
1605 return get_request(self)
1605 return get_request(self)
1606
1606
1607 def __wrapper(self, func, *fargs, **fkwargs):
1607 def __wrapper(self, func, *fargs, **fkwargs):
1608 from rhodecode.lib import helpers as h
1608 from rhodecode.lib import helpers as h
1609 cls = fargs[0]
1609 cls = fargs[0]
1610 user = cls._rhodecode_user
1610 user = cls._rhodecode_user
1611 request = self._get_request()
1611 request = self._get_request()
1612 _ = request.translate
1612 _ = request.translate
1613
1613
1614 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1614 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1615 log.debug('Starting login restriction checks for user: %s', user)
1615 log.debug('Starting login restriction checks for user: %s', user)
1616 # check if our IP is allowed
1616 # check if our IP is allowed
1617 ip_access_valid = True
1617 ip_access_valid = True
1618 if not user.ip_allowed:
1618 if not user.ip_allowed:
1619 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr,))),
1619 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr,))),
1620 category='warning')
1620 category='warning')
1621 ip_access_valid = False
1621 ip_access_valid = False
1622
1622
1623 # check if we used an APIKEY and it's a valid one
1623 # check if we used an APIKEY and it's a valid one
1624 # defined white-list of controllers which API access will be enabled
1624 # defined white-list of controllers which API access will be enabled
1625 _auth_token = request.GET.get(
1625 _auth_token = request.GET.get(
1626 'auth_token', '') or request.GET.get('api_key', '')
1626 'auth_token', '') or request.GET.get('api_key', '')
1627 auth_token_access_valid = allowed_auth_token_access(
1627 auth_token_access_valid = allowed_auth_token_access(
1628 loc, auth_token=_auth_token)
1628 loc, auth_token=_auth_token)
1629
1629
1630 # explicit controller is enabled or API is in our whitelist
1630 # explicit controller is enabled or API is in our whitelist
1631 if self.auth_token_access or auth_token_access_valid:
1631 if self.auth_token_access or auth_token_access_valid:
1632 log.debug('Checking AUTH TOKEN access for %s', cls)
1632 log.debug('Checking AUTH TOKEN access for %s', cls)
1633 db_user = user.get_instance()
1633 db_user = user.get_instance()
1634
1634
1635 if db_user:
1635 if db_user:
1636 if self.auth_token_access:
1636 if self.auth_token_access:
1637 roles = self.auth_token_access
1637 roles = self.auth_token_access
1638 else:
1638 else:
1639 roles = [UserApiKeys.ROLE_HTTP]
1639 roles = [UserApiKeys.ROLE_HTTP]
1640 token_match = db_user.authenticate_by_token(
1640 token_match = db_user.authenticate_by_token(
1641 _auth_token, roles=roles)
1641 _auth_token, roles=roles)
1642 else:
1642 else:
1643 log.debug('Unable to fetch db instance for auth user: %s', user)
1643 log.debug('Unable to fetch db instance for auth user: %s', user)
1644 token_match = False
1644 token_match = False
1645
1645
1646 if _auth_token and token_match:
1646 if _auth_token and token_match:
1647 auth_token_access_valid = True
1647 auth_token_access_valid = True
1648 log.debug('AUTH TOKEN ****%s is VALID', _auth_token[-4:])
1648 log.debug('AUTH TOKEN ****%s is VALID', _auth_token[-4:])
1649 else:
1649 else:
1650 auth_token_access_valid = False
1650 auth_token_access_valid = False
1651 if not _auth_token:
1651 if not _auth_token:
1652 log.debug("AUTH TOKEN *NOT* present in request")
1652 log.debug("AUTH TOKEN *NOT* present in request")
1653 else:
1653 else:
1654 log.warning("AUTH TOKEN ****%s *NOT* valid", _auth_token[-4:])
1654 log.warning("AUTH TOKEN ****%s *NOT* valid", _auth_token[-4:])
1655
1655
1656 log.debug('Checking if %s is authenticated @ %s', user.username, loc)
1656 log.debug('Checking if %s is authenticated @ %s', user.username, loc)
1657 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1657 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1658 else 'AUTH_TOKEN_AUTH'
1658 else 'AUTH_TOKEN_AUTH'
1659
1659
1660 if ip_access_valid and (
1660 if ip_access_valid and (
1661 user.is_authenticated or auth_token_access_valid):
1661 user.is_authenticated or auth_token_access_valid):
1662 log.info('user %s authenticating with:%s IS authenticated on func %s',
1662 log.info('user %s authenticating with:%s IS authenticated on func %s',
1663 user, reason, loc)
1663 user, reason, loc)
1664
1664
1665 return func(*fargs, **fkwargs)
1665 return func(*fargs, **fkwargs)
1666 else:
1666 else:
1667 log.warning(
1667 log.warning(
1668 'user %s authenticating with:%s NOT authenticated on '
1668 'user %s authenticating with:%s NOT authenticated on '
1669 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s',
1669 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s',
1670 user, reason, loc, ip_access_valid, auth_token_access_valid)
1670 user, reason, loc, ip_access_valid, auth_token_access_valid)
1671 # we preserve the get PARAM
1671 # we preserve the get PARAM
1672 came_from = get_came_from(request)
1672 came_from = get_came_from(request)
1673
1673
1674 log.debug('redirecting to login page with %s', came_from)
1674 log.debug('redirecting to login page with %s', came_from)
1675 raise HTTPFound(
1675 raise HTTPFound(
1676 h.route_path('login', _query={'came_from': came_from}))
1676 h.route_path('login', _query={'came_from': came_from}))
1677
1677
1678
1678
1679 class NotAnonymous(object):
1679 class NotAnonymous(object):
1680 """
1680 """
1681 Must be logged in to execute this function else
1681 Must be logged in to execute this function else
1682 redirect to login page
1682 redirect to login page
1683 """
1683 """
1684
1684
1685 def __call__(self, func):
1685 def __call__(self, func):
1686 return get_cython_compat_decorator(self.__wrapper, func)
1686 return get_cython_compat_decorator(self.__wrapper, func)
1687
1687
1688 def _get_request(self):
1688 def _get_request(self):
1689 return get_request(self)
1689 return get_request(self)
1690
1690
1691 def __wrapper(self, func, *fargs, **fkwargs):
1691 def __wrapper(self, func, *fargs, **fkwargs):
1692 import rhodecode.lib.helpers as h
1692 import rhodecode.lib.helpers as h
1693 cls = fargs[0]
1693 cls = fargs[0]
1694 self.user = cls._rhodecode_user
1694 self.user = cls._rhodecode_user
1695 request = self._get_request()
1695 request = self._get_request()
1696 _ = request.translate
1696 _ = request.translate
1697 log.debug('Checking if user is not anonymous @%s', cls)
1697 log.debug('Checking if user is not anonymous @%s', cls)
1698
1698
1699 anonymous = self.user.username == User.DEFAULT_USER
1699 anonymous = self.user.username == User.DEFAULT_USER
1700
1700
1701 if anonymous:
1701 if anonymous:
1702 came_from = get_came_from(request)
1702 came_from = get_came_from(request)
1703 h.flash(_('You need to be a registered user to '
1703 h.flash(_('You need to be a registered user to '
1704 'perform this action'),
1704 'perform this action'),
1705 category='warning')
1705 category='warning')
1706 raise HTTPFound(
1706 raise HTTPFound(
1707 h.route_path('login', _query={'came_from': came_from}))
1707 h.route_path('login', _query={'came_from': came_from}))
1708 else:
1708 else:
1709 return func(*fargs, **fkwargs)
1709 return func(*fargs, **fkwargs)
1710
1710
1711
1711
1712 class PermsDecorator(object):
1712 class PermsDecorator(object):
1713 """
1713 """
1714 Base class for controller decorators, we extract the current user from
1714 Base class for controller decorators, we extract the current user from
1715 the class itself, which has it stored in base controllers
1715 the class itself, which has it stored in base controllers
1716 """
1716 """
1717
1717
1718 def __init__(self, *required_perms):
1718 def __init__(self, *required_perms):
1719 self.required_perms = set(required_perms)
1719 self.required_perms = set(required_perms)
1720
1720
1721 def __call__(self, func):
1721 def __call__(self, func):
1722 return get_cython_compat_decorator(self.__wrapper, func)
1722 return get_cython_compat_decorator(self.__wrapper, func)
1723
1723
1724 def _get_request(self):
1724 def _get_request(self):
1725 return get_request(self)
1725 return get_request(self)
1726
1726
1727 def __wrapper(self, func, *fargs, **fkwargs):
1727 def __wrapper(self, func, *fargs, **fkwargs):
1728 import rhodecode.lib.helpers as h
1728 import rhodecode.lib.helpers as h
1729 cls = fargs[0]
1729 cls = fargs[0]
1730 _user = cls._rhodecode_user
1730 _user = cls._rhodecode_user
1731 request = self._get_request()
1731 request = self._get_request()
1732 _ = request.translate
1732 _ = request.translate
1733
1733
1734 log.debug('checking %s permissions %s for %s %s',
1734 log.debug('checking %s permissions %s for %s %s',
1735 self.__class__.__name__, self.required_perms, cls, _user)
1735 self.__class__.__name__, self.required_perms, cls, _user)
1736
1736
1737 if self.check_permissions(_user):
1737 if self.check_permissions(_user):
1738 log.debug('Permission granted for %s %s', cls, _user)
1738 log.debug('Permission granted for %s %s', cls, _user)
1739 return func(*fargs, **fkwargs)
1739 return func(*fargs, **fkwargs)
1740
1740
1741 else:
1741 else:
1742 log.debug('Permission denied for %s %s', cls, _user)
1742 log.debug('Permission denied for %s %s', cls, _user)
1743 anonymous = _user.username == User.DEFAULT_USER
1743 anonymous = _user.username == User.DEFAULT_USER
1744
1744
1745 if anonymous:
1745 if anonymous:
1746 came_from = get_came_from(self._get_request())
1746 came_from = get_came_from(self._get_request())
1747 h.flash(_('You need to be signed in to view this page'),
1747 h.flash(_('You need to be signed in to view this page'),
1748 category='warning')
1748 category='warning')
1749 raise HTTPFound(
1749 raise HTTPFound(
1750 h.route_path('login', _query={'came_from': came_from}))
1750 h.route_path('login', _query={'came_from': came_from}))
1751
1751
1752 else:
1752 else:
1753 # redirect with 404 to prevent resource discovery
1753 # redirect with 404 to prevent resource discovery
1754 raise HTTPNotFound()
1754 raise HTTPNotFound()
1755
1755
1756 def check_permissions(self, user):
1756 def check_permissions(self, user):
1757 """Dummy function for overriding"""
1757 """Dummy function for overriding"""
1758 raise NotImplementedError(
1758 raise NotImplementedError(
1759 'You have to write this function in child class')
1759 'You have to write this function in child class')
1760
1760
1761
1761
1762 class HasPermissionAllDecorator(PermsDecorator):
1762 class HasPermissionAllDecorator(PermsDecorator):
1763 """
1763 """
1764 Checks for access permission for all given predicates. All of them
1764 Checks for access permission for all given predicates. All of them
1765 have to be meet in order to fulfill the request
1765 have to be meet in order to fulfill the request
1766 """
1766 """
1767
1767
1768 def check_permissions(self, user):
1768 def check_permissions(self, user):
1769 perms = user.permissions_with_scope({})
1769 perms = user.permissions_with_scope({})
1770 if self.required_perms.issubset(perms['global']):
1770 if self.required_perms.issubset(perms['global']):
1771 return True
1771 return True
1772 return False
1772 return False
1773
1773
1774
1774
1775 class HasPermissionAnyDecorator(PermsDecorator):
1775 class HasPermissionAnyDecorator(PermsDecorator):
1776 """
1776 """
1777 Checks for access permission for any of given predicates. In order to
1777 Checks for access permission for any of given predicates. In order to
1778 fulfill the request any of predicates must be meet
1778 fulfill the request any of predicates must be meet
1779 """
1779 """
1780
1780
1781 def check_permissions(self, user):
1781 def check_permissions(self, user):
1782 perms = user.permissions_with_scope({})
1782 perms = user.permissions_with_scope({})
1783 if self.required_perms.intersection(perms['global']):
1783 if self.required_perms.intersection(perms['global']):
1784 return True
1784 return True
1785 return False
1785 return False
1786
1786
1787
1787
1788 class HasRepoPermissionAllDecorator(PermsDecorator):
1788 class HasRepoPermissionAllDecorator(PermsDecorator):
1789 """
1789 """
1790 Checks for access permission for all given predicates for specific
1790 Checks for access permission for all given predicates for specific
1791 repository. All of them have to be meet in order to fulfill the request
1791 repository. All of them have to be meet in order to fulfill the request
1792 """
1792 """
1793 def _get_repo_name(self):
1793 def _get_repo_name(self):
1794 _request = self._get_request()
1794 _request = self._get_request()
1795 return get_repo_slug(_request)
1795 return get_repo_slug(_request)
1796
1796
1797 def check_permissions(self, user):
1797 def check_permissions(self, user):
1798 perms = user.permissions
1798 perms = user.permissions
1799 repo_name = self._get_repo_name()
1799 repo_name = self._get_repo_name()
1800
1800
1801 try:
1801 try:
1802 user_perms = {perms['repositories'][repo_name]}
1802 user_perms = {perms['repositories'][repo_name]}
1803 except KeyError:
1803 except KeyError:
1804 log.debug('cannot locate repo with name: `%s` in permissions defs',
1804 log.debug('cannot locate repo with name: `%s` in permissions defs',
1805 repo_name)
1805 repo_name)
1806 return False
1806 return False
1807
1807
1808 log.debug('checking `%s` permissions for repo `%s`',
1808 log.debug('checking `%s` permissions for repo `%s`',
1809 user_perms, repo_name)
1809 user_perms, repo_name)
1810 if self.required_perms.issubset(user_perms):
1810 if self.required_perms.issubset(user_perms):
1811 return True
1811 return True
1812 return False
1812 return False
1813
1813
1814
1814
1815 class HasRepoPermissionAnyDecorator(PermsDecorator):
1815 class HasRepoPermissionAnyDecorator(PermsDecorator):
1816 """
1816 """
1817 Checks for access permission for any of given predicates for specific
1817 Checks for access permission for any of given predicates for specific
1818 repository. In order to fulfill the request any of predicates must be meet
1818 repository. In order to fulfill the request any of predicates must be meet
1819 """
1819 """
1820 def _get_repo_name(self):
1820 def _get_repo_name(self):
1821 _request = self._get_request()
1821 _request = self._get_request()
1822 return get_repo_slug(_request)
1822 return get_repo_slug(_request)
1823
1823
1824 def check_permissions(self, user):
1824 def check_permissions(self, user):
1825 perms = user.permissions
1825 perms = user.permissions
1826 repo_name = self._get_repo_name()
1826 repo_name = self._get_repo_name()
1827
1827
1828 try:
1828 try:
1829 user_perms = {perms['repositories'][repo_name]}
1829 user_perms = {perms['repositories'][repo_name]}
1830 except KeyError:
1830 except KeyError:
1831 log.debug(
1831 log.debug(
1832 'cannot locate repo with name: `%s` in permissions defs',
1832 'cannot locate repo with name: `%s` in permissions defs',
1833 repo_name)
1833 repo_name)
1834 return False
1834 return False
1835
1835
1836 log.debug('checking `%s` permissions for repo `%s`',
1836 log.debug('checking `%s` permissions for repo `%s`',
1837 user_perms, repo_name)
1837 user_perms, repo_name)
1838 if self.required_perms.intersection(user_perms):
1838 if self.required_perms.intersection(user_perms):
1839 return True
1839 return True
1840 return False
1840 return False
1841
1841
1842
1842
1843 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1843 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1844 """
1844 """
1845 Checks for access permission for all given predicates for specific
1845 Checks for access permission for all given predicates for specific
1846 repository group. All of them have to be meet in order to
1846 repository group. All of them have to be meet in order to
1847 fulfill the request
1847 fulfill the request
1848 """
1848 """
1849 def _get_repo_group_name(self):
1849 def _get_repo_group_name(self):
1850 _request = self._get_request()
1850 _request = self._get_request()
1851 return get_repo_group_slug(_request)
1851 return get_repo_group_slug(_request)
1852
1852
1853 def check_permissions(self, user):
1853 def check_permissions(self, user):
1854 perms = user.permissions
1854 perms = user.permissions
1855 group_name = self._get_repo_group_name()
1855 group_name = self._get_repo_group_name()
1856 try:
1856 try:
1857 user_perms = {perms['repositories_groups'][group_name]}
1857 user_perms = {perms['repositories_groups'][group_name]}
1858 except KeyError:
1858 except KeyError:
1859 log.debug(
1859 log.debug(
1860 'cannot locate repo group with name: `%s` in permissions defs',
1860 'cannot locate repo group with name: `%s` in permissions defs',
1861 group_name)
1861 group_name)
1862 return False
1862 return False
1863
1863
1864 log.debug('checking `%s` permissions for repo group `%s`',
1864 log.debug('checking `%s` permissions for repo group `%s`',
1865 user_perms, group_name)
1865 user_perms, group_name)
1866 if self.required_perms.issubset(user_perms):
1866 if self.required_perms.issubset(user_perms):
1867 return True
1867 return True
1868 return False
1868 return False
1869
1869
1870
1870
1871 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1871 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1872 """
1872 """
1873 Checks for access permission for any of given predicates for specific
1873 Checks for access permission for any of given predicates for specific
1874 repository group. In order to fulfill the request any
1874 repository group. In order to fulfill the request any
1875 of predicates must be met
1875 of predicates must be met
1876 """
1876 """
1877 def _get_repo_group_name(self):
1877 def _get_repo_group_name(self):
1878 _request = self._get_request()
1878 _request = self._get_request()
1879 return get_repo_group_slug(_request)
1879 return get_repo_group_slug(_request)
1880
1880
1881 def check_permissions(self, user):
1881 def check_permissions(self, user):
1882 perms = user.permissions
1882 perms = user.permissions
1883 group_name = self._get_repo_group_name()
1883 group_name = self._get_repo_group_name()
1884
1884
1885 try:
1885 try:
1886 user_perms = {perms['repositories_groups'][group_name]}
1886 user_perms = {perms['repositories_groups'][group_name]}
1887 except KeyError:
1887 except KeyError:
1888 log.debug(
1888 log.debug(
1889 'cannot locate repo group with name: `%s` in permissions defs',
1889 'cannot locate repo group with name: `%s` in permissions defs',
1890 group_name)
1890 group_name)
1891 return False
1891 return False
1892
1892
1893 log.debug('checking `%s` permissions for repo group `%s`',
1893 log.debug('checking `%s` permissions for repo group `%s`',
1894 user_perms, group_name)
1894 user_perms, group_name)
1895 if self.required_perms.intersection(user_perms):
1895 if self.required_perms.intersection(user_perms):
1896 return True
1896 return True
1897 return False
1897 return False
1898
1898
1899
1899
1900 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1900 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1901 """
1901 """
1902 Checks for access permission for all given predicates for specific
1902 Checks for access permission for all given predicates for specific
1903 user group. All of them have to be meet in order to fulfill the request
1903 user group. All of them have to be meet in order to fulfill the request
1904 """
1904 """
1905 def _get_user_group_name(self):
1905 def _get_user_group_name(self):
1906 _request = self._get_request()
1906 _request = self._get_request()
1907 return get_user_group_slug(_request)
1907 return get_user_group_slug(_request)
1908
1908
1909 def check_permissions(self, user):
1909 def check_permissions(self, user):
1910 perms = user.permissions
1910 perms = user.permissions
1911 group_name = self._get_user_group_name()
1911 group_name = self._get_user_group_name()
1912 try:
1912 try:
1913 user_perms = {perms['user_groups'][group_name]}
1913 user_perms = {perms['user_groups'][group_name]}
1914 except KeyError:
1914 except KeyError:
1915 return False
1915 return False
1916
1916
1917 if self.required_perms.issubset(user_perms):
1917 if self.required_perms.issubset(user_perms):
1918 return True
1918 return True
1919 return False
1919 return False
1920
1920
1921
1921
1922 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1922 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1923 """
1923 """
1924 Checks for access permission for any of given predicates for specific
1924 Checks for access permission for any of given predicates for specific
1925 user group. In order to fulfill the request any of predicates must be meet
1925 user group. In order to fulfill the request any of predicates must be meet
1926 """
1926 """
1927 def _get_user_group_name(self):
1927 def _get_user_group_name(self):
1928 _request = self._get_request()
1928 _request = self._get_request()
1929 return get_user_group_slug(_request)
1929 return get_user_group_slug(_request)
1930
1930
1931 def check_permissions(self, user):
1931 def check_permissions(self, user):
1932 perms = user.permissions
1932 perms = user.permissions
1933 group_name = self._get_user_group_name()
1933 group_name = self._get_user_group_name()
1934 try:
1934 try:
1935 user_perms = {perms['user_groups'][group_name]}
1935 user_perms = {perms['user_groups'][group_name]}
1936 except KeyError:
1936 except KeyError:
1937 return False
1937 return False
1938
1938
1939 if self.required_perms.intersection(user_perms):
1939 if self.required_perms.intersection(user_perms):
1940 return True
1940 return True
1941 return False
1941 return False
1942
1942
1943
1943
1944 # CHECK FUNCTIONS
1944 # CHECK FUNCTIONS
1945 class PermsFunction(object):
1945 class PermsFunction(object):
1946 """Base function for other check functions"""
1946 """Base function for other check functions"""
1947
1947
1948 def __init__(self, *perms):
1948 def __init__(self, *perms):
1949 self.required_perms = set(perms)
1949 self.required_perms = set(perms)
1950 self.repo_name = None
1950 self.repo_name = None
1951 self.repo_group_name = None
1951 self.repo_group_name = None
1952 self.user_group_name = None
1952 self.user_group_name = None
1953
1953
1954 def __bool__(self):
1954 def __bool__(self):
1955 frame = inspect.currentframe()
1955 frame = inspect.currentframe()
1956 stack_trace = traceback.format_stack(frame)
1956 stack_trace = traceback.format_stack(frame)
1957 log.error('Checking bool value on a class instance of perm '
1957 log.error('Checking bool value on a class instance of perm '
1958 'function is not allowed: %s', ''.join(stack_trace))
1958 'function is not allowed: %s', ''.join(stack_trace))
1959 # rather than throwing errors, here we always return False so if by
1959 # rather than throwing errors, here we always return False so if by
1960 # accident someone checks truth for just an instance it will always end
1960 # accident someone checks truth for just an instance it will always end
1961 # up in returning False
1961 # up in returning False
1962 return False
1962 return False
1963 __nonzero__ = __bool__
1963 __nonzero__ = __bool__
1964
1964
1965 def __call__(self, check_location='', user=None):
1965 def __call__(self, check_location='', user=None):
1966 if not user:
1966 if not user:
1967 log.debug('Using user attribute from global request')
1967 log.debug('Using user attribute from global request')
1968 request = self._get_request()
1968 request = self._get_request()
1969 user = request.user
1969 user = request.user
1970
1970
1971 # init auth user if not already given
1971 # init auth user if not already given
1972 if not isinstance(user, AuthUser):
1972 if not isinstance(user, AuthUser):
1973 log.debug('Wrapping user %s into AuthUser', user)
1973 log.debug('Wrapping user %s into AuthUser', user)
1974 user = AuthUser(user.user_id)
1974 user = AuthUser(user.user_id)
1975
1975
1976 cls_name = self.__class__.__name__
1976 cls_name = self.__class__.__name__
1977 check_scope = self._get_check_scope(cls_name)
1977 check_scope = self._get_check_scope(cls_name)
1978 check_location = check_location or 'unspecified location'
1978 check_location = check_location or 'unspecified location'
1979
1979
1980 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
1980 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
1981 self.required_perms, user, check_scope, check_location)
1981 self.required_perms, user, check_scope, check_location)
1982 if not user:
1982 if not user:
1983 log.warning('Empty user given for permission check')
1983 log.warning('Empty user given for permission check')
1984 return False
1984 return False
1985
1985
1986 if self.check_permissions(user):
1986 if self.check_permissions(user):
1987 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1987 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1988 check_scope, user, check_location)
1988 check_scope, user, check_location)
1989 return True
1989 return True
1990
1990
1991 else:
1991 else:
1992 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1992 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1993 check_scope, user, check_location)
1993 check_scope, user, check_location)
1994 return False
1994 return False
1995
1995
1996 def _get_request(self):
1996 def _get_request(self):
1997 return get_request(self)
1997 return get_request(self)
1998
1998
1999 def _get_check_scope(self, cls_name):
1999 def _get_check_scope(self, cls_name):
2000 return {
2000 return {
2001 'HasPermissionAll': 'GLOBAL',
2001 'HasPermissionAll': 'GLOBAL',
2002 'HasPermissionAny': 'GLOBAL',
2002 'HasPermissionAny': 'GLOBAL',
2003 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
2003 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
2004 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
2004 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
2005 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
2005 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
2006 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
2006 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
2007 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
2007 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
2008 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
2008 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
2009 }.get(cls_name, '?:%s' % cls_name)
2009 }.get(cls_name, '?:%s' % cls_name)
2010
2010
2011 def check_permissions(self, user):
2011 def check_permissions(self, user):
2012 """Dummy function for overriding"""
2012 """Dummy function for overriding"""
2013 raise Exception('You have to write this function in child class')
2013 raise Exception('You have to write this function in child class')
2014
2014
2015
2015
2016 class HasPermissionAll(PermsFunction):
2016 class HasPermissionAll(PermsFunction):
2017 def check_permissions(self, user):
2017 def check_permissions(self, user):
2018 perms = user.permissions_with_scope({})
2018 perms = user.permissions_with_scope({})
2019 if self.required_perms.issubset(perms.get('global')):
2019 if self.required_perms.issubset(perms.get('global')):
2020 return True
2020 return True
2021 return False
2021 return False
2022
2022
2023
2023
2024 class HasPermissionAny(PermsFunction):
2024 class HasPermissionAny(PermsFunction):
2025 def check_permissions(self, user):
2025 def check_permissions(self, user):
2026 perms = user.permissions_with_scope({})
2026 perms = user.permissions_with_scope({})
2027 if self.required_perms.intersection(perms.get('global')):
2027 if self.required_perms.intersection(perms.get('global')):
2028 return True
2028 return True
2029 return False
2029 return False
2030
2030
2031
2031
2032 class HasRepoPermissionAll(PermsFunction):
2032 class HasRepoPermissionAll(PermsFunction):
2033 def __call__(self, repo_name=None, check_location='', user=None):
2033 def __call__(self, repo_name=None, check_location='', user=None):
2034 self.repo_name = repo_name
2034 self.repo_name = repo_name
2035 return super(HasRepoPermissionAll, self).__call__(check_location, user)
2035 return super(HasRepoPermissionAll, self).__call__(check_location, user)
2036
2036
2037 def _get_repo_name(self):
2037 def _get_repo_name(self):
2038 if not self.repo_name:
2038 if not self.repo_name:
2039 _request = self._get_request()
2039 _request = self._get_request()
2040 self.repo_name = get_repo_slug(_request)
2040 self.repo_name = get_repo_slug(_request)
2041 return self.repo_name
2041 return self.repo_name
2042
2042
2043 def check_permissions(self, user):
2043 def check_permissions(self, user):
2044 self.repo_name = self._get_repo_name()
2044 self.repo_name = self._get_repo_name()
2045 perms = user.permissions
2045 perms = user.permissions
2046 try:
2046 try:
2047 user_perms = {perms['repositories'][self.repo_name]}
2047 user_perms = {perms['repositories'][self.repo_name]}
2048 except KeyError:
2048 except KeyError:
2049 return False
2049 return False
2050 if self.required_perms.issubset(user_perms):
2050 if self.required_perms.issubset(user_perms):
2051 return True
2051 return True
2052 return False
2052 return False
2053
2053
2054
2054
2055 class HasRepoPermissionAny(PermsFunction):
2055 class HasRepoPermissionAny(PermsFunction):
2056 def __call__(self, repo_name=None, check_location='', user=None):
2056 def __call__(self, repo_name=None, check_location='', user=None):
2057 self.repo_name = repo_name
2057 self.repo_name = repo_name
2058 return super(HasRepoPermissionAny, self).__call__(check_location, user)
2058 return super(HasRepoPermissionAny, self).__call__(check_location, user)
2059
2059
2060 def _get_repo_name(self):
2060 def _get_repo_name(self):
2061 if not self.repo_name:
2061 if not self.repo_name:
2062 _request = self._get_request()
2062 _request = self._get_request()
2063 self.repo_name = get_repo_slug(_request)
2063 self.repo_name = get_repo_slug(_request)
2064 return self.repo_name
2064 return self.repo_name
2065
2065
2066 def check_permissions(self, user):
2066 def check_permissions(self, user):
2067 self.repo_name = self._get_repo_name()
2067 self.repo_name = self._get_repo_name()
2068 perms = user.permissions
2068 perms = user.permissions
2069 try:
2069 try:
2070 user_perms = {perms['repositories'][self.repo_name]}
2070 user_perms = {perms['repositories'][self.repo_name]}
2071 except KeyError:
2071 except KeyError:
2072 return False
2072 return False
2073 if self.required_perms.intersection(user_perms):
2073 if self.required_perms.intersection(user_perms):
2074 return True
2074 return True
2075 return False
2075 return False
2076
2076
2077
2077
2078 class HasRepoGroupPermissionAny(PermsFunction):
2078 class HasRepoGroupPermissionAny(PermsFunction):
2079 def __call__(self, group_name=None, check_location='', user=None):
2079 def __call__(self, group_name=None, check_location='', user=None):
2080 self.repo_group_name = group_name
2080 self.repo_group_name = group_name
2081 return super(HasRepoGroupPermissionAny, self).__call__(
2081 return super(HasRepoGroupPermissionAny, self).__call__(check_location, user)
2082 check_location, user)
2083
2082
2084 def check_permissions(self, user):
2083 def check_permissions(self, user):
2085 perms = user.permissions
2084 perms = user.permissions
2086 try:
2085 try:
2087 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2086 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2088 except KeyError:
2087 except KeyError:
2089 return False
2088 return False
2090 if self.required_perms.intersection(user_perms):
2089 if self.required_perms.intersection(user_perms):
2091 return True
2090 return True
2092 return False
2091 return False
2093
2092
2094
2093
2095 class HasRepoGroupPermissionAll(PermsFunction):
2094 class HasRepoGroupPermissionAll(PermsFunction):
2096 def __call__(self, group_name=None, check_location='', user=None):
2095 def __call__(self, group_name=None, check_location='', user=None):
2097 self.repo_group_name = group_name
2096 self.repo_group_name = group_name
2098 return super(HasRepoGroupPermissionAll, self).__call__(
2097 return super(HasRepoGroupPermissionAll, self).__call__(check_location, user)
2099 check_location, user)
2100
2098
2101 def check_permissions(self, user):
2099 def check_permissions(self, user):
2102 perms = user.permissions
2100 perms = user.permissions
2103 try:
2101 try:
2104 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2102 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2105 except KeyError:
2103 except KeyError:
2106 return False
2104 return False
2107 if self.required_perms.issubset(user_perms):
2105 if self.required_perms.issubset(user_perms):
2108 return True
2106 return True
2109 return False
2107 return False
2110
2108
2111
2109
2112 class HasUserGroupPermissionAny(PermsFunction):
2110 class HasUserGroupPermissionAny(PermsFunction):
2113 def __call__(self, user_group_name=None, check_location='', user=None):
2111 def __call__(self, user_group_name=None, check_location='', user=None):
2114 self.user_group_name = user_group_name
2112 self.user_group_name = user_group_name
2115 return super(HasUserGroupPermissionAny, self).__call__(
2113 return super(HasUserGroupPermissionAny, self).__call__(check_location, user)
2116 check_location, user)
2117
2114
2118 def check_permissions(self, user):
2115 def check_permissions(self, user):
2119 perms = user.permissions
2116 perms = user.permissions
2120 try:
2117 try:
2121 user_perms = {perms['user_groups'][self.user_group_name]}
2118 user_perms = {perms['user_groups'][self.user_group_name]}
2122 except KeyError:
2119 except KeyError:
2123 return False
2120 return False
2124 if self.required_perms.intersection(user_perms):
2121 if self.required_perms.intersection(user_perms):
2125 return True
2122 return True
2126 return False
2123 return False
2127
2124
2128
2125
2129 class HasUserGroupPermissionAll(PermsFunction):
2126 class HasUserGroupPermissionAll(PermsFunction):
2130 def __call__(self, user_group_name=None, check_location='', user=None):
2127 def __call__(self, user_group_name=None, check_location='', user=None):
2131 self.user_group_name = user_group_name
2128 self.user_group_name = user_group_name
2132 return super(HasUserGroupPermissionAll, self).__call__(
2129 return super(HasUserGroupPermissionAll, self).__call__(check_location, user)
2133 check_location, user)
2134
2130
2135 def check_permissions(self, user):
2131 def check_permissions(self, user):
2136 perms = user.permissions
2132 perms = user.permissions
2137 try:
2133 try:
2138 user_perms = {perms['user_groups'][self.user_group_name]}
2134 user_perms = {perms['user_groups'][self.user_group_name]}
2139 except KeyError:
2135 except KeyError:
2140 return False
2136 return False
2141 if self.required_perms.issubset(user_perms):
2137 if self.required_perms.issubset(user_perms):
2142 return True
2138 return True
2143 return False
2139 return False
2144
2140
2145
2141
2146 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
2142 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
2147 class HasPermissionAnyMiddleware(object):
2143 class HasPermissionAnyMiddleware(object):
2148 def __init__(self, *perms):
2144 def __init__(self, *perms):
2149 self.required_perms = set(perms)
2145 self.required_perms = set(perms)
2150
2146
2151 def __call__(self, auth_user, repo_name):
2147 def __call__(self, auth_user, repo_name):
2152 # repo_name MUST be unicode, since we handle keys in permission
2148 # repo_name MUST be unicode, since we handle keys in permission
2153 # dict by unicode
2149 # dict by unicode
2154 repo_name = safe_unicode(repo_name)
2150 repo_name = safe_unicode(repo_name)
2155 log.debug(
2151 log.debug(
2156 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
2152 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
2157 self.required_perms, auth_user, repo_name)
2153 self.required_perms, auth_user, repo_name)
2158
2154
2159 if self.check_permissions(auth_user, repo_name):
2155 if self.check_permissions(auth_user, repo_name):
2160 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
2156 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
2161 repo_name, auth_user, 'PermissionMiddleware')
2157 repo_name, auth_user, 'PermissionMiddleware')
2162 return True
2158 return True
2163
2159
2164 else:
2160 else:
2165 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
2161 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
2166 repo_name, auth_user, 'PermissionMiddleware')
2162 repo_name, auth_user, 'PermissionMiddleware')
2167 return False
2163 return False
2168
2164
2169 def check_permissions(self, user, repo_name):
2165 def check_permissions(self, user, repo_name):
2170 perms = user.permissions_with_scope({'repo_name': repo_name})
2166 perms = user.permissions_with_scope({'repo_name': repo_name})
2171
2167
2172 try:
2168 try:
2173 user_perms = {perms['repositories'][repo_name]}
2169 user_perms = {perms['repositories'][repo_name]}
2174 except Exception:
2170 except Exception:
2175 log.exception('Error while accessing user permissions')
2171 log.exception('Error while accessing user permissions')
2176 return False
2172 return False
2177
2173
2178 if self.required_perms.intersection(user_perms):
2174 if self.required_perms.intersection(user_perms):
2179 return True
2175 return True
2180 return False
2176 return False
2181
2177
2182
2178
2183 # SPECIAL VERSION TO HANDLE API AUTH
2179 # SPECIAL VERSION TO HANDLE API AUTH
2184 class _BaseApiPerm(object):
2180 class _BaseApiPerm(object):
2185 def __init__(self, *perms):
2181 def __init__(self, *perms):
2186 self.required_perms = set(perms)
2182 self.required_perms = set(perms)
2187
2183
2188 def __call__(self, check_location=None, user=None, repo_name=None,
2184 def __call__(self, check_location=None, user=None, repo_name=None,
2189 group_name=None, user_group_name=None):
2185 group_name=None, user_group_name=None):
2190 cls_name = self.__class__.__name__
2186 cls_name = self.__class__.__name__
2191 check_scope = 'global:%s' % (self.required_perms,)
2187 check_scope = 'global:%s' % (self.required_perms,)
2192 if repo_name:
2188 if repo_name:
2193 check_scope += ', repo_name:%s' % (repo_name,)
2189 check_scope += ', repo_name:%s' % (repo_name,)
2194
2190
2195 if group_name:
2191 if group_name:
2196 check_scope += ', repo_group_name:%s' % (group_name,)
2192 check_scope += ', repo_group_name:%s' % (group_name,)
2197
2193
2198 if user_group_name:
2194 if user_group_name:
2199 check_scope += ', user_group_name:%s' % (user_group_name,)
2195 check_scope += ', user_group_name:%s' % (user_group_name,)
2200
2196
2201 log.debug('checking cls:%s %s %s @ %s',
2197 log.debug('checking cls:%s %s %s @ %s',
2202 cls_name, self.required_perms, check_scope, check_location)
2198 cls_name, self.required_perms, check_scope, check_location)
2203 if not user:
2199 if not user:
2204 log.debug('Empty User passed into arguments')
2200 log.debug('Empty User passed into arguments')
2205 return False
2201 return False
2206
2202
2207 # process user
2203 # process user
2208 if not isinstance(user, AuthUser):
2204 if not isinstance(user, AuthUser):
2209 user = AuthUser(user.user_id)
2205 user = AuthUser(user.user_id)
2210 if not check_location:
2206 if not check_location:
2211 check_location = 'unspecified'
2207 check_location = 'unspecified'
2212 if self.check_permissions(user.permissions, repo_name, group_name,
2208 if self.check_permissions(user.permissions, repo_name, group_name,
2213 user_group_name):
2209 user_group_name):
2214 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2210 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2215 check_scope, user, check_location)
2211 check_scope, user, check_location)
2216 return True
2212 return True
2217
2213
2218 else:
2214 else:
2219 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2215 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2220 check_scope, user, check_location)
2216 check_scope, user, check_location)
2221 return False
2217 return False
2222
2218
2223 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2219 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2224 user_group_name=None):
2220 user_group_name=None):
2225 """
2221 """
2226 implement in child class should return True if permissions are ok,
2222 implement in child class should return True if permissions are ok,
2227 False otherwise
2223 False otherwise
2228
2224
2229 :param perm_defs: dict with permission definitions
2225 :param perm_defs: dict with permission definitions
2230 :param repo_name: repo name
2226 :param repo_name: repo name
2231 """
2227 """
2232 raise NotImplementedError()
2228 raise NotImplementedError()
2233
2229
2234
2230
2235 class HasPermissionAllApi(_BaseApiPerm):
2231 class HasPermissionAllApi(_BaseApiPerm):
2236 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2232 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2237 user_group_name=None):
2233 user_group_name=None):
2238 if self.required_perms.issubset(perm_defs.get('global')):
2234 if self.required_perms.issubset(perm_defs.get('global')):
2239 return True
2235 return True
2240 return False
2236 return False
2241
2237
2242
2238
2243 class HasPermissionAnyApi(_BaseApiPerm):
2239 class HasPermissionAnyApi(_BaseApiPerm):
2244 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2240 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2245 user_group_name=None):
2241 user_group_name=None):
2246 if self.required_perms.intersection(perm_defs.get('global')):
2242 if self.required_perms.intersection(perm_defs.get('global')):
2247 return True
2243 return True
2248 return False
2244 return False
2249
2245
2250
2246
2251 class HasRepoPermissionAllApi(_BaseApiPerm):
2247 class HasRepoPermissionAllApi(_BaseApiPerm):
2252 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2248 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2253 user_group_name=None):
2249 user_group_name=None):
2254 try:
2250 try:
2255 _user_perms = {perm_defs['repositories'][repo_name]}
2251 _user_perms = {perm_defs['repositories'][repo_name]}
2256 except KeyError:
2252 except KeyError:
2257 log.warning(traceback.format_exc())
2253 log.warning(traceback.format_exc())
2258 return False
2254 return False
2259 if self.required_perms.issubset(_user_perms):
2255 if self.required_perms.issubset(_user_perms):
2260 return True
2256 return True
2261 return False
2257 return False
2262
2258
2263
2259
2264 class HasRepoPermissionAnyApi(_BaseApiPerm):
2260 class HasRepoPermissionAnyApi(_BaseApiPerm):
2265 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2261 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2266 user_group_name=None):
2262 user_group_name=None):
2267 try:
2263 try:
2268 _user_perms = {perm_defs['repositories'][repo_name]}
2264 _user_perms = {perm_defs['repositories'][repo_name]}
2269 except KeyError:
2265 except KeyError:
2270 log.warning(traceback.format_exc())
2266 log.warning(traceback.format_exc())
2271 return False
2267 return False
2272 if self.required_perms.intersection(_user_perms):
2268 if self.required_perms.intersection(_user_perms):
2273 return True
2269 return True
2274 return False
2270 return False
2275
2271
2276
2272
2277 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
2273 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
2278 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2274 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2279 user_group_name=None):
2275 user_group_name=None):
2280 try:
2276 try:
2281 _user_perms = {perm_defs['repositories_groups'][group_name]}
2277 _user_perms = {perm_defs['repositories_groups'][group_name]}
2282 except KeyError:
2278 except KeyError:
2283 log.warning(traceback.format_exc())
2279 log.warning(traceback.format_exc())
2284 return False
2280 return False
2285 if self.required_perms.intersection(_user_perms):
2281 if self.required_perms.intersection(_user_perms):
2286 return True
2282 return True
2287 return False
2283 return False
2288
2284
2289
2285
2290 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
2286 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
2291 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2287 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2292 user_group_name=None):
2288 user_group_name=None):
2293 try:
2289 try:
2294 _user_perms = {perm_defs['repositories_groups'][group_name]}
2290 _user_perms = {perm_defs['repositories_groups'][group_name]}
2295 except KeyError:
2291 except KeyError:
2296 log.warning(traceback.format_exc())
2292 log.warning(traceback.format_exc())
2297 return False
2293 return False
2298 if self.required_perms.issubset(_user_perms):
2294 if self.required_perms.issubset(_user_perms):
2299 return True
2295 return True
2300 return False
2296 return False
2301
2297
2302
2298
2303 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
2299 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
2304 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2300 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2305 user_group_name=None):
2301 user_group_name=None):
2306 try:
2302 try:
2307 _user_perms = {perm_defs['user_groups'][user_group_name]}
2303 _user_perms = {perm_defs['user_groups'][user_group_name]}
2308 except KeyError:
2304 except KeyError:
2309 log.warning(traceback.format_exc())
2305 log.warning(traceback.format_exc())
2310 return False
2306 return False
2311 if self.required_perms.intersection(_user_perms):
2307 if self.required_perms.intersection(_user_perms):
2312 return True
2308 return True
2313 return False
2309 return False
2314
2310
2315
2311
2316 def check_ip_access(source_ip, allowed_ips=None):
2312 def check_ip_access(source_ip, allowed_ips=None):
2317 """
2313 """
2318 Checks if source_ip is a subnet of any of allowed_ips.
2314 Checks if source_ip is a subnet of any of allowed_ips.
2319
2315
2320 :param source_ip:
2316 :param source_ip:
2321 :param allowed_ips: list of allowed ips together with mask
2317 :param allowed_ips: list of allowed ips together with mask
2322 """
2318 """
2323 log.debug('checking if ip:%s is subnet of %s', source_ip, allowed_ips)
2319 log.debug('checking if ip:%s is subnet of %s', source_ip, allowed_ips)
2324 source_ip_address = ipaddress.ip_address(safe_unicode(source_ip))
2320 source_ip_address = ipaddress.ip_address(safe_unicode(source_ip))
2325 if isinstance(allowed_ips, (tuple, list, set)):
2321 if isinstance(allowed_ips, (tuple, list, set)):
2326 for ip in allowed_ips:
2322 for ip in allowed_ips:
2327 ip = safe_unicode(ip)
2323 ip = safe_unicode(ip)
2328 try:
2324 try:
2329 network_address = ipaddress.ip_network(ip, strict=False)
2325 network_address = ipaddress.ip_network(ip, strict=False)
2330 if source_ip_address in network_address:
2326 if source_ip_address in network_address:
2331 log.debug('IP %s is network %s', source_ip_address, network_address)
2327 log.debug('IP %s is network %s', source_ip_address, network_address)
2332 return True
2328 return True
2333 # for any case we cannot determine the IP, don't crash just
2329 # for any case we cannot determine the IP, don't crash just
2334 # skip it and log as error, we want to say forbidden still when
2330 # skip it and log as error, we want to say forbidden still when
2335 # sending bad IP
2331 # sending bad IP
2336 except Exception:
2332 except Exception:
2337 log.error(traceback.format_exc())
2333 log.error(traceback.format_exc())
2338 continue
2334 continue
2339 return False
2335 return False
2340
2336
2341
2337
2342 def get_cython_compat_decorator(wrapper, func):
2338 def get_cython_compat_decorator(wrapper, func):
2343 """
2339 """
2344 Creates a cython compatible decorator. The previously used
2340 Creates a cython compatible decorator. The previously used
2345 decorator.decorator() function seems to be incompatible with cython.
2341 decorator.decorator() function seems to be incompatible with cython.
2346
2342
2347 :param wrapper: __wrapper method of the decorator class
2343 :param wrapper: __wrapper method of the decorator class
2348 :param func: decorated function
2344 :param func: decorated function
2349 """
2345 """
2350 @wraps(func)
2346 @wraps(func)
2351 def local_wrapper(*args, **kwds):
2347 def local_wrapper(*args, **kwds):
2352 return wrapper(func, *args, **kwds)
2348 return wrapper(func, *args, **kwds)
2353 local_wrapper.__wrapped__ = func
2349 local_wrapper.__wrapped__ = func
2354 return local_wrapper
2350 return local_wrapper
2355
2351
2356
2352
@@ -1,584 +1,583 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 The base Controller API
22 The base Controller API
23 Provides the BaseController class for subclassing. And usage in different
23 Provides the BaseController class for subclassing. And usage in different
24 controllers
24 controllers
25 """
25 """
26
26
27 import logging
27 import logging
28 import socket
28 import socket
29
29
30 import markupsafe
30 import markupsafe
31 import ipaddress
31 import ipaddress
32
32
33 from paste.auth.basic import AuthBasicAuthenticator
33 from paste.auth.basic import AuthBasicAuthenticator
34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
36
36
37 import rhodecode
37 import rhodecode
38 from rhodecode.apps._base import TemplateArgs
38 from rhodecode.apps._base import TemplateArgs
39 from rhodecode.authentication.base import VCS_TYPE
39 from rhodecode.authentication.base import VCS_TYPE
40 from rhodecode.lib import auth, utils2
40 from rhodecode.lib import auth, utils2
41 from rhodecode.lib import helpers as h
41 from rhodecode.lib import helpers as h
42 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
42 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
43 from rhodecode.lib.exceptions import UserCreationError
43 from rhodecode.lib.exceptions import UserCreationError
44 from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes)
44 from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes)
45 from rhodecode.lib.utils2 import (
45 from rhodecode.lib.utils2 import (
46 str2bool, safe_unicode, AttributeDict, safe_int, sha1, aslist, safe_str)
46 str2bool, safe_unicode, AttributeDict, safe_int, sha1, aslist, safe_str)
47 from rhodecode.model.db import Repository, User, ChangesetComment, UserBookmark
47 from rhodecode.model.db import Repository, User, ChangesetComment, UserBookmark
48 from rhodecode.model.notification import NotificationModel
48 from rhodecode.model.notification import NotificationModel
49 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
49 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 def _filter_proxy(ip):
54 def _filter_proxy(ip):
55 """
55 """
56 Passed in IP addresses in HEADERS can be in a special format of multiple
56 Passed in IP addresses in HEADERS can be in a special format of multiple
57 ips. Those comma separated IPs are passed from various proxies in the
57 ips. Those comma separated IPs are passed from various proxies in the
58 chain of request processing. The left-most being the original client.
58 chain of request processing. The left-most being the original client.
59 We only care about the first IP which came from the org. client.
59 We only care about the first IP which came from the org. client.
60
60
61 :param ip: ip string from headers
61 :param ip: ip string from headers
62 """
62 """
63 if ',' in ip:
63 if ',' in ip:
64 _ips = ip.split(',')
64 _ips = ip.split(',')
65 _first_ip = _ips[0].strip()
65 _first_ip = _ips[0].strip()
66 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
66 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
67 return _first_ip
67 return _first_ip
68 return ip
68 return ip
69
69
70
70
71 def _filter_port(ip):
71 def _filter_port(ip):
72 """
72 """
73 Removes a port from ip, there are 4 main cases to handle here.
73 Removes a port from ip, there are 4 main cases to handle here.
74 - ipv4 eg. 127.0.0.1
74 - ipv4 eg. 127.0.0.1
75 - ipv6 eg. ::1
75 - ipv6 eg. ::1
76 - ipv4+port eg. 127.0.0.1:8080
76 - ipv4+port eg. 127.0.0.1:8080
77 - ipv6+port eg. [::1]:8080
77 - ipv6+port eg. [::1]:8080
78
78
79 :param ip:
79 :param ip:
80 """
80 """
81 def is_ipv6(ip_addr):
81 def is_ipv6(ip_addr):
82 if hasattr(socket, 'inet_pton'):
82 if hasattr(socket, 'inet_pton'):
83 try:
83 try:
84 socket.inet_pton(socket.AF_INET6, ip_addr)
84 socket.inet_pton(socket.AF_INET6, ip_addr)
85 except socket.error:
85 except socket.error:
86 return False
86 return False
87 else:
87 else:
88 # fallback to ipaddress
88 # fallback to ipaddress
89 try:
89 try:
90 ipaddress.IPv6Address(safe_unicode(ip_addr))
90 ipaddress.IPv6Address(safe_unicode(ip_addr))
91 except Exception:
91 except Exception:
92 return False
92 return False
93 return True
93 return True
94
94
95 if ':' not in ip: # must be ipv4 pure ip
95 if ':' not in ip: # must be ipv4 pure ip
96 return ip
96 return ip
97
97
98 if '[' in ip and ']' in ip: # ipv6 with port
98 if '[' in ip and ']' in ip: # ipv6 with port
99 return ip.split(']')[0][1:].lower()
99 return ip.split(']')[0][1:].lower()
100
100
101 # must be ipv6 or ipv4 with port
101 # must be ipv6 or ipv4 with port
102 if is_ipv6(ip):
102 if is_ipv6(ip):
103 return ip
103 return ip
104 else:
104 else:
105 ip, _port = ip.split(':')[:2] # means ipv4+port
105 ip, _port = ip.split(':')[:2] # means ipv4+port
106 return ip
106 return ip
107
107
108
108
109 def get_ip_addr(environ):
109 def get_ip_addr(environ):
110 proxy_key = 'HTTP_X_REAL_IP'
110 proxy_key = 'HTTP_X_REAL_IP'
111 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
111 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
112 def_key = 'REMOTE_ADDR'
112 def_key = 'REMOTE_ADDR'
113 _filters = lambda x: _filter_port(_filter_proxy(x))
113 _filters = lambda x: _filter_port(_filter_proxy(x))
114
114
115 ip = environ.get(proxy_key)
115 ip = environ.get(proxy_key)
116 if ip:
116 if ip:
117 return _filters(ip)
117 return _filters(ip)
118
118
119 ip = environ.get(proxy_key2)
119 ip = environ.get(proxy_key2)
120 if ip:
120 if ip:
121 return _filters(ip)
121 return _filters(ip)
122
122
123 ip = environ.get(def_key, '0.0.0.0')
123 ip = environ.get(def_key, '0.0.0.0')
124 return _filters(ip)
124 return _filters(ip)
125
125
126
126
127 def get_server_ip_addr(environ, log_errors=True):
127 def get_server_ip_addr(environ, log_errors=True):
128 hostname = environ.get('SERVER_NAME')
128 hostname = environ.get('SERVER_NAME')
129 try:
129 try:
130 return socket.gethostbyname(hostname)
130 return socket.gethostbyname(hostname)
131 except Exception as e:
131 except Exception as e:
132 if log_errors:
132 if log_errors:
133 # in some cases this lookup is not possible, and we don't want to
133 # in some cases this lookup is not possible, and we don't want to
134 # make it an exception in logs
134 # make it an exception in logs
135 log.exception('Could not retrieve server ip address: %s', e)
135 log.exception('Could not retrieve server ip address: %s', e)
136 return hostname
136 return hostname
137
137
138
138
139 def get_server_port(environ):
139 def get_server_port(environ):
140 return environ.get('SERVER_PORT')
140 return environ.get('SERVER_PORT')
141
141
142
142
143 def get_access_path(environ):
143 def get_access_path(environ):
144 path = environ.get('PATH_INFO')
144 path = environ.get('PATH_INFO')
145 org_req = environ.get('pylons.original_request')
145 org_req = environ.get('pylons.original_request')
146 if org_req:
146 if org_req:
147 path = org_req.environ.get('PATH_INFO')
147 path = org_req.environ.get('PATH_INFO')
148 return path
148 return path
149
149
150
150
151 def get_user_agent(environ):
151 def get_user_agent(environ):
152 return environ.get('HTTP_USER_AGENT')
152 return environ.get('HTTP_USER_AGENT')
153
153
154
154
155 def vcs_operation_context(
155 def vcs_operation_context(
156 environ, repo_name, username, action, scm, check_locking=True,
156 environ, repo_name, username, action, scm, check_locking=True,
157 is_shadow_repo=False, check_branch_perms=False, detect_force_push=False):
157 is_shadow_repo=False, check_branch_perms=False, detect_force_push=False):
158 """
158 """
159 Generate the context for a vcs operation, e.g. push or pull.
159 Generate the context for a vcs operation, e.g. push or pull.
160
160
161 This context is passed over the layers so that hooks triggered by the
161 This context is passed over the layers so that hooks triggered by the
162 vcs operation know details like the user, the user's IP address etc.
162 vcs operation know details like the user, the user's IP address etc.
163
163
164 :param check_locking: Allows to switch of the computation of the locking
164 :param check_locking: Allows to switch of the computation of the locking
165 data. This serves mainly the need of the simplevcs middleware to be
165 data. This serves mainly the need of the simplevcs middleware to be
166 able to disable this for certain operations.
166 able to disable this for certain operations.
167
167
168 """
168 """
169 # Tri-state value: False: unlock, None: nothing, True: lock
169 # Tri-state value: False: unlock, None: nothing, True: lock
170 make_lock = None
170 make_lock = None
171 locked_by = [None, None, None]
171 locked_by = [None, None, None]
172 is_anonymous = username == User.DEFAULT_USER
172 is_anonymous = username == User.DEFAULT_USER
173 user = User.get_by_username(username)
173 user = User.get_by_username(username)
174 if not is_anonymous and check_locking:
174 if not is_anonymous and check_locking:
175 log.debug('Checking locking on repository "%s"', repo_name)
175 log.debug('Checking locking on repository "%s"', repo_name)
176 repo = Repository.get_by_repo_name(repo_name)
176 repo = Repository.get_by_repo_name(repo_name)
177 make_lock, __, locked_by = repo.get_locking_state(
177 make_lock, __, locked_by = repo.get_locking_state(
178 action, user.user_id)
178 action, user.user_id)
179 user_id = user.user_id
179 user_id = user.user_id
180 settings_model = VcsSettingsModel(repo=repo_name)
180 settings_model = VcsSettingsModel(repo=repo_name)
181 ui_settings = settings_model.get_ui_settings()
181 ui_settings = settings_model.get_ui_settings()
182
182
183 # NOTE(marcink): This should be also in sync with
183 # NOTE(marcink): This should be also in sync with
184 # rhodecode/apps/ssh_support/lib/backends/base.py:update_environment scm_data
184 # rhodecode/apps/ssh_support/lib/backends/base.py:update_environment scm_data
185 store = [x for x in ui_settings if x.key == '/']
185 store = [x for x in ui_settings if x.key == '/']
186 repo_store = ''
186 repo_store = ''
187 if store:
187 if store:
188 repo_store = store[0].value
188 repo_store = store[0].value
189
189
190 scm_data = {
190 scm_data = {
191 'ip': get_ip_addr(environ),
191 'ip': get_ip_addr(environ),
192 'username': username,
192 'username': username,
193 'user_id': user_id,
193 'user_id': user_id,
194 'action': action,
194 'action': action,
195 'repository': repo_name,
195 'repository': repo_name,
196 'scm': scm,
196 'scm': scm,
197 'config': rhodecode.CONFIG['__file__'],
197 'config': rhodecode.CONFIG['__file__'],
198 'repo_store': repo_store,
198 'repo_store': repo_store,
199 'make_lock': make_lock,
199 'make_lock': make_lock,
200 'locked_by': locked_by,
200 'locked_by': locked_by,
201 'server_url': utils2.get_server_url(environ),
201 'server_url': utils2.get_server_url(environ),
202 'user_agent': get_user_agent(environ),
202 'user_agent': get_user_agent(environ),
203 'hooks': get_enabled_hook_classes(ui_settings),
203 'hooks': get_enabled_hook_classes(ui_settings),
204 'is_shadow_repo': is_shadow_repo,
204 'is_shadow_repo': is_shadow_repo,
205 'detect_force_push': detect_force_push,
205 'detect_force_push': detect_force_push,
206 'check_branch_perms': check_branch_perms,
206 'check_branch_perms': check_branch_perms,
207 }
207 }
208 return scm_data
208 return scm_data
209
209
210
210
211 class BasicAuth(AuthBasicAuthenticator):
211 class BasicAuth(AuthBasicAuthenticator):
212
212
213 def __init__(self, realm, authfunc, registry, auth_http_code=None,
213 def __init__(self, realm, authfunc, registry, auth_http_code=None,
214 initial_call_detection=False, acl_repo_name=None):
214 initial_call_detection=False, acl_repo_name=None):
215 self.realm = realm
215 self.realm = realm
216 self.initial_call = initial_call_detection
216 self.initial_call = initial_call_detection
217 self.authfunc = authfunc
217 self.authfunc = authfunc
218 self.registry = registry
218 self.registry = registry
219 self.acl_repo_name = acl_repo_name
219 self.acl_repo_name = acl_repo_name
220 self._rc_auth_http_code = auth_http_code
220 self._rc_auth_http_code = auth_http_code
221
221
222 def _get_response_from_code(self, http_code):
222 def _get_response_from_code(self, http_code):
223 try:
223 try:
224 return get_exception(safe_int(http_code))
224 return get_exception(safe_int(http_code))
225 except Exception:
225 except Exception:
226 log.exception('Failed to fetch response for code %s', http_code)
226 log.exception('Failed to fetch response for code %s', http_code)
227 return HTTPForbidden
227 return HTTPForbidden
228
228
229 def get_rc_realm(self):
229 def get_rc_realm(self):
230 return safe_str(self.registry.rhodecode_settings.get('rhodecode_realm'))
230 return safe_str(self.registry.rhodecode_settings.get('rhodecode_realm'))
231
231
232 def build_authentication(self):
232 def build_authentication(self):
233 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
233 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
234 if self._rc_auth_http_code and not self.initial_call:
234 if self._rc_auth_http_code and not self.initial_call:
235 # return alternative HTTP code if alternative http return code
235 # return alternative HTTP code if alternative http return code
236 # is specified in RhodeCode config, but ONLY if it's not the
236 # is specified in RhodeCode config, but ONLY if it's not the
237 # FIRST call
237 # FIRST call
238 custom_response_klass = self._get_response_from_code(
238 custom_response_klass = self._get_response_from_code(
239 self._rc_auth_http_code)
239 self._rc_auth_http_code)
240 return custom_response_klass(headers=head)
240 return custom_response_klass(headers=head)
241 return HTTPUnauthorized(headers=head)
241 return HTTPUnauthorized(headers=head)
242
242
243 def authenticate(self, environ):
243 def authenticate(self, environ):
244 authorization = AUTHORIZATION(environ)
244 authorization = AUTHORIZATION(environ)
245 if not authorization:
245 if not authorization:
246 return self.build_authentication()
246 return self.build_authentication()
247 (authmeth, auth) = authorization.split(' ', 1)
247 (authmeth, auth) = authorization.split(' ', 1)
248 if 'basic' != authmeth.lower():
248 if 'basic' != authmeth.lower():
249 return self.build_authentication()
249 return self.build_authentication()
250 auth = auth.strip().decode('base64')
250 auth = auth.strip().decode('base64')
251 _parts = auth.split(':', 1)
251 _parts = auth.split(':', 1)
252 if len(_parts) == 2:
252 if len(_parts) == 2:
253 username, password = _parts
253 username, password = _parts
254 auth_data = self.authfunc(
254 auth_data = self.authfunc(
255 username, password, environ, VCS_TYPE,
255 username, password, environ, VCS_TYPE,
256 registry=self.registry, acl_repo_name=self.acl_repo_name)
256 registry=self.registry, acl_repo_name=self.acl_repo_name)
257 if auth_data:
257 if auth_data:
258 return {'username': username, 'auth_data': auth_data}
258 return {'username': username, 'auth_data': auth_data}
259 if username and password:
259 if username and password:
260 # we mark that we actually executed authentication once, at
260 # we mark that we actually executed authentication once, at
261 # that point we can use the alternative auth code
261 # that point we can use the alternative auth code
262 self.initial_call = False
262 self.initial_call = False
263
263
264 return self.build_authentication()
264 return self.build_authentication()
265
265
266 __call__ = authenticate
266 __call__ = authenticate
267
267
268
268
269 def calculate_version_hash(config):
269 def calculate_version_hash(config):
270 return sha1(
270 return sha1(
271 config.get('beaker.session.secret', '') +
271 config.get('beaker.session.secret', '') +
272 rhodecode.__version__)[:8]
272 rhodecode.__version__)[:8]
273
273
274
274
275 def get_current_lang(request):
275 def get_current_lang(request):
276 # NOTE(marcink): remove after pyramid move
276 # NOTE(marcink): remove after pyramid move
277 try:
277 try:
278 return translation.get_lang()[0]
278 return translation.get_lang()[0]
279 except:
279 except:
280 pass
280 pass
281
281
282 return getattr(request, '_LOCALE_', request.locale_name)
282 return getattr(request, '_LOCALE_', request.locale_name)
283
283
284
284
285 def attach_context_attributes(context, request, user_id=None):
285 def attach_context_attributes(context, request, user_id=None):
286 """
286 """
287 Attach variables into template context called `c`.
287 Attach variables into template context called `c`.
288 """
288 """
289 config = request.registry.settings
289 config = request.registry.settings
290
290
291
292 rc_config = SettingsModel().get_all_settings(cache=True)
291 rc_config = SettingsModel().get_all_settings(cache=True)
293
292
294 context.rhodecode_version = rhodecode.__version__
293 context.rhodecode_version = rhodecode.__version__
295 context.rhodecode_edition = config.get('rhodecode.edition')
294 context.rhodecode_edition = config.get('rhodecode.edition')
296 # unique secret + version does not leak the version but keep consistency
295 # unique secret + version does not leak the version but keep consistency
297 context.rhodecode_version_hash = calculate_version_hash(config)
296 context.rhodecode_version_hash = calculate_version_hash(config)
298
297
299 # Default language set for the incoming request
298 # Default language set for the incoming request
300 context.language = get_current_lang(request)
299 context.language = get_current_lang(request)
301
300
302 # Visual options
301 # Visual options
303 context.visual = AttributeDict({})
302 context.visual = AttributeDict({})
304
303
305 # DB stored Visual Items
304 # DB stored Visual Items
306 context.visual.show_public_icon = str2bool(
305 context.visual.show_public_icon = str2bool(
307 rc_config.get('rhodecode_show_public_icon'))
306 rc_config.get('rhodecode_show_public_icon'))
308 context.visual.show_private_icon = str2bool(
307 context.visual.show_private_icon = str2bool(
309 rc_config.get('rhodecode_show_private_icon'))
308 rc_config.get('rhodecode_show_private_icon'))
310 context.visual.stylify_metatags = str2bool(
309 context.visual.stylify_metatags = str2bool(
311 rc_config.get('rhodecode_stylify_metatags'))
310 rc_config.get('rhodecode_stylify_metatags'))
312 context.visual.dashboard_items = safe_int(
311 context.visual.dashboard_items = safe_int(
313 rc_config.get('rhodecode_dashboard_items', 100))
312 rc_config.get('rhodecode_dashboard_items', 100))
314 context.visual.admin_grid_items = safe_int(
313 context.visual.admin_grid_items = safe_int(
315 rc_config.get('rhodecode_admin_grid_items', 100))
314 rc_config.get('rhodecode_admin_grid_items', 100))
316 context.visual.show_revision_number = str2bool(
315 context.visual.show_revision_number = str2bool(
317 rc_config.get('rhodecode_show_revision_number', True))
316 rc_config.get('rhodecode_show_revision_number', True))
318 context.visual.show_sha_length = safe_int(
317 context.visual.show_sha_length = safe_int(
319 rc_config.get('rhodecode_show_sha_length', 100))
318 rc_config.get('rhodecode_show_sha_length', 100))
320 context.visual.repository_fields = str2bool(
319 context.visual.repository_fields = str2bool(
321 rc_config.get('rhodecode_repository_fields'))
320 rc_config.get('rhodecode_repository_fields'))
322 context.visual.show_version = str2bool(
321 context.visual.show_version = str2bool(
323 rc_config.get('rhodecode_show_version'))
322 rc_config.get('rhodecode_show_version'))
324 context.visual.use_gravatar = str2bool(
323 context.visual.use_gravatar = str2bool(
325 rc_config.get('rhodecode_use_gravatar'))
324 rc_config.get('rhodecode_use_gravatar'))
326 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
325 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
327 context.visual.default_renderer = rc_config.get(
326 context.visual.default_renderer = rc_config.get(
328 'rhodecode_markup_renderer', 'rst')
327 'rhodecode_markup_renderer', 'rst')
329 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
328 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
330 context.visual.rhodecode_support_url = \
329 context.visual.rhodecode_support_url = \
331 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
330 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
332
331
333 context.visual.affected_files_cut_off = 60
332 context.visual.affected_files_cut_off = 60
334
333
335 context.pre_code = rc_config.get('rhodecode_pre_code')
334 context.pre_code = rc_config.get('rhodecode_pre_code')
336 context.post_code = rc_config.get('rhodecode_post_code')
335 context.post_code = rc_config.get('rhodecode_post_code')
337 context.rhodecode_name = rc_config.get('rhodecode_title')
336 context.rhodecode_name = rc_config.get('rhodecode_title')
338 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
337 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
339 # if we have specified default_encoding in the request, it has more
338 # if we have specified default_encoding in the request, it has more
340 # priority
339 # priority
341 if request.GET.get('default_encoding'):
340 if request.GET.get('default_encoding'):
342 context.default_encodings.insert(0, request.GET.get('default_encoding'))
341 context.default_encodings.insert(0, request.GET.get('default_encoding'))
343 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
342 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
344 context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl')
343 context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl')
345
344
346 # INI stored
345 # INI stored
347 context.labs_active = str2bool(
346 context.labs_active = str2bool(
348 config.get('labs_settings_active', 'false'))
347 config.get('labs_settings_active', 'false'))
349 context.ssh_enabled = str2bool(
348 context.ssh_enabled = str2bool(
350 config.get('ssh.generate_authorized_keyfile', 'false'))
349 config.get('ssh.generate_authorized_keyfile', 'false'))
351 context.ssh_key_generator_enabled = str2bool(
350 context.ssh_key_generator_enabled = str2bool(
352 config.get('ssh.enable_ui_key_generator', 'true'))
351 config.get('ssh.enable_ui_key_generator', 'true'))
353
352
354 context.visual.allow_repo_location_change = str2bool(
353 context.visual.allow_repo_location_change = str2bool(
355 config.get('allow_repo_location_change', True))
354 config.get('allow_repo_location_change', True))
356 context.visual.allow_custom_hooks_settings = str2bool(
355 context.visual.allow_custom_hooks_settings = str2bool(
357 config.get('allow_custom_hooks_settings', True))
356 config.get('allow_custom_hooks_settings', True))
358 context.debug_style = str2bool(config.get('debug_style', False))
357 context.debug_style = str2bool(config.get('debug_style', False))
359
358
360 context.rhodecode_instanceid = config.get('instance_id')
359 context.rhodecode_instanceid = config.get('instance_id')
361
360
362 context.visual.cut_off_limit_diff = safe_int(
361 context.visual.cut_off_limit_diff = safe_int(
363 config.get('cut_off_limit_diff'))
362 config.get('cut_off_limit_diff'))
364 context.visual.cut_off_limit_file = safe_int(
363 context.visual.cut_off_limit_file = safe_int(
365 config.get('cut_off_limit_file'))
364 config.get('cut_off_limit_file'))
366
365
367 # AppEnlight
366 # AppEnlight
368 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
367 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
369 context.appenlight_api_public_key = config.get(
368 context.appenlight_api_public_key = config.get(
370 'appenlight.api_public_key', '')
369 'appenlight.api_public_key', '')
371 context.appenlight_server_url = config.get('appenlight.server_url', '')
370 context.appenlight_server_url = config.get('appenlight.server_url', '')
372
371
373 diffmode = {
372 diffmode = {
374 "unified": "unified",
373 "unified": "unified",
375 "sideside": "sideside"
374 "sideside": "sideside"
376 }.get(request.GET.get('diffmode'))
375 }.get(request.GET.get('diffmode'))
377
376
378 if diffmode and diffmode != request.session.get('rc_user_session_attr.diffmode'):
377 if diffmode and diffmode != request.session.get('rc_user_session_attr.diffmode'):
379 request.session['rc_user_session_attr.diffmode'] = diffmode
378 request.session['rc_user_session_attr.diffmode'] = diffmode
380
379
381 # session settings per user
380 # session settings per user
382 session_attrs = {
381 session_attrs = {
383 # defaults
382 # defaults
384 "clone_url_format": "http",
383 "clone_url_format": "http",
385 "diffmode": "sideside"
384 "diffmode": "sideside"
386 }
385 }
387 for k, v in request.session.items():
386 for k, v in request.session.items():
388 pref = 'rc_user_session_attr.'
387 pref = 'rc_user_session_attr.'
389 if k and k.startswith(pref):
388 if k and k.startswith(pref):
390 k = k[len(pref):]
389 k = k[len(pref):]
391 session_attrs[k] = v
390 session_attrs[k] = v
392
391
393 context.user_session_attrs = session_attrs
392 context.user_session_attrs = session_attrs
394
393
395 # JS template context
394 # JS template context
396 context.template_context = {
395 context.template_context = {
397 'repo_name': None,
396 'repo_name': None,
398 'repo_type': None,
397 'repo_type': None,
399 'repo_landing_commit': None,
398 'repo_landing_commit': None,
400 'rhodecode_user': {
399 'rhodecode_user': {
401 'username': None,
400 'username': None,
402 'email': None,
401 'email': None,
403 'notification_status': False
402 'notification_status': False
404 },
403 },
405 'session_attrs': session_attrs,
404 'session_attrs': session_attrs,
406 'visual': {
405 'visual': {
407 'default_renderer': None
406 'default_renderer': None
408 },
407 },
409 'commit_data': {
408 'commit_data': {
410 'commit_id': None
409 'commit_id': None
411 },
410 },
412 'pull_request_data': {'pull_request_id': None},
411 'pull_request_data': {'pull_request_id': None},
413 'timeago': {
412 'timeago': {
414 'refresh_time': 120 * 1000,
413 'refresh_time': 120 * 1000,
415 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
414 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
416 },
415 },
417 'pyramid_dispatch': {
416 'pyramid_dispatch': {
418
417
419 },
418 },
420 'extra': {'plugins': {}}
419 'extra': {'plugins': {}}
421 }
420 }
422 # END CONFIG VARS
421 # END CONFIG VARS
423
422
424 context.csrf_token = auth.get_csrf_token(session=request.session)
423 context.csrf_token = auth.get_csrf_token(session=request.session)
425 context.backends = rhodecode.BACKENDS.keys()
424 context.backends = rhodecode.BACKENDS.keys()
426 context.backends.sort()
425 context.backends.sort()
427 unread_count = 0
426 unread_count = 0
428 user_bookmark_list = []
427 user_bookmark_list = []
429 if user_id:
428 if user_id:
430 unread_count = NotificationModel().get_unread_cnt_for_user(user_id)
429 unread_count = NotificationModel().get_unread_cnt_for_user(user_id)
431 user_bookmark_list = UserBookmark.get_bookmarks_for_user(user_id)
430 user_bookmark_list = UserBookmark.get_bookmarks_for_user(user_id)
432 context.unread_notifications = unread_count
431 context.unread_notifications = unread_count
433 context.bookmark_items = user_bookmark_list
432 context.bookmark_items = user_bookmark_list
434
433
435 # web case
434 # web case
436 if hasattr(request, 'user'):
435 if hasattr(request, 'user'):
437 context.auth_user = request.user
436 context.auth_user = request.user
438 context.rhodecode_user = request.user
437 context.rhodecode_user = request.user
439
438
440 # api case
439 # api case
441 if hasattr(request, 'rpc_user'):
440 if hasattr(request, 'rpc_user'):
442 context.auth_user = request.rpc_user
441 context.auth_user = request.rpc_user
443 context.rhodecode_user = request.rpc_user
442 context.rhodecode_user = request.rpc_user
444
443
445 # attach the whole call context to the request
444 # attach the whole call context to the request
446 request.call_context = context
445 request.call_context = context
447
446
448
447
449 def get_auth_user(request):
448 def get_auth_user(request):
450 environ = request.environ
449 environ = request.environ
451 session = request.session
450 session = request.session
452
451
453 ip_addr = get_ip_addr(environ)
452 ip_addr = get_ip_addr(environ)
454 # make sure that we update permissions each time we call controller
453 # make sure that we update permissions each time we call controller
455 _auth_token = (request.GET.get('auth_token', '') or
454 _auth_token = (request.GET.get('auth_token', '') or
456 request.GET.get('api_key', ''))
455 request.GET.get('api_key', ''))
457
456
458 if _auth_token:
457 if _auth_token:
459 # when using API_KEY we assume user exists, and
458 # when using API_KEY we assume user exists, and
460 # doesn't need auth based on cookies.
459 # doesn't need auth based on cookies.
461 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
460 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
462 authenticated = False
461 authenticated = False
463 else:
462 else:
464 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
463 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
465 try:
464 try:
466 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
465 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
467 ip_addr=ip_addr)
466 ip_addr=ip_addr)
468 except UserCreationError as e:
467 except UserCreationError as e:
469 h.flash(e, 'error')
468 h.flash(e, 'error')
470 # container auth or other auth functions that create users
469 # container auth or other auth functions that create users
471 # on the fly can throw this exception signaling that there's
470 # on the fly can throw this exception signaling that there's
472 # issue with user creation, explanation should be provided
471 # issue with user creation, explanation should be provided
473 # in Exception itself. We then create a simple blank
472 # in Exception itself. We then create a simple blank
474 # AuthUser
473 # AuthUser
475 auth_user = AuthUser(ip_addr=ip_addr)
474 auth_user = AuthUser(ip_addr=ip_addr)
476
475
477 # in case someone changes a password for user it triggers session
476 # in case someone changes a password for user it triggers session
478 # flush and forces a re-login
477 # flush and forces a re-login
479 if password_changed(auth_user, session):
478 if password_changed(auth_user, session):
480 session.invalidate()
479 session.invalidate()
481 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
480 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
482 auth_user = AuthUser(ip_addr=ip_addr)
481 auth_user = AuthUser(ip_addr=ip_addr)
483
482
484 authenticated = cookie_store.get('is_authenticated')
483 authenticated = cookie_store.get('is_authenticated')
485
484
486 if not auth_user.is_authenticated and auth_user.is_user_object:
485 if not auth_user.is_authenticated and auth_user.is_user_object:
487 # user is not authenticated and not empty
486 # user is not authenticated and not empty
488 auth_user.set_authenticated(authenticated)
487 auth_user.set_authenticated(authenticated)
489
488
490 return auth_user
489 return auth_user
491
490
492
491
493 def h_filter(s):
492 def h_filter(s):
494 """
493 """
495 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
494 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
496 we wrap this with additional functionality that converts None to empty
495 we wrap this with additional functionality that converts None to empty
497 strings
496 strings
498 """
497 """
499 if s is None:
498 if s is None:
500 return markupsafe.Markup()
499 return markupsafe.Markup()
501 return markupsafe.escape(s)
500 return markupsafe.escape(s)
502
501
503
502
504 def add_events_routes(config):
503 def add_events_routes(config):
505 """
504 """
506 Adds routing that can be used in events. Because some events are triggered
505 Adds routing that can be used in events. Because some events are triggered
507 outside of pyramid context, we need to bootstrap request with some
506 outside of pyramid context, we need to bootstrap request with some
508 routing registered
507 routing registered
509 """
508 """
510
509
511 from rhodecode.apps._base import ADMIN_PREFIX
510 from rhodecode.apps._base import ADMIN_PREFIX
512
511
513 config.add_route(name='home', pattern='/')
512 config.add_route(name='home', pattern='/')
514
513
515 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
514 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
516 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
515 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
517 config.add_route(name='repo_summary', pattern='/{repo_name}')
516 config.add_route(name='repo_summary', pattern='/{repo_name}')
518 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
517 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
519 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
518 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
520
519
521 config.add_route(name='pullrequest_show',
520 config.add_route(name='pullrequest_show',
522 pattern='/{repo_name}/pull-request/{pull_request_id}')
521 pattern='/{repo_name}/pull-request/{pull_request_id}')
523 config.add_route(name='pull_requests_global',
522 config.add_route(name='pull_requests_global',
524 pattern='/pull-request/{pull_request_id}')
523 pattern='/pull-request/{pull_request_id}')
525 config.add_route(name='repo_commit',
524 config.add_route(name='repo_commit',
526 pattern='/{repo_name}/changeset/{commit_id}')
525 pattern='/{repo_name}/changeset/{commit_id}')
527
526
528 config.add_route(name='repo_files',
527 config.add_route(name='repo_files',
529 pattern='/{repo_name}/files/{commit_id}/{f_path}')
528 pattern='/{repo_name}/files/{commit_id}/{f_path}')
530
529
531
530
532 def bootstrap_config(request):
531 def bootstrap_config(request):
533 import pyramid.testing
532 import pyramid.testing
534 registry = pyramid.testing.Registry('RcTestRegistry')
533 registry = pyramid.testing.Registry('RcTestRegistry')
535
534
536 config = pyramid.testing.setUp(registry=registry, request=request)
535 config = pyramid.testing.setUp(registry=registry, request=request)
537
536
538 # allow pyramid lookup in testing
537 # allow pyramid lookup in testing
539 config.include('pyramid_mako')
538 config.include('pyramid_mako')
540 config.include('pyramid_beaker')
539 config.include('pyramid_beaker')
541 config.include('rhodecode.lib.rc_cache')
540 config.include('rhodecode.lib.rc_cache')
542
541
543 add_events_routes(config)
542 add_events_routes(config)
544
543
545 return config
544 return config
546
545
547
546
548 def bootstrap_request(**kwargs):
547 def bootstrap_request(**kwargs):
549 import pyramid.testing
548 import pyramid.testing
550
549
551 class TestRequest(pyramid.testing.DummyRequest):
550 class TestRequest(pyramid.testing.DummyRequest):
552 application_url = kwargs.pop('application_url', 'http://example.com')
551 application_url = kwargs.pop('application_url', 'http://example.com')
553 host = kwargs.pop('host', 'example.com:80')
552 host = kwargs.pop('host', 'example.com:80')
554 domain = kwargs.pop('domain', 'example.com')
553 domain = kwargs.pop('domain', 'example.com')
555
554
556 def translate(self, msg):
555 def translate(self, msg):
557 return msg
556 return msg
558
557
559 def plularize(self, singular, plural, n):
558 def plularize(self, singular, plural, n):
560 return singular
559 return singular
561
560
562 def get_partial_renderer(self, tmpl_name):
561 def get_partial_renderer(self, tmpl_name):
563
562
564 from rhodecode.lib.partial_renderer import get_partial_renderer
563 from rhodecode.lib.partial_renderer import get_partial_renderer
565 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
564 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
566
565
567 _call_context = TemplateArgs()
566 _call_context = TemplateArgs()
568 _call_context.visual = TemplateArgs()
567 _call_context.visual = TemplateArgs()
569 _call_context.visual.show_sha_length = 12
568 _call_context.visual.show_sha_length = 12
570 _call_context.visual.show_revision_number = True
569 _call_context.visual.show_revision_number = True
571
570
572 @property
571 @property
573 def call_context(self):
572 def call_context(self):
574 return self._call_context
573 return self._call_context
575
574
576 class TestDummySession(pyramid.testing.DummySession):
575 class TestDummySession(pyramid.testing.DummySession):
577 def save(*arg, **kw):
576 def save(*arg, **kw):
578 pass
577 pass
579
578
580 request = TestRequest(**kwargs)
579 request = TestRequest(**kwargs)
581 request.session = TestDummySession()
580 request.session = TestDummySession()
582
581
583 return request
582 return request
584
583
@@ -1,150 +1,150 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 %if c.show_private:
5 %if c.show_private:
6 ${_('Private Gists for user %s') % c.rhodecode_user.username}
6 ${_('Private Gists for user %s') % c.rhodecode_user.username}
7 %elif c.show_public:
7 %elif c.show_public:
8 ${_('Public Gists for user %s') % c.rhodecode_user.username}
8 ${_('Public Gists for user %s') % c.rhodecode_user.username}
9 %else:
9 %else:
10 ${_('Public Gists')}
10 ${_('Public Gists')}
11 %endif
11 %endif
12 %if c.rhodecode_name:
12 %if c.rhodecode_name:
13 &middot; ${h.branding(c.rhodecode_name)}
13 &middot; ${h.branding(c.rhodecode_name)}
14 %endif
14 %endif
15 </%def>
15 </%def>
16
16
17 <%def name="breadcrumbs_links()">
17 <%def name="breadcrumbs_links()">
18 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
18 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
19 %if c.show_private and not c.show_public:
19 %if c.show_private and not c.show_public:
20 ${_('Private Gists for user %s') % c.rhodecode_user.username}
20 ${_('Private Gists for user %s') % c.rhodecode_user.username}
21 %elif c.show_public and not c.show_private:
21 %elif c.show_public and not c.show_private:
22 ${_('Public Gists for user %s') % c.rhodecode_user.username}
22 ${_('Public Gists for user %s') % c.rhodecode_user.username}
23 %elif c.show_public and c.show_private:
23 %elif c.show_public and c.show_private:
24 ${_('All Gists for user %s') % c.rhodecode_user.username}
24 ${_('All Gists for user %s') % c.rhodecode_user.username}
25 %else:
25 %else:
26 ${_('All Public Gists')}
26 ${_('All Public Gists')}
27 %endif
27 %endif
28 - <span id="gists_count">0</span>
28 - <span id="gists_count">0</span>
29 </%def>
29 </%def>
30
30
31 <%def name="menu_bar_nav()">
31 <%def name="menu_bar_nav()">
32 ${self.menu_items(active='gists')}
32 ${self.menu_items(active='gists')}
33 </%def>
33 </%def>
34
34
35
35
36
36
37 <%def name="main()">
37 <%def name="main()">
38 <div class="box">
38 <div class="box">
39 <div class="title">
39 <div class="title">
40 ${self.breadcrumbs(class_="breadcrumbs block-left")}
40 ${self.breadcrumbs(class_="breadcrumbs block-left")}
41 %if c.rhodecode_user.username != h.DEFAULT_USER:
41 %if c.rhodecode_user.username != h.DEFAULT_USER:
42 <ul class="links block-right">
42 <ul class="links block-right">
43 <li>
43 <li>
44 <a href="${h.route_path('gists_new')}" class="btn btn-primary">${_(u'Create New Gist')}</a>
44 <a href="${h.route_path('gists_new')}" class="btn btn-primary">${_(u'Create New Gist')}</a>
45 </li>
45 </li>
46 </ul>
46 </ul>
47 %endif
47 %endif
48 </div>
48 </div>
49
49
50
50
51 <div class="sidebar-col-wrapper scw-small">
51 <div class="sidebar-col-wrapper scw-small">
52 ##main
52 ##main
53 <div class="sidebar">
53 <div class="sidebar">
54 <ul class="nav nav-pills nav-stacked">
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 <li class="${'active' if c.active=='all' else ''}"><a href="${h.route_path('gists_show', _query={'all': 1})}">${_('All gists')}</a></li>
56 <li class="${'active' if c.active=='all' else ''}"><a href="${h.route_path('gists_show', _query={'all': 1})}">${_('All gists')}</a></li>
57 %endif
57 %endif
58 <li class="${'active' if c.active=='public' else ''}"><a href="${h.route_path('gists_show')}">${_('All public')}</a></li>
58 <li class="${'active' if c.active=='public' else ''}"><a href="${h.route_path('gists_show')}">${_('All public')}</a></li>
59 %if c.rhodecode_user.username != h.DEFAULT_USER:
59 %if c.rhodecode_user.username != h.DEFAULT_USER:
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>
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 <li class="${'active' if c.active=='my_private' else ''}"><a href="${h.route_path('gists_show', _query={'private': 1})}">${_('My private')}</a></li>
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 <li class="${'active' if c.active=='my_public' else ''}"><a href="${h.route_path('gists_show', _query={'public': 1})}">${_('My public')}</a></li>
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 %endif
63 %endif
64 </ul>
64 </ul>
65 </div>
65 </div>
66
66
67 <div class="main-content">
67 <div class="main-content">
68 <div id="repos_list_wrap">
68 <div id="repos_list_wrap">
69 <table id="gist_list_table" class="display"></table>
69 <table id="gist_list_table" class="display"></table>
70 </div>
70 </div>
71 </div>
71 </div>
72 </div>
72 </div>
73 </div>
73 </div>
74 <script>
74 <script>
75 $(document).ready(function() {
75 $(document).ready(function() {
76
76
77 var get_datatable_count = function(){
77 var get_datatable_count = function(){
78 var api = $('#gist_list_table').dataTable().api();
78 var api = $('#gist_list_table').dataTable().api();
79 $('#gists_count').text(api.page.info().recordsDisplay);
79 $('#gists_count').text(api.page.info().recordsDisplay);
80 };
80 };
81
81
82
82
83 // custom filter that filters by access_id, description or author
83 // custom filter that filters by access_id, description or author
84 $.fn.dataTable.ext.search.push(
84 $.fn.dataTable.ext.search.push(
85 function( settings, data, dataIndex ) {
85 function( settings, data, dataIndex ) {
86 var query = $('#q_filter').val();
86 var query = $('#q_filter').val();
87 var author = data[0].strip();
87 var author = data[0].strip();
88 var access_id = data[2].strip();
88 var access_id = data[2].strip();
89 var description = data[3].strip();
89 var description = data[3].strip();
90
90
91 var query_str = (access_id + " " + author + " " + description).toLowerCase();
91 var query_str = (access_id + " " + author + " " + description).toLowerCase();
92
92
93 if(query_str.indexOf(query.toLowerCase()) !== -1){
93 if(query_str.indexOf(query.toLowerCase()) !== -1){
94 return true;
94 return true;
95 }
95 }
96 return false;
96 return false;
97 }
97 }
98 );
98 );
99
99
100 // gists list
100 // gists list
101 $('#gist_list_table').DataTable({
101 $('#gist_list_table').DataTable({
102 data: ${c.data|n},
102 data: ${c.data|n},
103 dom: 'rtp',
103 dom: 'rtp',
104 pageLength: ${c.visual.dashboard_items},
104 pageLength: ${c.visual.dashboard_items},
105 order: [[ 4, "desc" ]],
105 order: [[ 4, "desc" ]],
106 columns: [
106 columns: [
107 { data: {"_": "author",
107 { data: {"_": "author",
108 "sort": "author_raw"}, title: "${_("Author")}", width: "250px", className: "td-user" },
108 "sort": "author_raw"}, title: "${_("Author")}", width: "250px", className: "td-user" },
109 { data: {"_": "type",
109 { data: {"_": "type",
110 "sort": "type"}, title: "${_("Type")}", width: "70px", className: "td-tags" },
110 "sort": "type"}, title: "${_("Type")}", width: "70px", className: "td-tags" },
111 { data: {"_": "access_id",
111 { data: {"_": "access_id",
112 "sort": "access_id"}, title: "${_("Name")}", width:"150px", className: "td-componentname" },
112 "sort": "access_id"}, title: "${_("Name")}", width:"150px", className: "td-componentname" },
113 { data: {"_": "description",
113 { data: {"_": "description",
114 "sort": "description"}, title: "${_("Description")}", width: "250px", className: "td-description" },
114 "sort": "description"}, title: "${_("Description")}", width: "250px", className: "td-description" },
115 { data: {"_": "created_on",
115 { data: {"_": "created_on",
116 "sort": "created_on_raw"}, title: "${_("Created on")}", className: "td-time" },
116 "sort": "created_on_raw"}, title: "${_("Created on")}", className: "td-time" },
117 { data: {"_": "expires",
117 { data: {"_": "expires",
118 "sort": "expires"}, title: "${_("Expires")}", className: "td-exp" }
118 "sort": "expires"}, title: "${_("Expires")}", className: "td-exp" }
119 ],
119 ],
120 language: {
120 language: {
121 paginate: DEFAULT_GRID_PAGINATION,
121 paginate: DEFAULT_GRID_PAGINATION,
122 emptyTable: _gettext("No gists available yet.")
122 emptyTable: _gettext("No gists available yet.")
123 },
123 },
124 "initComplete": function( settings, json ) {
124 "initComplete": function( settings, json ) {
125 timeagoActivate();
125 timeagoActivate();
126 get_datatable_count();
126 get_datatable_count();
127 }
127 }
128 });
128 });
129
129
130 // update the counter when things change
130 // update the counter when things change
131 $('#gist_list_table').on('draw.dt', function() {
131 $('#gist_list_table').on('draw.dt', function() {
132 timeagoActivate();
132 timeagoActivate();
133 get_datatable_count();
133 get_datatable_count();
134 });
134 });
135
135
136 // filter, filter both grids
136 // filter, filter both grids
137 $('#q_filter').on( 'keyup', function () {
137 $('#q_filter').on( 'keyup', function () {
138 var repo_api = $('#gist_list_table').dataTable().api();
138 var repo_api = $('#gist_list_table').dataTable().api();
139 repo_api
139 repo_api
140 .draw();
140 .draw();
141 });
141 });
142
142
143 // refilter table if page load via back button
143 // refilter table if page load via back button
144 $("#q_filter").trigger('keyup');
144 $("#q_filter").trigger('keyup');
145
145
146 });
146 });
147
147
148 </script>
148 </script>
149 </%def>
149 </%def>
150
150
@@ -1,118 +1,118 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="robots()">
4 <%def name="robots()">
5 %if c.gist.gist_type != 'public':
5 %if c.gist.gist_type != 'public':
6 <meta name="robots" content="noindex, nofollow">
6 <meta name="robots" content="noindex, nofollow">
7 %else:
7 %else:
8 ${parent.robots()}
8 ${parent.robots()}
9 %endif
9 %endif
10 </%def>
10 </%def>
11
11
12 <%def name="title()">
12 <%def name="title()">
13 ${_('Gist')} &middot; ${c.gist.gist_access_id}
13 ${_('Gist')} &middot; ${c.gist.gist_access_id}
14 %if c.rhodecode_name:
14 %if c.rhodecode_name:
15 &middot; ${h.branding(c.rhodecode_name)}
15 &middot; ${h.branding(c.rhodecode_name)}
16 %endif
16 %endif
17 </%def>
17 </%def>
18
18
19 <%def name="breadcrumbs_links()">
19 <%def name="breadcrumbs_links()">
20 ${_('Gist')} &middot; ${c.gist.gist_access_id}
20 ${_('Gist')} &middot; ${c.gist.gist_access_id}
21 </%def>
21 </%def>
22
22
23 <%def name="menu_bar_nav()">
23 <%def name="menu_bar_nav()">
24 ${self.menu_items(active='gists')}
24 ${self.menu_items(active='gists')}
25 </%def>
25 </%def>
26
26
27 <%def name="main()">
27 <%def name="main()">
28 <div class="box">
28 <div class="box">
29 <!-- box / title -->
29 <!-- box / title -->
30 <div class="title">
30 <div class="title">
31 ${self.breadcrumbs()}
31 ${self.breadcrumbs()}
32 %if c.rhodecode_user.username != h.DEFAULT_USER:
32 %if c.rhodecode_user.username != h.DEFAULT_USER:
33 <ul class="links">
33 <ul class="links">
34 <li>
34 <li>
35 <a href="${h.route_path('gists_new')}" class="btn btn-primary">${_(u'Create New Gist')}</a>
35 <a href="${h.route_path('gists_new')}" class="btn btn-primary">${_(u'Create New Gist')}</a>
36 </li>
36 </li>
37 </ul>
37 </ul>
38 %endif
38 %endif
39 </div>
39 </div>
40
40
41 <div class="table">
41 <div class="table">
42 <div id="files_data">
42 <div id="files_data">
43 <div id="codeblock" class="codeblock">
43 <div id="codeblock" class="codeblock">
44 <div class="code-header">
44 <div class="code-header">
45 <div class="gist_url">
45 <div class="gist_url">
46 <code>
46 <code>
47 ${c.gist.gist_url()} <span class="icon-clipboard clipboard-action" data-clipboard-text="${c.gist.gist_url()}" title="${_('Copy the url')}"></span>
47 ${c.gist.gist_url()} <span class="icon-clipboard clipboard-action" data-clipboard-text="${c.gist.gist_url()}" title="${_('Copy the url')}"></span>
48 </code>
48 </code>
49 </div>
49 </div>
50 <div class="stats">
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 <div class="remove_gist">
52 <div class="remove_gist">
53 ${h.secure_form(h.route_path('gist_delete', gist_id=c.gist.gist_access_id), request=request)}
53 ${h.secure_form(h.route_path('gist_delete', gist_id=c.gist.gist_access_id), request=request)}
54 ${h.submit('remove_gist', _('Delete'),class_="btn btn-mini btn-danger",onclick="return confirm('"+_('Confirm to delete this Gist')+"');")}
54 ${h.submit('remove_gist', _('Delete'),class_="btn btn-mini btn-danger",onclick="return confirm('"+_('Confirm to delete this Gist')+"');")}
55 ${h.end_form()}
55 ${h.end_form()}
56 </div>
56 </div>
57 %endif
57 %endif
58 <div class="buttons">
58 <div class="buttons">
59 ## only owner should see that
59 ## only owner should see that
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>
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 ${h.link_to(_('Edit'), h.route_path('gist_edit', gist_id=c.gist.gist_access_id), class_="btn btn-mini")}
63 ${h.link_to(_('Edit'), h.route_path('gist_edit', gist_id=c.gist.gist_access_id), class_="btn btn-mini")}
64 %endif
64 %endif
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")}
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 </div>
66 </div>
67 <div class="left" >
67 <div class="left" >
68 %if c.gist.gist_type != 'public':
68 %if c.gist.gist_type != 'public':
69 <span class="tag tag-ok disabled">${_('Private Gist')}</span>
69 <span class="tag tag-ok disabled">${_('Private Gist')}</span>
70 %endif
70 %endif
71 <span> ${c.gist.gist_description}</span>
71 <span> ${c.gist.gist_description}</span>
72 <span>${_('Expires')}:
72 <span>${_('Expires')}:
73 %if c.gist.gist_expires == -1:
73 %if c.gist.gist_expires == -1:
74 ${_('never')}
74 ${_('never')}
75 %else:
75 %else:
76 ${h.age_component(h.time_to_utcdatetime(c.gist.gist_expires))}
76 ${h.age_component(h.time_to_utcdatetime(c.gist.gist_expires))}
77 %endif
77 %endif
78 </span>
78 </span>
79
79
80 </div>
80 </div>
81 </div>
81 </div>
82
82
83 <div class="author">
83 <div class="author">
84 <div title="${h.tooltip(c.file_last_commit.author)}">
84 <div title="${h.tooltip(c.file_last_commit.author)}">
85 ${self.gravatar_with_user(c.file_last_commit.author, 16)} - ${_('created')} ${h.age_component(c.file_last_commit.date)}
85 ${self.gravatar_with_user(c.file_last_commit.author, 16)} - ${_('created')} ${h.age_component(c.file_last_commit.date)}
86 </div>
86 </div>
87
87
88 </div>
88 </div>
89 <div class="commit">${h.urlify_commit_message(c.file_last_commit.message, None)}</div>
89 <div class="commit">${h.urlify_commit_message(c.file_last_commit.message, None)}</div>
90 </div>
90 </div>
91
91
92 ## iterate over the files
92 ## iterate over the files
93 % for file in c.files:
93 % for file in c.files:
94 <% renderer = c.render and h.renderer_from_filename(file.path, exclude=['.txt', '.TXT'])%>
94 <% renderer = c.render and h.renderer_from_filename(file.path, exclude=['.txt', '.TXT'])%>
95 <!--
95 <!--
96 <div id="${h.FID('G', file.path)}" class="stats" >
96 <div id="${h.FID('G', file.path)}" class="stats" >
97 <a href="${c.gist.gist_url()}">ΒΆ</a>
97 <a href="${c.gist.gist_url()}">ΒΆ</a>
98 <b >${file.path}</b>
98 <b >${file.path}</b>
99 <div>
99 <div>
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")}
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 </div>
101 </div>
102 </div>
102 </div>
103 -->
103 -->
104 <div class="code-body textarea text-area editor">
104 <div class="code-body textarea text-area editor">
105 %if renderer:
105 %if renderer:
106 ${h.render(file.content, renderer=renderer)}
106 ${h.render(file.content, renderer=renderer)}
107 %else:
107 %else:
108 ${h.pygmentize(file,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")}
108 ${h.pygmentize(file,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")}
109 %endif
109 %endif
110 </div>
110 </div>
111 %endfor
111 %endfor
112 </div>
112 </div>
113 </div>
113 </div>
114 </div>
114 </div>
115
115
116
116
117 </div>
117 </div>
118 </%def>
118 </%def>
@@ -1,50 +1,42 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Settings administration')}
5 ${_('Settings administration')}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 &raquo;
13 &raquo;
14 ${_('Settings')}
14 ${_('Settings')}
15 </%def>
15 </%def>
16
16
17 <%def name="menu_bar_nav()">
17 <%def name="menu_bar_nav()">
18 ${self.menu_items(active='admin')}
18 ${self.menu_items(active='admin')}
19 </%def>
19 </%def>
20
20
21 <%def name="menu_bar_subnav()">
21 <%def name="menu_bar_subnav()">
22 ${self.admin_menu()}
22 ${self.admin_menu()}
23 </%def>
23 </%def>
24
24
25 <%def name="side_bar_nav()">
25 <%def name="side_bar_nav()">
26
26
27 </%def>
27 </%def>
28
28
29 <%def name="main_content()">
29 <%def name="main_content()">
30 Hello Admin
30 Hello Admin
31 </%def>
31 </%def>
32
32
33 <%def name="main()">
33 <%def name="main()">
34 <div class="box">
34 <div class="box">
35
35
36 ##main
36 ##main
37 <div class='sidebar-col-wrapper'>
37 <div class="main-content-auto-width">
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">
45 ${self.main_content()}
38 ${self.main_content()}
46 </div>
47 </div>
39 </div>
48 </div>
40 </div>
49
41
50 </%def> No newline at end of file
42 </%def>
@@ -1,219 +1,219 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <div class="panel panel-default">
3 <div class="panel panel-default">
4 <div class="panel-heading">
4 <div class="panel-heading">
5 <h3 class="panel-title">${_('Repository Group Permissions')}</h3>
5 <h3 class="panel-title">${_('Repository Group Permissions')}</h3>
6 </div>
6 </div>
7 <div class="panel-body">
7 <div class="panel-body">
8 ${h.secure_form(h.route_path('edit_repo_group_perms_update', repo_group_name=c.repo_group.group_name), request=request)}
8 ${h.secure_form(h.route_path('edit_repo_group_perms_update', repo_group_name=c.repo_group.group_name), request=request)}
9 <table id="permissions_manage" class="rctable permissions">
9 <table id="permissions_manage" class="rctable permissions">
10 <tr>
10 <tr>
11 <th class="td-radio">${_('None')}</th>
11 <th class="td-radio">${_('None')}</th>
12 <th class="td-radio">${_('Read')}</th>
12 <th class="td-radio">${_('Read')}</th>
13 <th class="td-radio">${_('Write')}</th>
13 <th class="td-radio">${_('Write')}</th>
14 <th class="td-radio">${_('Admin')}</th>
14 <th class="td-radio">${_('Admin')}</th>
15 <th class="td-owner">${_('User/User Group')}</th>
15 <th class="td-owner">${_('User/User Group')}</th>
16 <th class="td-action"></th>
16 <th class="td-action"></th>
17 <th class="td-action"></th>
17 <th class="td-action"></th>
18 </tr>
18 </tr>
19 ## USERS
19 ## USERS
20 %for _user in c.repo_group.permissions():
20 %for _user in c.repo_group.permissions():
21 ## super admin/owner row
21 ## super admin/owner row
22 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
22 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
23 <tr class="perm_admin_row">
23 <tr class="perm_admin_row">
24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
26 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
26 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
27 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
27 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
28 <td class="td-user">
28 <td class="td-user">
29 ${base.gravatar(_user.email, 16)}
29 ${base.gravatar(_user.email, 16)}
30 ${h.link_to_user(_user.username)}
30 ${h.link_to_user(_user.username)}
31 %if getattr(_user, 'admin_row', None):
31 %if getattr(_user, 'admin_row', None):
32 (${_('super admin')})
32 (${_('super admin')})
33 %endif
33 %endif
34 %if getattr(_user, 'owner_row', None):
34 %if getattr(_user, 'owner_row', None):
35 (${_('owner')})
35 (${_('owner')})
36 %endif
36 %endif
37 </td>
37 </td>
38 <td></td>
38 <td></td>
39 <td class="quick_repo_menu">
39 <td class="quick_repo_menu">
40 % if c.rhodecode_user.is_admin:
40 % if c.rhodecode_user.is_admin:
41 <i class="icon-more"></i>
41 <i class="icon-more"></i>
42 <div class="menu_items_container" style="display: none;">
42 <div class="menu_items_container" style="display: none;">
43 <ul class="menu_items">
43 <ul class="menu_items">
44 <li>
44 <li>
45 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-groups-permissions'))}
45 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-groups-permissions'))}
46 </li>
46 </li>
47 </ul>
47 </ul>
48 </div>
48 </div>
49 % endif
49 % endif
50 </td>
50 </td>
51 </tr>
51 </tr>
52 %else:
52 %else:
53 <tr>
53 <tr>
54 ##forbid revoking permission from yourself, except if you're an super admin
54 ##forbid revoking permission from yourself, except if you're an super admin
55 %if c.rhodecode_user.user_id != _user.user_id or c.rhodecode_user.is_admin:
55 %if c.rhodecode_user.user_id != _user.user_id or c.rhodecode_user.is_admin:
56 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.none', checked=_user.permission=='group.none')}</td>
56 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.none', checked=_user.permission=='group.none')}</td>
57 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.read', checked=_user.permission=='group.read')}</td>
57 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.read', checked=_user.permission=='group.read')}</td>
58 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.write', checked=_user.permission=='group.write')}</td>
58 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.write', checked=_user.permission=='group.write')}</td>
59 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.admin', checked=_user.permission=='group.admin')}</td>
59 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.admin', checked=_user.permission=='group.admin')}</td>
60 <td class="td-user">
60 <td class="td-user">
61 ${base.gravatar(_user.email, 16)}
61 ${base.gravatar(_user.email, 16)}
62 <span class="user">
62 <span class="user">
63 % if _user.username == h.DEFAULT_USER:
63 % if _user.username == h.DEFAULT_USER:
64 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
64 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
65 % else:
65 % else:
66 ${h.link_to_user(_user.username)}
66 ${h.link_to_user(_user.username)}
67 %if getattr(_user, 'duplicate_perm', None):
67 %if getattr(_user, 'duplicate_perm', None):
68 (${_('inactive duplicate')})
68 (${_('inactive duplicate')})
69 %endif
69 %endif
70 % endif
70 % endif
71 </span>
71 </span>
72 </td>
72 </td>
73 <td class="td-action">
73 <td class="td-action">
74 %if _user.username != h.DEFAULT_USER:
74 %if _user.username != h.DEFAULT_USER:
75 <span class="btn btn-link btn-danger revoke_perm"
75 <span class="btn btn-link btn-danger revoke_perm"
76 member="${_user.user_id}" member_type="user">
76 member="${_user.user_id}" member_type="user">
77 ${_('Remove')}
77 ${_('Remove')}
78 </span>
78 </span>
79 %endif
79 %endif
80 </td>
80 </td>
81 <td class="quick_repo_menu">
81 <td class="quick_repo_menu">
82 % if c.rhodecode_user.is_admin:
82 % if c.rhodecode_user.is_admin:
83 <i class="icon-more"></i>
83 <i class="icon-more"></i>
84 <div class="menu_items_container" style="display: none;">
84 <div class="menu_items_container" style="display: none;">
85 <ul class="menu_items">
85 <ul class="menu_items">
86 <li>
86 <li>
87 % if _user.username == h.DEFAULT_USER:
87 % if _user.username == h.DEFAULT_USER:
88 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='repositories-groups-permissions'))}
88 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='repositories-groups-permissions'))}
89 % else:
89 % else:
90 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-groups-permissions'))}
90 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-groups-permissions'))}
91 % endif
91 % endif
92 </li>
92 </li>
93 </ul>
93 </ul>
94 </div>
94 </div>
95 % endif
95 % endif
96 </td>
96 </td>
97 %else:
97 %else:
98 ## special case for currently logged-in user permissions, we make sure he cannot take his own permissions
98 ## special case for currently logged-in user permissions, we make sure he cannot take his own permissions
99 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.none', disabled="disabled")}</td>
99 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.none', disabled="disabled")}</td>
100 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.read', disabled="disabled")}</td>
100 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.read', disabled="disabled")}</td>
101 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.write', disabled="disabled")}</td>
101 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.write', disabled="disabled")}</td>
102 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.admin', disabled="disabled")}</td>
102 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.admin', disabled="disabled")}</td>
103 <td class="td-user">
103 <td class="td-user">
104 ${base.gravatar(_user.email, 16)}
104 ${base.gravatar(_user.email, 16)}
105 <span class="user">
105 <span class="user">
106 % if _user.username == h.DEFAULT_USER:
106 % if _user.username == h.DEFAULT_USER:
107 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
107 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
108 % else:
108 % else:
109 ${h.link_to_user(_user.username)}
109 ${h.link_to_user(_user.username)}
110 %if getattr(_user, 'duplicate_perm', None):
110 %if getattr(_user, 'duplicate_perm', None):
111 (${_('inactive duplicate')})
111 (${_('inactive duplicate')})
112 %endif
112 %endif
113 % endif
113 % endif
114 <span class="user-perm-help-text">(${_('delegated admin')})</span>
114 <span class="user-perm-help-text">(${_('delegated admin')})</span>
115 </span>
115 </span>
116 </td>
116 </td>
117 <td></td>
117 <td></td>
118 <td class="quick_repo_menu">
118 <td class="quick_repo_menu">
119 % if c.rhodecode_user.is_admin:
119 % if c.rhodecode_user.is_admin:
120 <i class="icon-more"></i>
120 <i class="icon-more"></i>
121 <div class="menu_items_container" style="display: none;">
121 <div class="menu_items_container" style="display: none;">
122 <ul class="menu_items">
122 <ul class="menu_items">
123 <li>
123 <li>
124 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-groups-permissions'))}
124 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-groups-permissions'))}
125 </li>
125 </li>
126 </ul>
126 </ul>
127 </div>
127 </div>
128 % endif
128 % endif
129 </td>
129 </td>
130 %endif
130 %endif
131 </tr>
131 </tr>
132 %endif
132 %endif
133 %endfor
133 %endfor
134
134
135 ## USER GROUPS
135 ## USER GROUPS
136 %for _user_group in c.repo_group.permission_user_groups(with_members=True):
136 %for _user_group in c.repo_group.permission_user_groups(with_members=True):
137 <tr id="id${id(_user_group.users_group_name)}">
137 <tr id="id${id(_user_group.users_group_name)}">
138 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.none', checked=_user_group.permission=='group.none')}</td>
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 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.read', checked=_user_group.permission=='group.read')}</td>
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 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.write', checked=_user_group.permission=='group.write')}</td>
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 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.admin', checked=_user_group.permission=='group.admin')}</td>
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 <td class="td-componentname">
142 <td class="td-componentname">
143 <i class="icon-user-group"></i>
143 <i class="icon-user-group"></i>
144 %if h.HasPermissionAny('hg.admin')():
144 %if c.is_super_admin:
145 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
145 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
146 ${_user_group.users_group_name}
146 ${_user_group.users_group_name}
147 </a>
147 </a>
148 %else:
148 %else:
149 ${h.link_to_group(_user_group.users_group_name)}
149 ${h.link_to_group(_user_group.users_group_name)}
150 %endif
150 %endif
151 (${_('members')}: ${len(_user_group.members)})
151 (${_('members')}: ${len(_user_group.members)})
152 </td>
152 </td>
153 <td class="td-action">
153 <td class="td-action">
154 <span class="btn btn-link btn-danger revoke_perm"
154 <span class="btn btn-link btn-danger revoke_perm"
155 member="${_user_group.users_group_id}" member_type="user_group">
155 member="${_user_group.users_group_id}" member_type="user_group">
156 ${_('Remove')}
156 ${_('Remove')}
157 </span>
157 </span>
158 </td>
158 </td>
159 <td class="quick_repo_menu">
159 <td class="quick_repo_menu">
160 % if c.rhodecode_user.is_admin:
160 % if c.rhodecode_user.is_admin:
161 <i class="icon-more"></i>
161 <i class="icon-more"></i>
162 <div class="menu_items_container" style="display: none;">
162 <div class="menu_items_container" style="display: none;">
163 <ul class="menu_items">
163 <ul class="menu_items">
164 <li>
164 <li>
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'))}
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 </li>
166 </li>
167 </ul>
167 </ul>
168 </div>
168 </div>
169 % endif
169 % endif
170 </td>
170 </td>
171 </tr>
171 </tr>
172 %endfor
172 %endfor
173
173
174 <tr class="new_members" id="add_perm_input"></tr>
174 <tr class="new_members" id="add_perm_input"></tr>
175 <tr>
175 <tr>
176 <td></td>
176 <td></td>
177 <td></td>
177 <td></td>
178 <td></td>
178 <td></td>
179 <td></td>
179 <td></td>
180 <td></td>
180 <td></td>
181 <td>
181 <td>
182 <span id="add_perm" class="link">
182 <span id="add_perm" class="link">
183 ${_('Add user/user group')}
183 ${_('Add user/user group')}
184 </span>
184 </span>
185 </td>
185 </td>
186 <td></td>
186 <td></td>
187 </tr>
187 </tr>
188 </table>
188 </table>
189
189
190 <div class="fields">
190 <div class="fields">
191 <div class="field">
191 <div class="field">
192 <div class="label label-radio">
192 <div class="label label-radio">
193 ${_('Apply to children')}:
193 ${_('Apply to children')}:
194 </div>
194 </div>
195 <div class="radios">
195 <div class="radios">
196 ${h.radio('recursive', 'none', label=_('None'), checked="checked")}
196 ${h.radio('recursive', 'none', label=_('None'), checked="checked")}
197 ${h.radio('recursive', 'groups', label=_('Repository Groups'))}
197 ${h.radio('recursive', 'groups', label=_('Repository Groups'))}
198 ${h.radio('recursive', 'repos', label=_('Repositories'))}
198 ${h.radio('recursive', 'repos', label=_('Repositories'))}
199 ${h.radio('recursive', 'all', label=_('Both'))}
199 ${h.radio('recursive', 'all', label=_('Both'))}
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>
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 </div>
201 </div>
202 </div>
202 </div>
203 </div>
203 </div>
204 <div class="buttons">
204 <div class="buttons">
205 ${h.submit('save',_('Save'),class_="btn btn-primary")}
205 ${h.submit('save',_('Save'),class_="btn btn-primary")}
206 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
206 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
207 </div>
207 </div>
208 ${h.end_form()}
208 ${h.end_form()}
209 </div>
209 </div>
210 </div>
210 </div>
211 <script type="text/javascript">
211 <script type="text/javascript">
212 $('#add_perm').on('click', function(e){
212 $('#add_perm').on('click', function(e){
213 addNewPermInput($(this), 'group');
213 addNewPermInput($(this), 'group');
214 });
214 });
215 $('.revoke_perm').on('click', function(e){
215 $('.revoke_perm').on('click', function(e){
216 markRevokePermInput($(this), 'group');
216 markRevokePermInput($(this), 'group');
217 });
217 });
218 quick_repo_menu();
218 quick_repo_menu();
219 </script>
219 </script>
@@ -1,101 +1,101 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Repository groups administration')}
5 ${_('Repository groups administration')}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="repo_group_count">0</span> ${_('repository groups')}
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="repo_group_count">0</span> ${_('repository groups')}
14 </%def>
14 </%def>
15
15
16 <%def name="menu_bar_nav()">
16 <%def name="menu_bar_nav()">
17 ${self.menu_items(active='admin')}
17 ${self.menu_items(active='admin')}
18 </%def>
18 </%def>
19
19
20 <%def name="menu_bar_subnav()">
20 <%def name="menu_bar_subnav()">
21 ${self.admin_menu(active='repository_groups')}
21 ${self.admin_menu(active='repository_groups')}
22 </%def>
22 </%def>
23
23
24 <%def name="main()">
24 <%def name="main()">
25 <div class="box">
25 <div class="box">
26 <div class="title">
26 <div class="title">
27
27
28 <ul class="links">
28 <ul class="links">
29 %if h.HasPermissionAny('hg.admin','hg.repogroup.create.true')():
29 %if c.can_create_repo_group:
30 <li>
30 <li>
31 <a href="${h.route_path('repo_group_new')}" class="btn btn-small btn-success">${_(u'Add Repository Group')}</a>
31 <a href="${h.route_path('repo_group_new')}" class="btn btn-small btn-success">${_(u'Add Repository Group')}</a>
32 </li>
32 </li>
33 %endif
33 %endif
34 </ul>
34 </ul>
35 </div>
35 </div>
36 <div id="repos_list_wrap">
36 <div id="repos_list_wrap">
37 <table id="group_list_table" class="display"></table>
37 <table id="group_list_table" class="display"></table>
38 </div>
38 </div>
39 </div>
39 </div>
40
40
41 <script>
41 <script>
42 $(document).ready(function() {
42 $(document).ready(function() {
43
43
44 var get_datatable_count = function(){
44 var get_datatable_count = function(){
45 var api = $('#group_list_table').dataTable().api();
45 var api = $('#group_list_table').dataTable().api();
46 $('#repo_group_count').text(api.page.info().recordsDisplay);
46 $('#repo_group_count').text(api.page.info().recordsDisplay);
47 };
47 };
48
48
49 // repo group list
49 // repo group list
50 $('#group_list_table').DataTable({
50 $('#group_list_table').DataTable({
51 data: ${c.data|n},
51 data: ${c.data|n},
52 dom: 'rtp',
52 dom: 'rtp',
53 pageLength: ${c.visual.admin_grid_items},
53 pageLength: ${c.visual.admin_grid_items},
54 order: [[ 0, "asc" ]],
54 order: [[ 0, "asc" ]],
55 columns: [
55 columns: [
56 { data: {"_": "name",
56 { data: {"_": "name",
57 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
57 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
58 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
58 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
59 { data: {"_": "desc",
59 { data: {"_": "desc",
60 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
60 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
61 { data: {"_": "last_change",
61 { data: {"_": "last_change",
62 "sort": "last_change_raw",
62 "sort": "last_change_raw",
63 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
63 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
64 { data: {"_": "top_level_repos",
64 { data: {"_": "top_level_repos",
65 "sort": "top_level_repos"}, title: "${_('Number of top level repositories')}" },
65 "sort": "top_level_repos"}, title: "${_('Number of top level repositories')}" },
66 { data: {"_": "owner",
66 { data: {"_": "owner",
67 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
67 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
68 { data: {"_": "action",
68 { data: {"_": "action",
69 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
69 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
70 ],
70 ],
71 language: {
71 language: {
72 paginate: DEFAULT_GRID_PAGINATION,
72 paginate: DEFAULT_GRID_PAGINATION,
73 emptyTable: _gettext("No repository groups available yet.")
73 emptyTable: _gettext("No repository groups available yet.")
74 },
74 },
75 "initComplete": function( settings, json ) {
75 "initComplete": function( settings, json ) {
76 get_datatable_count();
76 get_datatable_count();
77 quick_repo_menu();
77 quick_repo_menu();
78 }
78 }
79 });
79 });
80
80
81 // update the counter when doing search
81 // update the counter when doing search
82 $('#group_list_table').on( 'search.dt', function (e,settings) {
82 $('#group_list_table').on( 'search.dt', function (e,settings) {
83 get_datatable_count();
83 get_datatable_count();
84 });
84 });
85
85
86 // filter, filter both grids
86 // filter, filter both grids
87 $('#q_filter').on( 'keyup', function () {
87 $('#q_filter').on( 'keyup', function () {
88
88
89 var repo_group_api = $('#group_list_table').dataTable().api();
89 var repo_group_api = $('#group_list_table').dataTable().api();
90 repo_group_api
90 repo_group_api
91 .columns(0)
91 .columns(0)
92 .search(this.value)
92 .search(this.value)
93 .draw();
93 .draw();
94 });
94 });
95
95
96 // refilter table if page load via back button
96 // refilter table if page load via back button
97 $("#q_filter").trigger('keyup');
97 $("#q_filter").trigger('keyup');
98 });
98 });
99 </script>
99 </script>
100 </%def>
100 </%def>
101
101
@@ -1,202 +1,202 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <div class="panel panel-default">
3 <div class="panel panel-default">
4 <div class="panel-heading">
4 <div class="panel-heading">
5 <h3 class="panel-title">${_('Repository Permissions')}</h3>
5 <h3 class="panel-title">${_('Repository Permissions')}</h3>
6 </div>
6 </div>
7 <div class="panel-body">
7 <div class="panel-body">
8 ${h.secure_form(h.route_path('edit_repo_perms', repo_name=c.repo_name), request=request)}
8 ${h.secure_form(h.route_path('edit_repo_perms', repo_name=c.repo_name), request=request)}
9 <table id="permissions_manage" class="rctable permissions">
9 <table id="permissions_manage" class="rctable permissions">
10 <tr>
10 <tr>
11 <th class="td-radio">${_('None')}</th>
11 <th class="td-radio">${_('None')}</th>
12 <th class="td-radio">${_('Read')}</th>
12 <th class="td-radio">${_('Read')}</th>
13 <th class="td-radio">${_('Write')}</th>
13 <th class="td-radio">${_('Write')}</th>
14 <th class="td-radio">${_('Admin')}</th>
14 <th class="td-radio">${_('Admin')}</th>
15 <th class="td-owner">${_('User/User Group')}</th>
15 <th class="td-owner">${_('User/User Group')}</th>
16 <th class="td-action"></th>
16 <th class="td-action"></th>
17 <th class="td-action"></th>
17 <th class="td-action"></th>
18 </tr>
18 </tr>
19 ## USERS
19 ## USERS
20 %for _user in c.rhodecode_db_repo.permissions():
20 %for _user in c.rhodecode_db_repo.permissions():
21 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
21 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
22 <tr class="perm_admin_row">
22 <tr class="perm_admin_row">
23 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
23 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
26 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
26 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
27 <td class="td-user">
27 <td class="td-user">
28 ${base.gravatar(_user.email, 16)}
28 ${base.gravatar(_user.email, 16)}
29 ${h.link_to_user(_user.username)}
29 ${h.link_to_user(_user.username)}
30 %if getattr(_user, 'admin_row', None):
30 %if getattr(_user, 'admin_row', None):
31 (${_('super admin')})
31 (${_('super admin')})
32 %endif
32 %endif
33 %if getattr(_user, 'owner_row', None):
33 %if getattr(_user, 'owner_row', None):
34 (${_('owner')})
34 (${_('owner')})
35 %endif
35 %endif
36 </td>
36 </td>
37 <td></td>
37 <td></td>
38 <td class="quick_repo_menu">
38 <td class="quick_repo_menu">
39 % if c.rhodecode_user.is_admin:
39 % if c.rhodecode_user.is_admin:
40 <i class="icon-more"></i>
40 <i class="icon-more"></i>
41 <div class="menu_items_container" style="display: none;">
41 <div class="menu_items_container" style="display: none;">
42 <ul class="menu_items">
42 <ul class="menu_items">
43 <li>
43 <li>
44 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-permissions'))}
44 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-permissions'))}
45 </li>
45 </li>
46 </ul>
46 </ul>
47 </div>
47 </div>
48 % endif
48 % endif
49 </td>
49 </td>
50 </tr>
50 </tr>
51 %elif _user.username == h.DEFAULT_USER and c.rhodecode_db_repo.private:
51 %elif _user.username == h.DEFAULT_USER and c.rhodecode_db_repo.private:
52 <tr>
52 <tr>
53 <td colspan="4">
53 <td colspan="4">
54 <span class="private_repo_msg">
54 <span class="private_repo_msg">
55 <strong title="${h.tooltip(_user.permission)}">${_('private repository')}</strong>
55 <strong title="${h.tooltip(_user.permission)}">${_('private repository')}</strong>
56 </span>
56 </span>
57 </td>
57 </td>
58 <td class="private_repo_msg">
58 <td class="private_repo_msg">
59 ${base.gravatar(h.DEFAULT_USER_EMAIL, 16)}
59 ${base.gravatar(h.DEFAULT_USER_EMAIL, 16)}
60 ${h.DEFAULT_USER} - ${_('only users/user groups explicitly added here will have access')}</td>
60 ${h.DEFAULT_USER} - ${_('only users/user groups explicitly added here will have access')}</td>
61 <td></td>
61 <td></td>
62 <td class="quick_repo_menu">
62 <td class="quick_repo_menu">
63 % if c.rhodecode_user.is_admin:
63 % if c.rhodecode_user.is_admin:
64 <i class="icon-more"></i>
64 <i class="icon-more"></i>
65 <div class="menu_items_container" style="display: none;">
65 <div class="menu_items_container" style="display: none;">
66 <ul class="menu_items">
66 <ul class="menu_items">
67 <li>
67 <li>
68 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='repositories-permissions'))}
68 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='repositories-permissions'))}
69 </li>
69 </li>
70 </ul>
70 </ul>
71 </div>
71 </div>
72 % endif
72 % endif
73 </td>
73 </td>
74 </tr>
74 </tr>
75 %else:
75 %else:
76 <% used_by_n_rules = len(getattr(_user, 'branch_rules', None) or []) %>
76 <% used_by_n_rules = len(getattr(_user, 'branch_rules', None) or []) %>
77 <tr>
77 <tr>
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>
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 <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>
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 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.write', checked=_user.permission=='repository.write')}</td>
80 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.write', checked=_user.permission=='repository.write')}</td>
81 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.admin', checked=_user.permission=='repository.admin')}</td>
81 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.admin', checked=_user.permission=='repository.admin')}</td>
82 <td class="td-user">
82 <td class="td-user">
83 ${base.gravatar(_user.email, 16)}
83 ${base.gravatar(_user.email, 16)}
84 <span class="user">
84 <span class="user">
85 % if _user.username == h.DEFAULT_USER:
85 % if _user.username == h.DEFAULT_USER:
86 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
86 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
87 % else:
87 % else:
88 ${h.link_to_user(_user.username)}
88 ${h.link_to_user(_user.username)}
89 %if getattr(_user, 'duplicate_perm', None):
89 %if getattr(_user, 'duplicate_perm', None):
90 (${_('inactive duplicate')})
90 (${_('inactive duplicate')})
91 %endif
91 %endif
92 %if getattr(_user, 'branch_rules', None):
92 %if getattr(_user, 'branch_rules', None):
93 % if used_by_n_rules == 1:
93 % if used_by_n_rules == 1:
94 (${_('used by {} branch rule, requires write+ permissions').format(used_by_n_rules)})
94 (${_('used by {} branch rule, requires write+ permissions').format(used_by_n_rules)})
95 % else:
95 % else:
96 (${_('used by {} branch rules, requires write+ permissions').format(used_by_n_rules)})
96 (${_('used by {} branch rules, requires write+ permissions').format(used_by_n_rules)})
97 % endif
97 % endif
98 %endif
98 %endif
99 % endif
99 % endif
100 </span>
100 </span>
101 </td>
101 </td>
102 <td class="td-action">
102 <td class="td-action">
103 %if _user.username != h.DEFAULT_USER and getattr(_user, 'branch_rules', None) is None:
103 %if _user.username != h.DEFAULT_USER and getattr(_user, 'branch_rules', None) is None:
104 <span class="btn btn-link btn-danger revoke_perm"
104 <span class="btn btn-link btn-danger revoke_perm"
105 member="${_user.user_id}" member_type="user">
105 member="${_user.user_id}" member_type="user">
106 ${_('Remove')}
106 ${_('Remove')}
107 </span>
107 </span>
108 %endif
108 %endif
109 </td>
109 </td>
110 <td class="quick_repo_menu">
110 <td class="quick_repo_menu">
111 % if c.rhodecode_user.is_admin:
111 % if c.rhodecode_user.is_admin:
112 <i class="icon-more"></i>
112 <i class="icon-more"></i>
113 <div class="menu_items_container" style="display: none;">
113 <div class="menu_items_container" style="display: none;">
114 <ul class="menu_items">
114 <ul class="menu_items">
115 <li>
115 <li>
116 % if _user.username == h.DEFAULT_USER:
116 % if _user.username == h.DEFAULT_USER:
117 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='repositories-permissions'))}
117 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='repositories-permissions'))}
118 % else:
118 % else:
119 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-permissions'))}
119 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-permissions'))}
120 % endif
120 % endif
121 </li>
121 </li>
122 </ul>
122 </ul>
123 </div>
123 </div>
124 % endif
124 % endif
125 </td>
125 </td>
126 </tr>
126 </tr>
127 %endif
127 %endif
128 %endfor
128 %endfor
129
129
130 ## USER GROUPS
130 ## USER GROUPS
131 %for _user_group in c.rhodecode_db_repo.permission_user_groups(with_members=True):
131 %for _user_group in c.rhodecode_db_repo.permission_user_groups(with_members=True):
132 <tr>
132 <tr>
133 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.none', checked=_user_group.permission=='repository.none')}</td>
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 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.read', checked=_user_group.permission=='repository.read')}</td>
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 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.write', checked=_user_group.permission=='repository.write')}</td>
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 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.admin', checked=_user_group.permission=='repository.admin')}</td>
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 <td class="td-componentname">
137 <td class="td-componentname">
138 <i class="icon-user-group"></i>
138 <i class="icon-user-group"></i>
139 %if h.HasPermissionAny('hg.admin')():
139 %if c.is_super_admin:
140 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
140 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
141 ${_user_group.users_group_name}
141 ${_user_group.users_group_name}
142 </a>
142 </a>
143 %else:
143 %else:
144 ${h.link_to_group(_user_group.users_group_name)}
144 ${h.link_to_group(_user_group.users_group_name)}
145 %endif
145 %endif
146 (${_('members')}: ${len(_user_group.members)})
146 (${_('members')}: ${len(_user_group.members)})
147 </td>
147 </td>
148 <td class="td-action">
148 <td class="td-action">
149 <span class="btn btn-link btn-danger revoke_perm"
149 <span class="btn btn-link btn-danger revoke_perm"
150 member="${_user_group.users_group_id}" member_type="user_group">
150 member="${_user_group.users_group_id}" member_type="user_group">
151 ${_('Remove')}
151 ${_('Remove')}
152 </span>
152 </span>
153 </td>
153 </td>
154 <td class="quick_repo_menu">
154 <td class="quick_repo_menu">
155 % if c.rhodecode_user.is_admin:
155 % if c.rhodecode_user.is_admin:
156 <i class="icon-more"></i>
156 <i class="icon-more"></i>
157 <div class="menu_items_container" style="display: none;">
157 <div class="menu_items_container" style="display: none;">
158 <ul class="menu_items">
158 <ul class="menu_items">
159 <li>
159 <li>
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'))}
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 </li>
161 </li>
162 </ul>
162 </ul>
163 </div>
163 </div>
164 % endif
164 % endif
165 </td>
165 </td>
166 </tr>
166 </tr>
167 %endfor
167 %endfor
168 <tr class="new_members" id="add_perm_input"></tr>
168 <tr class="new_members" id="add_perm_input"></tr>
169
169
170 <tr>
170 <tr>
171 <td></td>
171 <td></td>
172 <td></td>
172 <td></td>
173 <td></td>
173 <td></td>
174 <td></td>
174 <td></td>
175 <td></td>
175 <td></td>
176 <td>
176 <td>
177 <span id="add_perm" class="link">
177 <span id="add_perm" class="link">
178 ${_('Add user/user group')}
178 ${_('Add user/user group')}
179 </span>
179 </span>
180 </td>
180 </td>
181 <td></td>
181 <td></td>
182 </tr>
182 </tr>
183
183
184 </table>
184 </table>
185
185
186 <div class="buttons">
186 <div class="buttons">
187 ${h.submit('save',_('Save'),class_="btn btn-primary")}
187 ${h.submit('save',_('Save'),class_="btn btn-primary")}
188 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
188 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
189 </div>
189 </div>
190 ${h.end_form()}
190 ${h.end_form()}
191 </div>
191 </div>
192 </div>
192 </div>
193
193
194 <script type="text/javascript">
194 <script type="text/javascript">
195 $('#add_perm').on('click', function(e){
195 $('#add_perm').on('click', function(e){
196 addNewPermInput($(this), 'repository');
196 addNewPermInput($(this), 'repository');
197 });
197 });
198 $('.revoke_perm').on('click', function(e){
198 $('.revoke_perm').on('click', function(e){
199 markRevokePermInput($(this), 'repository');
199 markRevokePermInput($(this), 'repository');
200 });
200 });
201 quick_repo_menu();
201 quick_repo_menu();
202 </script>
202 </script>
@@ -1,104 +1,104 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Repositories administration')}
5 ${_('Repositories administration')}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="repo_count">0</span> ${_('repositories')}
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="repo_count">0</span> ${_('repositories')}
14 </%def>
14 </%def>
15
15
16 <%def name="menu_bar_nav()">
16 <%def name="menu_bar_nav()">
17 ${self.menu_items(active='admin')}
17 ${self.menu_items(active='admin')}
18 </%def>
18 </%def>
19
19
20 <%def name="menu_bar_subnav()">
20 <%def name="menu_bar_subnav()">
21 ${self.admin_menu(active='repositories')}
21 ${self.admin_menu(active='repositories')}
22 </%def>
22 </%def>
23
23
24 <%def name="main()">
24 <%def name="main()">
25 <div class="box">
25 <div class="box">
26 <div class="title">
26 <div class="title">
27 <ul class="links">
27 <ul class="links">
28 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
28 %if c.can_create_repo:
29 <li>
29 <li>
30 <a href="${h.route_path('repo_new')}" class="btn btn-small btn-success">${_(u'Add Repository')}</a>
30 <a href="${h.route_path('repo_new')}" class="btn btn-small btn-success">${_(u'Add Repository')}</a>
31 </li>
31 </li>
32 %endif
32 %endif
33 </ul>
33 </ul>
34 </div>
34 </div>
35 <div id="repos_list_wrap">
35 <div id="repos_list_wrap">
36 <table id="repo_list_table" class="display"></table>
36 <table id="repo_list_table" class="display"></table>
37 </div>
37 </div>
38 </div>
38 </div>
39
39
40 <script>
40 <script>
41 $(document).ready(function() {
41 $(document).ready(function() {
42
42
43 var get_datatable_count = function(){
43 var get_datatable_count = function(){
44 var api = $('#repo_list_table').dataTable().api();
44 var api = $('#repo_list_table').dataTable().api();
45 $('#repo_count').text(api.page.info().recordsDisplay);
45 $('#repo_count').text(api.page.info().recordsDisplay);
46 };
46 };
47
47
48
48
49 // repo list
49 // repo list
50 $('#repo_list_table').DataTable({
50 $('#repo_list_table').DataTable({
51 data: ${c.data|n},
51 data: ${c.data|n},
52 dom: 'rtp',
52 dom: 'rtp',
53 pageLength: ${c.visual.admin_grid_items},
53 pageLength: ${c.visual.admin_grid_items},
54 order: [[ 0, "asc" ]],
54 order: [[ 0, "asc" ]],
55 columns: [
55 columns: [
56 { data: {"_": "name",
56 { data: {"_": "name",
57 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
57 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
58 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
58 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
59 { data: {"_": "desc",
59 { data: {"_": "desc",
60 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
60 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
61 { data: {"_": "last_change",
61 { data: {"_": "last_change",
62 "sort": "last_change_raw",
62 "sort": "last_change_raw",
63 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
63 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
64 { data: {"_": "last_changeset",
64 { data: {"_": "last_changeset",
65 "sort": "last_changeset_raw",
65 "sort": "last_changeset_raw",
66 "type": Number}, title: "${_('Commit')}", className: "td-commit" },
66 "type": Number}, title: "${_('Commit')}", className: "td-commit" },
67 { data: {"_": "owner",
67 { data: {"_": "owner",
68 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
68 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
69 { data: {"_": "state",
69 { data: {"_": "state",
70 "sort": "state"}, title: "${_('State')}", className: "td-tags td-state" },
70 "sort": "state"}, title: "${_('State')}", className: "td-tags td-state" },
71 { data: {"_": "action",
71 { data: {"_": "action",
72 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
72 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
73 ],
73 ],
74 language: {
74 language: {
75 paginate: DEFAULT_GRID_PAGINATION,
75 paginate: DEFAULT_GRID_PAGINATION,
76 emptyTable:_gettext("No repositories available yet.")
76 emptyTable:_gettext("No repositories available yet.")
77 },
77 },
78 "initComplete": function( settings, json ) {
78 "initComplete": function( settings, json ) {
79 get_datatable_count();
79 get_datatable_count();
80 quick_repo_menu();
80 quick_repo_menu();
81 }
81 }
82 });
82 });
83
83
84 // update the counter when doing search
84 // update the counter when doing search
85 $('#repo_list_table').on( 'search.dt', function (e,settings) {
85 $('#repo_list_table').on( 'search.dt', function (e,settings) {
86 get_datatable_count();
86 get_datatable_count();
87 });
87 });
88
88
89 // filter, filter both grids
89 // filter, filter both grids
90 $('#q_filter').on( 'keyup', function () {
90 $('#q_filter').on( 'keyup', function () {
91 var repo_api = $('#repo_list_table').dataTable().api();
91 var repo_api = $('#repo_list_table').dataTable().api();
92 repo_api
92 repo_api
93 .columns(0)
93 .columns(0)
94 .search(this.value)
94 .search(this.value)
95 .draw();
95 .draw();
96 });
96 });
97
97
98 // refilter table if page load via back button
98 // refilter table if page load via back button
99 $("#q_filter").trigger('keyup');
99 $("#q_filter").trigger('keyup');
100 });
100 });
101
101
102 </script>
102 </script>
103
103
104 </%def>
104 </%def>
@@ -1,207 +1,207 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <div class="panel panel-default">
3 <div class="panel panel-default">
4 <div class="panel-heading">
4 <div class="panel-heading">
5 <h3 class="panel-title">${_('User Group Permissions')}</h3>
5 <h3 class="panel-title">${_('User Group Permissions')}</h3>
6 </div>
6 </div>
7 <div class="panel-body">
7 <div class="panel-body">
8 ${h.secure_form(h.route_path('edit_user_group_perms_update', user_group_id=c.user_group.users_group_id), request=request)}
8 ${h.secure_form(h.route_path('edit_user_group_perms_update', user_group_id=c.user_group.users_group_id), request=request)}
9 <table id="permissions_manage" class="rctable permissions">
9 <table id="permissions_manage" class="rctable permissions">
10 <tr>
10 <tr>
11 <th class="td-radio">${_('None')}</th>
11 <th class="td-radio">${_('None')}</th>
12 <th class="td-radio">${_('Read')}</th>
12 <th class="td-radio">${_('Read')}</th>
13 <th class="td-radio">${_('Write')}</th>
13 <th class="td-radio">${_('Write')}</th>
14 <th class="td-radio">${_('Admin')}</th>
14 <th class="td-radio">${_('Admin')}</th>
15 <th>${_('User/User Group')}</th>
15 <th>${_('User/User Group')}</th>
16 <th class="td-action"></th>
16 <th class="td-action"></th>
17 <th class="td-action"></th>
17 <th class="td-action"></th>
18 </tr>
18 </tr>
19 ## USERS
19 ## USERS
20 %for _user in c.user_group.permissions():
20 %for _user in c.user_group.permissions():
21 ## super admin/owner row
21 ## super admin/owner row
22 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
22 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
23 <tr class="perm_admin_row">
23 <tr class="perm_admin_row">
24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
26 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
26 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
27 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
27 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
28 <td class="td-user">
28 <td class="td-user">
29 ${base.gravatar(_user.email, 16)}
29 ${base.gravatar(_user.email, 16)}
30 <span class="user">
30 <span class="user">
31 ${h.link_to_user(_user.username)}
31 ${h.link_to_user(_user.username)}
32 %if getattr(_user, 'admin_row', None):
32 %if getattr(_user, 'admin_row', None):
33 (${_('super admin')})
33 (${_('super admin')})
34 %endif
34 %endif
35 %if getattr(_user, 'owner_row', None):
35 %if getattr(_user, 'owner_row', None):
36 (${_('owner')})
36 (${_('owner')})
37 %endif
37 %endif
38 </span>
38 </span>
39 </td>
39 </td>
40 <td></td>
40 <td></td>
41 <td class="quick_repo_menu">
41 <td class="quick_repo_menu">
42 % if c.rhodecode_user.is_admin:
42 % if c.rhodecode_user.is_admin:
43 <i class="icon-more"></i>
43 <i class="icon-more"></i>
44 <div class="menu_items_container" style="display: none;">
44 <div class="menu_items_container" style="display: none;">
45 <ul class="menu_items">
45 <ul class="menu_items">
46 <li>
46 <li>
47 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='user-groups-permissions'))}
47 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='user-groups-permissions'))}
48 </li>
48 </li>
49 </ul>
49 </ul>
50 </div>
50 </div>
51 % endif
51 % endif
52 </td>
52 </td>
53 </tr>
53 </tr>
54 %else:
54 %else:
55 ##forbid revoking permission from yourself, except if you're an super admin
55 ##forbid revoking permission from yourself, except if you're an super admin
56 <tr>
56 <tr>
57 %if c.rhodecode_user.user_id != _user.user_id or c.rhodecode_user.is_admin:
57 %if c.rhodecode_user.user_id != _user.user_id or c.rhodecode_user.is_admin:
58 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.none')}</td>
58 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.none')}</td>
59 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.read')}</td>
59 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.read')}</td>
60 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.write')}</td>
60 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.write')}</td>
61 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.admin')}</td>
61 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.admin')}</td>
62 <td class="td-user">
62 <td class="td-user">
63 ${base.gravatar(_user.email, 16)}
63 ${base.gravatar(_user.email, 16)}
64 <span class="user">
64 <span class="user">
65 % if _user.username == h.DEFAULT_USER:
65 % if _user.username == h.DEFAULT_USER:
66 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
66 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
67 % else:
67 % else:
68 ${h.link_to_user(_user.username)}
68 ${h.link_to_user(_user.username)}
69 %if getattr(_user, 'duplicate_perm', None):
69 %if getattr(_user, 'duplicate_perm', None):
70 (${_('inactive duplicate')})
70 (${_('inactive duplicate')})
71 %endif
71 %endif
72 % endif
72 % endif
73 </span>
73 </span>
74 </td>
74 </td>
75 <td class="td-action">
75 <td class="td-action">
76 %if _user.username != h.DEFAULT_USER:
76 %if _user.username != h.DEFAULT_USER:
77 <span class="btn btn-link btn-danger revoke_perm"
77 <span class="btn btn-link btn-danger revoke_perm"
78 member="${_user.user_id}" member_type="user">
78 member="${_user.user_id}" member_type="user">
79 ${_('Remove')}
79 ${_('Remove')}
80 </span>
80 </span>
81 %endif
81 %endif
82 </td>
82 </td>
83 <td class="quick_repo_menu">
83 <td class="quick_repo_menu">
84 % if c.rhodecode_user.is_admin:
84 % if c.rhodecode_user.is_admin:
85 <i class="icon-more"></i>
85 <i class="icon-more"></i>
86 <div class="menu_items_container" style="display: none;">
86 <div class="menu_items_container" style="display: none;">
87 <ul class="menu_items">
87 <ul class="menu_items">
88 <li>
88 <li>
89 % if _user.username == h.DEFAULT_USER:
89 % if _user.username == h.DEFAULT_USER:
90 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='user-groups-permissions'))}
90 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='user-groups-permissions'))}
91 % else:
91 % else:
92 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='user-groups-permissions'))}
92 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='user-groups-permissions'))}
93 % endif
93 % endif
94 </li>
94 </li>
95 </ul>
95 </ul>
96 </div>
96 </div>
97 % endif
97 % endif
98 </td>
98 </td>
99 %else:
99 %else:
100 ## special case for currently logged-in user permissions, we make sure he cannot take his own permissions
100 ## special case for currently logged-in user permissions, we make sure he cannot take his own permissions
101 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.none', disabled="disabled")}</td>
101 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.none', disabled="disabled")}</td>
102 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.read', disabled="disabled")}</td>
102 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.read', disabled="disabled")}</td>
103 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.write', disabled="disabled")}</td>
103 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.write', disabled="disabled")}</td>
104 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.admin', disabled="disabled")}</td>
104 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.admin', disabled="disabled")}</td>
105 <td class="td-user">
105 <td class="td-user">
106 ${base.gravatar(_user.email, 16)}
106 ${base.gravatar(_user.email, 16)}
107 <span class="user">
107 <span class="user">
108 % if _user.username == h.DEFAULT_USER:
108 % if _user.username == h.DEFAULT_USER:
109 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
109 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
110 % else:
110 % else:
111 ${h.link_to_user(_user.username)}
111 ${h.link_to_user(_user.username)}
112 %if getattr(_user, 'duplicate_perm', None):
112 %if getattr(_user, 'duplicate_perm', None):
113 (${_('inactive duplicate')})
113 (${_('inactive duplicate')})
114 %endif
114 %endif
115 % endif
115 % endif
116 <span class="user-perm-help-text">(${_('delegated admin')})</span>
116 <span class="user-perm-help-text">(${_('delegated admin')})</span>
117 </span>
117 </span>
118 </td>
118 </td>
119 <td></td>
119 <td></td>
120 <td class="quick_repo_menu">
120 <td class="quick_repo_menu">
121 % if c.rhodecode_user.is_admin:
121 % if c.rhodecode_user.is_admin:
122 <i class="icon-more"></i>
122 <i class="icon-more"></i>
123 <div class="menu_items_container" style="display: none;">
123 <div class="menu_items_container" style="display: none;">
124 <ul class="menu_items">
124 <ul class="menu_items">
125 <li>
125 <li>
126 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='user-groups-permissions'))}
126 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='user-groups-permissions'))}
127 </li>
127 </li>
128 </ul>
128 </ul>
129 </div>
129 </div>
130 % endif
130 % endif
131 </td>
131 </td>
132 %endif
132 %endif
133 </tr>
133 </tr>
134 %endif
134 %endif
135 %endfor
135 %endfor
136
136
137 ## USER GROUPS
137 ## USER GROUPS
138 %for _user_group in c.user_group.permission_user_groups(with_members=True):
138 %for _user_group in c.user_group.permission_user_groups(with_members=True):
139 <tr>
139 <tr>
140 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.none')}</td>
140 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.none')}</td>
141 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.read')}</td>
141 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.read')}</td>
142 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.write')}</td>
142 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.write')}</td>
143 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.admin')}</td>
143 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.admin')}</td>
144 <td class="td-user">
144 <td class="td-user">
145 <i class="icon-user-group"></i>
145 <i class="icon-user-group"></i>
146 %if h.HasPermissionAny('hg.admin')():
146 %if c.is_super_admin:
147 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
147 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
148 ${_user_group.users_group_name}
148 ${_user_group.users_group_name}
149 </a>
149 </a>
150 %else:
150 %else:
151 ${h.link_to_group(_user_group.users_group_name)}
151 ${h.link_to_group(_user_group.users_group_name)}
152 %endif
152 %endif
153 (${_('members')}: ${len(_user_group.members)})
153 (${_('members')}: ${len(_user_group.members)})
154 </td>
154 </td>
155 <td class="td-action">
155 <td class="td-action">
156 <span class="btn btn-link btn-danger revoke_perm"
156 <span class="btn btn-link btn-danger revoke_perm"
157 member="${_user_group.users_group_id}" member_type="user_group">
157 member="${_user_group.users_group_id}" member_type="user_group">
158 ${_('Remove')}
158 ${_('Remove')}
159 </span>
159 </span>
160 </td>
160 </td>
161 <td class="quick_repo_menu">
161 <td class="quick_repo_menu">
162 % if c.rhodecode_user.is_admin:
162 % if c.rhodecode_user.is_admin:
163 <i class="icon-more"></i>
163 <i class="icon-more"></i>
164 <div class="menu_items_container" style="display: none;">
164 <div class="menu_items_container" style="display: none;">
165 <ul class="menu_items">
165 <ul class="menu_items">
166 <li>
166 <li>
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'))}
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 </li>
168 </li>
169 </ul>
169 </ul>
170 </div>
170 </div>
171 % endif
171 % endif
172 </td>
172 </td>
173 </tr>
173 </tr>
174 %endfor
174 %endfor
175 <tr class="new_members" id="add_perm_input"></tr>
175 <tr class="new_members" id="add_perm_input"></tr>
176 <tr>
176 <tr>
177 <td></td>
177 <td></td>
178 <td></td>
178 <td></td>
179 <td></td>
179 <td></td>
180 <td></td>
180 <td></td>
181 <td></td>
181 <td></td>
182 <td>
182 <td>
183 <span id="add_perm" class="link">
183 <span id="add_perm" class="link">
184 ${_('Add user/user group')}
184 ${_('Add user/user group')}
185 </span>
185 </span>
186 </td>
186 </td>
187 <td></td>
187 <td></td>
188 </tr>
188 </tr>
189 </table>
189 </table>
190
190
191 <div class="buttons">
191 <div class="buttons">
192 ${h.submit('save',_('Save'),class_="btn btn-primary")}
192 ${h.submit('save',_('Save'),class_="btn btn-primary")}
193 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
193 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
194 </div>
194 </div>
195 ${h.end_form()}
195 ${h.end_form()}
196 </div>
196 </div>
197 </div>
197 </div>
198
198
199 <script type="text/javascript">
199 <script type="text/javascript">
200 $('#add_perm').on('click', function(e){
200 $('#add_perm').on('click', function(e){
201 addNewPermInput($(this), 'usergroup');
201 addNewPermInput($(this), 'usergroup');
202 });
202 });
203 $('.revoke_perm').on('click', function(e){
203 $('.revoke_perm').on('click', function(e){
204 markRevokePermInput($(this), 'usergroup');
204 markRevokePermInput($(this), 'usergroup');
205 });
205 });
206 quick_repo_menu()
206 quick_repo_menu()
207 </script>
207 </script>
@@ -1,118 +1,118 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('User groups administration')}
5 ${_('User groups administration')}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="user_group_count">0</span>
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="user_group_count">0</span>
14 </%def>
14 </%def>
15
15
16 <%def name="menu_bar_nav()">
16 <%def name="menu_bar_nav()">
17 ${self.menu_items(active='admin')}
17 ${self.menu_items(active='admin')}
18 </%def>
18 </%def>
19
19
20 <%def name="menu_bar_subnav()">
20 <%def name="menu_bar_subnav()">
21 ${self.admin_menu(active='user_groups')}
21 ${self.admin_menu(active='user_groups')}
22 </%def>
22 </%def>
23
23
24 <%def name="main()">
24 <%def name="main()">
25 <div class="box">
25 <div class="box">
26
26
27 <div class="title">
27 <div class="title">
28 <ul class="links">
28 <ul class="links">
29 %if h.HasPermissionAny('hg.admin', 'hg.usergroup.create.true')():
29 %if c.can_create_user_group:
30 <li>
30 <li>
31 <a href="${h.route_path('user_groups_new')}" class="btn btn-small btn-success">${_(u'Add User Group')}</a>
31 <a href="${h.route_path('user_groups_new')}" class="btn btn-small btn-success">${_(u'Add User Group')}</a>
32 </li>
32 </li>
33 %endif
33 %endif
34 </ul>
34 </ul>
35 </div>
35 </div>
36
36
37 <div id="repos_list_wrap">
37 <div id="repos_list_wrap">
38 <table id="user_group_list_table" class="display"></table>
38 <table id="user_group_list_table" class="display"></table>
39 </div>
39 </div>
40
40
41 </div>
41 </div>
42 <script>
42 <script>
43 $(document).ready(function() {
43 $(document).ready(function() {
44 var $userGroupsListTable = $('#user_group_list_table');
44 var $userGroupsListTable = $('#user_group_list_table');
45
45
46 // user list
46 // user list
47 $userGroupsListTable.DataTable({
47 $userGroupsListTable.DataTable({
48 processing: true,
48 processing: true,
49 serverSide: true,
49 serverSide: true,
50 ajax: {
50 ajax: {
51 "url": "${h.route_path('user_groups_data')}",
51 "url": "${h.route_path('user_groups_data')}",
52 "dataSrc": function (json) {
52 "dataSrc": function (json) {
53 var filteredCount = json.recordsFiltered;
53 var filteredCount = json.recordsFiltered;
54 var filteredInactiveCount = json.recordsFilteredInactive;
54 var filteredInactiveCount = json.recordsFilteredInactive;
55 var totalInactive = json.recordsTotalInactive;
55 var totalInactive = json.recordsTotalInactive;
56 var total = json.recordsTotal;
56 var total = json.recordsTotal;
57
57
58 var _text = _gettext(
58 var _text = _gettext(
59 "{0} ({1} inactive) of {2} user groups ({3} inactive)").format(
59 "{0} ({1} inactive) of {2} user groups ({3} inactive)").format(
60 filteredCount, filteredInactiveCount, total, totalInactive);
60 filteredCount, filteredInactiveCount, total, totalInactive);
61
61
62 if (total === filteredCount) {
62 if (total === filteredCount) {
63 _text = _gettext(
63 _text = _gettext(
64 "{0} user groups ({1} inactive)").format(total, totalInactive);
64 "{0} user groups ({1} inactive)").format(total, totalInactive);
65 }
65 }
66 $('#user_group_count').text(_text);
66 $('#user_group_count').text(_text);
67 return json.data;
67 return json.data;
68 },
68 },
69 },
69 },
70
70
71 dom: 'rtp',
71 dom: 'rtp',
72 pageLength: ${c.visual.admin_grid_items},
72 pageLength: ${c.visual.admin_grid_items},
73 order: [[ 0, "asc" ]],
73 order: [[ 0, "asc" ]],
74 columns: [
74 columns: [
75 { data: {"_": "users_group_name",
75 { data: {"_": "users_group_name",
76 "sort": "users_group_name"}, title: "${_('Name')}", className: "td-componentname" },
76 "sort": "users_group_name"}, title: "${_('Name')}", className: "td-componentname" },
77 { data: {"_": "description",
77 { data: {"_": "description",
78 "sort": "description"}, title: "${_('Description')}", className: "td-description" },
78 "sort": "description"}, title: "${_('Description')}", className: "td-description" },
79 { data: {"_": "members",
79 { data: {"_": "members",
80 "sort": "members"}, title: "${_('Members')}", className: "td-number" },
80 "sort": "members"}, title: "${_('Members')}", className: "td-number" },
81 { data: {"_": "sync",
81 { data: {"_": "sync",
82 "sort": "sync"}, title: "${_('Sync')}", className: "td-sync" },
82 "sort": "sync"}, title: "${_('Sync')}", className: "td-sync" },
83 { data: {"_": "active",
83 { data: {"_": "active",
84 "sort": "active"}, title: "${_('Active')}", className: "td-active" },
84 "sort": "active"}, title: "${_('Active')}", className: "td-active" },
85 { data: {"_": "owner",
85 { data: {"_": "owner",
86 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
86 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
87 { data: {"_": "action",
87 { data: {"_": "action",
88 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false}
88 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false}
89 ],
89 ],
90 language: {
90 language: {
91 paginate: DEFAULT_GRID_PAGINATION,
91 paginate: DEFAULT_GRID_PAGINATION,
92 sProcessing: _gettext('loading...'),
92 sProcessing: _gettext('loading...'),
93 emptyTable: _gettext("No user groups available yet.")
93 emptyTable: _gettext("No user groups available yet.")
94 }
94 }
95 });
95 });
96
96
97 $userGroupsListTable.on('xhr.dt', function(e, settings, json, xhr){
97 $userGroupsListTable.on('xhr.dt', function(e, settings, json, xhr){
98 $userGroupsListTable.css('opacity', 1);
98 $userGroupsListTable.css('opacity', 1);
99 });
99 });
100
100
101 $userGroupsListTable.on('preXhr.dt', function(e, settings, data){
101 $userGroupsListTable.on('preXhr.dt', function(e, settings, data){
102 $userGroupsListTable.css('opacity', 0.3);
102 $userGroupsListTable.css('opacity', 0.3);
103 });
103 });
104
104
105 // filter
105 // filter
106 $('#q_filter').on('keyup',
106 $('#q_filter').on('keyup',
107 $.debounce(250, function() {
107 $.debounce(250, function() {
108 $('#user_group_list_table').DataTable().search(
108 $('#user_group_list_table').DataTable().search(
109 $('#q_filter').val()
109 $('#q_filter').val()
110 ).draw();
110 ).draw();
111 })
111 })
112 );
112 );
113
113
114 });
114 });
115
115
116 </script>
116 </script>
117
117
118 </%def>
118 </%def>
@@ -1,942 +1,942 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="root.mako"/>
2 <%inherit file="root.mako"/>
3
3
4 <%include file="/ejs_templates/templates.html"/>
4 <%include file="/ejs_templates/templates.html"/>
5
5
6 <div class="outerwrapper">
6 <div class="outerwrapper">
7 <!-- HEADER -->
7 <!-- HEADER -->
8 <div class="header">
8 <div class="header">
9 <div id="header-inner" class="wrapper">
9 <div id="header-inner" class="wrapper">
10 <div id="logo">
10 <div id="logo">
11 <div class="logo-wrapper">
11 <div class="logo-wrapper">
12 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
12 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
13 </div>
13 </div>
14 % if c.rhodecode_name:
14 % if c.rhodecode_name:
15 <div class="branding">
15 <div class="branding">
16 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
16 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
17 </div>
17 </div>
18 % endif
18 % endif
19 </div>
19 </div>
20 <!-- MENU BAR NAV -->
20 <!-- MENU BAR NAV -->
21 ${self.menu_bar_nav()}
21 ${self.menu_bar_nav()}
22 <!-- END MENU BAR NAV -->
22 <!-- END MENU BAR NAV -->
23 </div>
23 </div>
24 </div>
24 </div>
25 ${self.menu_bar_subnav()}
25 ${self.menu_bar_subnav()}
26 <!-- END HEADER -->
26 <!-- END HEADER -->
27
27
28 <!-- CONTENT -->
28 <!-- CONTENT -->
29 <div id="content" class="wrapper">
29 <div id="content" class="wrapper">
30
30
31 <rhodecode-toast id="notifications"></rhodecode-toast>
31 <rhodecode-toast id="notifications"></rhodecode-toast>
32
32
33 <div class="main">
33 <div class="main">
34 ${next.main()}
34 ${next.main()}
35 </div>
35 </div>
36 </div>
36 </div>
37 <!-- END CONTENT -->
37 <!-- END CONTENT -->
38
38
39 </div>
39 </div>
40 <!-- FOOTER -->
40 <!-- FOOTER -->
41 <div id="footer">
41 <div id="footer">
42 <div id="footer-inner" class="title wrapper">
42 <div id="footer-inner" class="title wrapper">
43 <div>
43 <div>
44 <p class="footer-link-right">
44 <p class="footer-link-right">
45 % if c.visual.show_version:
45 % if c.visual.show_version:
46 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
46 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
47 % endif
47 % endif
48 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
48 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
49 % if c.visual.rhodecode_support_url:
49 % if c.visual.rhodecode_support_url:
50 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
50 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
51 % endif
51 % endif
52 </p>
52 </p>
53 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
53 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
54 <p class="server-instance" style="display:${sid}">
54 <p class="server-instance" style="display:${sid}">
55 ## display hidden instance ID if specially defined
55 ## display hidden instance ID if specially defined
56 % if c.rhodecode_instanceid:
56 % if c.rhodecode_instanceid:
57 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
57 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
58 % endif
58 % endif
59 </p>
59 </p>
60 </div>
60 </div>
61 </div>
61 </div>
62 </div>
62 </div>
63
63
64 <!-- END FOOTER -->
64 <!-- END FOOTER -->
65
65
66 ### MAKO DEFS ###
66 ### MAKO DEFS ###
67
67
68 <%def name="menu_bar_subnav()">
68 <%def name="menu_bar_subnav()">
69 </%def>
69 </%def>
70
70
71 <%def name="breadcrumbs(class_='breadcrumbs')">
71 <%def name="breadcrumbs(class_='breadcrumbs')">
72 <div class="${class_}">
72 <div class="${class_}">
73 ${self.breadcrumbs_links()}
73 ${self.breadcrumbs_links()}
74 </div>
74 </div>
75 </%def>
75 </%def>
76
76
77 <%def name="admin_menu(active=None)">
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 def is_active(selected):
79 def is_active(selected):
86 if selected == active:
80 if selected == active:
87 return "active"
81 return "active"
88 %>
82 %>
89
83
90 <div id="context-bar">
84 <div id="context-bar">
91 <div class="wrapper">
85 <div class="wrapper">
92 <div class="title">
86 <div class="title">
93 <div class="title-content">
87 <div class="title-content">
94 <div class="title-main">
88 <div class="title-main">
95 % if is_super_admin:
89 % if is_super_admin:
96 ${_('Super Admin Panel')}
90 ${_('Super Admin Panel')}
97 % else:
91 % else:
98 ${_('Delegated Admin Panel')}
92 ${_('Delegated Admin Panel')}
99 % endif
93 % endif
100 </div>
94 </div>
101 </div>
95 </div>
102 </div>
96 </div>
103
97
104 <ul id="context-pages" class="navigation horizontal-list">
98 <ul id="context-pages" class="navigation horizontal-list">
105
99
106 ## super admin case
100 ## super admin case
107 % if is_super_admin:
101 % if c.is_super_admin:
108 <li class="${is_active('audit_logs')}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
102 <li class="${is_active('audit_logs')}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
109 <li class="${is_active('repositories')}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
103 <li class="${is_active('repositories')}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
110 <li class="${is_active('repository_groups')}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
104 <li class="${is_active('repository_groups')}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
111 <li class="${is_active('users')}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
105 <li class="${is_active('users')}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
112 <li class="${is_active('user_groups')}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
106 <li class="${is_active('user_groups')}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
113 <li class="${is_active('permissions')}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
107 <li class="${is_active('permissions')}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
114 <li class="${is_active('authentication')}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
108 <li class="${is_active('authentication')}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
115 <li class="${is_active('integrations')}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
109 <li class="${is_active('integrations')}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
116 <li class="${is_active('defaults')}"><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
110 <li class="${is_active('defaults')}"><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
117 <li class="${is_active('settings')}"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
111 <li class="${is_active('settings')}"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
118
112
119 ## delegated admin
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 %if repositories:
121 %if repositories:
122 <li class="${is_active('repositories')} local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
122 <li class="${is_active('repositories')} local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
123 %endif
123 %endif
124 %if repository_groups:
124 %if repository_groups:
125 <li class="${is_active('repository_groups')} local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
125 <li class="${is_active('repository_groups')} local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
126 %endif
126 %endif
127 %if user_groups:
127 %if user_groups:
128 <li class="${is_active('user_groups')} local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
128 <li class="${is_active('user_groups')} local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
129 %endif
129 %endif
130 % endif
130 % endif
131 </ul>
131 </ul>
132
132
133 </div>
133 </div>
134 <div class="clear"></div>
134 <div class="clear"></div>
135 </div>
135 </div>
136 </%def>
136 </%def>
137
137
138 <%def name="dt_info_panel(elements)">
138 <%def name="dt_info_panel(elements)">
139 <dl class="dl-horizontal">
139 <dl class="dl-horizontal">
140 %for dt, dd, title, show_items in elements:
140 %for dt, dd, title, show_items in elements:
141 <dt>${dt}:</dt>
141 <dt>${dt}:</dt>
142 <dd title="${h.tooltip(title)}">
142 <dd title="${h.tooltip(title)}">
143 %if callable(dd):
143 %if callable(dd):
144 ## allow lazy evaluation of elements
144 ## allow lazy evaluation of elements
145 ${dd()}
145 ${dd()}
146 %else:
146 %else:
147 ${dd}
147 ${dd}
148 %endif
148 %endif
149 %if show_items:
149 %if show_items:
150 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
150 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
151 %endif
151 %endif
152 </dd>
152 </dd>
153
153
154 %if show_items:
154 %if show_items:
155 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
155 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
156 %for item in show_items:
156 %for item in show_items:
157 <dt></dt>
157 <dt></dt>
158 <dd>${item}</dd>
158 <dd>${item}</dd>
159 %endfor
159 %endfor
160 </div>
160 </div>
161 %endif
161 %endif
162
162
163 %endfor
163 %endfor
164 </dl>
164 </dl>
165 </%def>
165 </%def>
166
166
167 <%def name="gravatar(email, size=16)">
167 <%def name="gravatar(email, size=16)">
168 <%
168 <%
169 if (size > 16):
169 if (size > 16):
170 gravatar_class = 'gravatar gravatar-large'
170 gravatar_class = 'gravatar gravatar-large'
171 else:
171 else:
172 gravatar_class = 'gravatar'
172 gravatar_class = 'gravatar'
173 %>
173 %>
174 <%doc>
174 <%doc>
175 TODO: johbo: For now we serve double size images to make it smooth
175 TODO: johbo: For now we serve double size images to make it smooth
176 for retina. This is how it worked until now. Should be replaced
176 for retina. This is how it worked until now. Should be replaced
177 with a better solution at some point.
177 with a better solution at some point.
178 </%doc>
178 </%doc>
179 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
179 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
180 </%def>
180 </%def>
181
181
182
182
183 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
183 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
184 <% email = h.email_or_none(contact) %>
184 <% email = h.email_or_none(contact) %>
185 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
185 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
186 ${self.gravatar(email, size)}
186 ${self.gravatar(email, size)}
187 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
187 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
188 </div>
188 </div>
189 </%def>
189 </%def>
190
190
191
191
192 <%def name="repo_page_title(repo_instance)">
192 <%def name="repo_page_title(repo_instance)">
193 <div class="title-content">
193 <div class="title-content">
194 <div class="title-main">
194 <div class="title-main">
195 ## SVN/HG/GIT icons
195 ## SVN/HG/GIT icons
196 %if h.is_hg(repo_instance):
196 %if h.is_hg(repo_instance):
197 <i class="icon-hg"></i>
197 <i class="icon-hg"></i>
198 %endif
198 %endif
199 %if h.is_git(repo_instance):
199 %if h.is_git(repo_instance):
200 <i class="icon-git"></i>
200 <i class="icon-git"></i>
201 %endif
201 %endif
202 %if h.is_svn(repo_instance):
202 %if h.is_svn(repo_instance):
203 <i class="icon-svn"></i>
203 <i class="icon-svn"></i>
204 %endif
204 %endif
205
205
206 ## public/private
206 ## public/private
207 %if repo_instance.private:
207 %if repo_instance.private:
208 <i class="icon-repo-private"></i>
208 <i class="icon-repo-private"></i>
209 %else:
209 %else:
210 <i class="icon-repo-public"></i>
210 <i class="icon-repo-public"></i>
211 %endif
211 %endif
212
212
213 ## repo name with group name
213 ## repo name with group name
214 ${h.breadcrumb_repo_link(repo_instance)}
214 ${h.breadcrumb_repo_link(repo_instance)}
215
215
216 </div>
216 </div>
217
217
218 ## FORKED
218 ## FORKED
219 %if repo_instance.fork:
219 %if repo_instance.fork:
220 <p class="discreet">
220 <p class="discreet">
221 <i class="icon-code-fork"></i> ${_('Fork of')}
221 <i class="icon-code-fork"></i> ${_('Fork of')}
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))}
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 </p>
223 </p>
224 %endif
224 %endif
225
225
226 ## IMPORTED FROM REMOTE
226 ## IMPORTED FROM REMOTE
227 %if repo_instance.clone_uri:
227 %if repo_instance.clone_uri:
228 <p class="discreet">
228 <p class="discreet">
229 <i class="icon-code-fork"></i> ${_('Clone from')}
229 <i class="icon-code-fork"></i> ${_('Clone from')}
230 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
230 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
231 </p>
231 </p>
232 %endif
232 %endif
233
233
234 ## LOCKING STATUS
234 ## LOCKING STATUS
235 %if repo_instance.locked[0]:
235 %if repo_instance.locked[0]:
236 <p class="locking_locked discreet">
236 <p class="locking_locked discreet">
237 <i class="icon-repo-lock"></i>
237 <i class="icon-repo-lock"></i>
238 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
238 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
239 </p>
239 </p>
240 %elif repo_instance.enable_locking:
240 %elif repo_instance.enable_locking:
241 <p class="locking_unlocked discreet">
241 <p class="locking_unlocked discreet">
242 <i class="icon-repo-unlock"></i>
242 <i class="icon-repo-unlock"></i>
243 ${_('Repository not locked. Pull repository to lock it.')}
243 ${_('Repository not locked. Pull repository to lock it.')}
244 </p>
244 </p>
245 %endif
245 %endif
246
246
247 </div>
247 </div>
248 </%def>
248 </%def>
249
249
250 <%def name="repo_menu(active=None)">
250 <%def name="repo_menu(active=None)">
251 <%
251 <%
252 def is_active(selected):
252 def is_active(selected):
253 if selected == active:
253 if selected == active:
254 return "active"
254 return "active"
255 %>
255 %>
256
256
257 <!--- REPO CONTEXT BAR -->
257 <!--- REPO CONTEXT BAR -->
258 <div id="context-bar">
258 <div id="context-bar">
259 <div class="wrapper">
259 <div class="wrapper">
260
260
261 <div class="title">
261 <div class="title">
262 ${self.repo_page_title(c.rhodecode_db_repo)}
262 ${self.repo_page_title(c.rhodecode_db_repo)}
263 </div>
263 </div>
264
264
265 <ul id="context-pages" class="navigation horizontal-list">
265 <ul id="context-pages" class="navigation horizontal-list">
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>
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 <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>
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 <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>
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 <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>
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 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
271 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
272 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
272 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
273 <li class="${is_active('showpullrequest')}">
273 <li class="${is_active('showpullrequest')}">
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)}">
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 <div class="menulabel">
275 <div class="menulabel">
276 %if c.repository_pull_requests == 1:
276 %if c.repository_pull_requests == 1:
277 ${c.repository_pull_requests} ${_('Pull Request')}
277 ${c.repository_pull_requests} ${_('Pull Request')}
278 %else:
278 %else:
279 ${c.repository_pull_requests} ${_('Pull Requests')}
279 ${c.repository_pull_requests} ${_('Pull Requests')}
280 %endif
280 %endif
281 </div>
281 </div>
282 </a>
282 </a>
283 </li>
283 </li>
284 %endif
284 %endif
285
285
286 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
286 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
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>
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 %endif
288 %endif
289
289
290 <li class="${is_active('options')}">
290 <li class="${is_active('options')}">
291 <a class="menulink dropdown">
291 <a class="menulink dropdown">
292 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
292 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
293 </a>
293 </a>
294 <ul class="submenu">
294 <ul class="submenu">
295
295
296 %if c.rhodecode_db_repo.fork:
296 %if c.rhodecode_db_repo.fork:
297 <li>
297 <li>
298 <a title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
298 <a title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
299 href="${h.route_path('repo_compare',
299 href="${h.route_path('repo_compare',
300 repo_name=c.rhodecode_db_repo.fork.repo_name,
300 repo_name=c.rhodecode_db_repo.fork.repo_name,
301 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
301 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
302 source_ref=c.rhodecode_db_repo.landing_rev[1],
302 source_ref=c.rhodecode_db_repo.landing_rev[1],
303 target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
303 target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
304 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
304 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
305 _query=dict(merge=1))}"
305 _query=dict(merge=1))}"
306 >
306 >
307 ${_('Compare fork')}
307 ${_('Compare fork')}
308 </a>
308 </a>
309 </li>
309 </li>
310 %endif
310 %endif
311
311
312 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
312 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
313 %if c.rhodecode_db_repo.locked[0]:
313 %if c.rhodecode_db_repo.locked[0]:
314 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
314 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
315 %else:
315 %else:
316 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
316 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
317 %endif
317 %endif
318 %endif
318 %endif
319 %if c.rhodecode_user.username != h.DEFAULT_USER:
319 %if c.rhodecode_user.username != h.DEFAULT_USER:
320 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
320 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
321 <li><a href="${h.route_path('repo_fork_new',repo_name=c.repo_name)}">${_('Fork')}</a></li>
321 <li><a href="${h.route_path('repo_fork_new',repo_name=c.repo_name)}">${_('Fork')}</a></li>
322 <li><a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
322 <li><a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
323 %endif
323 %endif
324 %endif
324 %endif
325 </ul>
325 </ul>
326 </li>
326 </li>
327 </ul>
327 </ul>
328 </div>
328 </div>
329 <div class="clear"></div>
329 <div class="clear"></div>
330 </div>
330 </div>
331 % if c.rhodecode_db_repo.archived:
331 % if c.rhodecode_db_repo.archived:
332 <div class="alert alert-warning text-center">
332 <div class="alert alert-warning text-center">
333 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
333 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
334 </div>
334 </div>
335 % endif
335 % endif
336 <!--- REPO END CONTEXT BAR -->
336 <!--- REPO END CONTEXT BAR -->
337
337
338 </%def>
338 </%def>
339
339
340 <%def name="repo_group_page_title(repo_group_instance)">
340 <%def name="repo_group_page_title(repo_group_instance)">
341 <div class="title-content">
341 <div class="title-content">
342 <div class="title-main">
342 <div class="title-main">
343 ## Repository Group icon
343 ## Repository Group icon
344 <i class="icon-folder-close"></i>
344 <i class="icon-folder-close"></i>
345
345
346 ## repo name with group name
346 ## repo name with group name
347 ${h.breadcrumb_repo_group_link(repo_group_instance)}
347 ${h.breadcrumb_repo_group_link(repo_group_instance)}
348 </div>
348 </div>
349
349
350 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
350 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
351 <div class="repo-group-desc">
351 <div class="repo-group-desc">
352 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
352 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
353 </div>
353 </div>
354
354
355 </div>
355 </div>
356 </%def>
356 </%def>
357
357
358 <%def name="repo_group_menu(active=None)">
358 <%def name="repo_group_menu(active=None)">
359 <%
359 <%
360 def is_active(selected):
360 def is_active(selected):
361 if selected == active:
361 if selected == active:
362 return "active"
362 return "active"
363
363
364 is_admin = h.HasPermissionAny('hg.admin')('can create repos index page')
365
366 gr_name = c.repo_group.group_name if c.repo_group else None
364 gr_name = c.repo_group.group_name if c.repo_group else None
367 # create repositories with write permission on group is set to true
365 # create repositories with write permission on group is set to true
368 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
366 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
369 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
367 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
370 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
368 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
371
369
372 %>
370 %>
373
371
374 <!--- REPO GROUP CONTEXT BAR -->
372 <!--- REPO GROUP CONTEXT BAR -->
375 <div id="context-bar">
373 <div id="context-bar">
376 <div class="wrapper">
374 <div class="wrapper">
377 <div class="title">
375 <div class="title">
378 ${self.repo_group_page_title(c.repo_group)}
376 ${self.repo_group_page_title(c.repo_group)}
379 </div>
377 </div>
380
378
381 <ul id="context-pages" class="navigation horizontal-list">
379 <ul id="context-pages" class="navigation horizontal-list">
382 <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>
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 <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>
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 % endif
383 % endif
386
384
387 <li class="${is_active('options')}">
385 <li class="${is_active('options')}">
388 <a class="menulink dropdown">
386 <a class="menulink dropdown">
389 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
387 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
390 </a>
388 </a>
391 <ul class="submenu">
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 <li><a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('Add Repository')}</a></li>
391 <li><a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('Add Repository')}</a></li>
394 %endif
392 %endif
395 %if is_admin or group_admin:
393 %if c.is_super_admin or group_admin:
396 <li><a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}">${_(u'Add Parent Group')}</a></li>
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 %endif
395 %endif
398 </ul>
396 </ul>
399 </li>
397 </li>
400 </ul>
398 </ul>
401 </div>
399 </div>
402 <div class="clear"></div>
400 <div class="clear"></div>
403 </div>
401 </div>
404
402
405 <!--- REPO GROUP CONTEXT BAR -->
403 <!--- REPO GROUP CONTEXT BAR -->
406
404
407 </%def>
405 </%def>
408
406
409
407
410 <%def name="usermenu(active=False)">
408 <%def name="usermenu(active=False)">
411 ## USER MENU
409 ## USER MENU
412 <li id="quick_login_li" class="${'active' if active else ''}">
410 <li id="quick_login_li" class="${'active' if active else ''}">
413 % if c.rhodecode_user.username == h.DEFAULT_USER:
411 % if c.rhodecode_user.username == h.DEFAULT_USER:
414 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
412 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
415 ${gravatar(c.rhodecode_user.email, 20)}
413 ${gravatar(c.rhodecode_user.email, 20)}
416 <span class="user">
414 <span class="user">
417 <span>${_('Sign in')}</span>
415 <span>${_('Sign in')}</span>
418 </span>
416 </span>
419 </a>
417 </a>
420 % else:
418 % else:
421 ## logged in user
419 ## logged in user
422 <a id="quick_login_link" class="menulink childs">
420 <a id="quick_login_link" class="menulink childs">
423 ${gravatar(c.rhodecode_user.email, 20)}
421 ${gravatar(c.rhodecode_user.email, 20)}
424 <span class="user">
422 <span class="user">
425 <span class="menu_link_user">${c.rhodecode_user.username}</span>
423 <span class="menu_link_user">${c.rhodecode_user.username}</span>
426 <div class="show_more"></div>
424 <div class="show_more"></div>
427 </span>
425 </span>
428 </a>
426 </a>
429 ## subnav with menu for logged in user
427 ## subnav with menu for logged in user
430 <div class="user-menu submenu">
428 <div class="user-menu submenu">
431 <div id="quick_login">
429 <div id="quick_login">
432 %if c.rhodecode_user.username != h.DEFAULT_USER:
430 %if c.rhodecode_user.username != h.DEFAULT_USER:
433 <div class="">
431 <div class="">
434 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
432 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
435 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
433 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
436 <div class="email">${c.rhodecode_user.email}</div>
434 <div class="email">${c.rhodecode_user.email}</div>
437 </div>
435 </div>
438 <div class="">
436 <div class="">
439 <ol class="links">
437 <ol class="links">
440 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
438 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
441 % if c.rhodecode_user.personal_repo_group:
439 % if c.rhodecode_user.personal_repo_group:
442 <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>
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 % endif
441 % endif
444 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
442 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
445 ## bookmark-items
443 ## bookmark-items
446 <li class="bookmark-items">
444 <li class="bookmark-items">
447 ${_('Bookmarks')}
445 ${_('Bookmarks')}
448 <div class="pull-right">
446 <div class="pull-right">
449 <a href="${h.route_path('my_account_bookmarks')}">${_('Manage')}</a>
447 <a href="${h.route_path('my_account_bookmarks')}">${_('Manage')}</a>
450 </div>
448 </div>
451 </li>
449 </li>
452 % if not c.bookmark_items:
450 % if not c.bookmark_items:
453 <li>
451 <li>
454 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
452 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
455 </li>
453 </li>
456 % endif
454 % endif
457 % for item in c.bookmark_items:
455 % for item in c.bookmark_items:
458 <li>
456 <li>
459 % if item.repository:
457 % if item.repository:
460 <div>
458 <div>
461 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
459 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
462 <code>${item.position}</code>
460 <code>${item.position}</code>
463 % if item.repository.repo_type == 'hg':
461 % if item.repository.repo_type == 'hg':
464 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
462 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
465 % elif item.repository.repo_type == 'git':
463 % elif item.repository.repo_type == 'git':
466 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
464 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
467 % elif item.repository.repo_type == 'svn':
465 % elif item.repository.repo_type == 'svn':
468 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
466 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
469 % endif
467 % endif
470 ${(item.title or h.shorter(item.repository.repo_name, 30))}
468 ${(item.title or h.shorter(item.repository.repo_name, 30))}
471 </a>
469 </a>
472 </div>
470 </div>
473 % elif item.repository_group:
471 % elif item.repository_group:
474 <div>
472 <div>
475 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
473 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
476 <code>${item.position}</code>
474 <code>${item.position}</code>
477 <i class="icon-folder-close" title="${_('Repository group')}" style="font-size: 16px"></i>
475 <i class="icon-folder-close" title="${_('Repository group')}" style="font-size: 16px"></i>
478 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
476 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
479 </a>
477 </a>
480 </div>
478 </div>
481 % else:
479 % else:
482 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
480 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
483 <code>${item.position}</code>
481 <code>${item.position}</code>
484 ${item.title}
482 ${item.title}
485 </a>
483 </a>
486 % endif
484 % endif
487 </li>
485 </li>
488 % endfor
486 % endfor
489
487
490 <li class="logout">
488 <li class="logout">
491 ${h.secure_form(h.route_path('logout'), request=request)}
489 ${h.secure_form(h.route_path('logout'), request=request)}
492 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
490 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
493 ${h.end_form()}
491 ${h.end_form()}
494 </li>
492 </li>
495 </ol>
493 </ol>
496 </div>
494 </div>
497 %endif
495 %endif
498 </div>
496 </div>
499 </div>
497 </div>
500 ## unread counter
498 ## unread counter
501 <div class="pill_container">
499 <div class="pill_container">
502 <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
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 </div>
501 </div>
504 % endif
502 % endif
505 </li>
503 </li>
506 </%def>
504 </%def>
507
505
508 <%def name="menu_items(active=None)">
506 <%def name="menu_items(active=None)">
509 <%
507 <%
510 def is_active(selected):
508 def is_active(selected):
511 if selected == active:
509 if selected == active:
512 return "active"
510 return "active"
513 return ""
511 return ""
514 %>
512 %>
515
513
516 <ul id="quick" class="main_nav navigation horizontal-list">
514 <ul id="quick" class="main_nav navigation horizontal-list">
517 ## notice box for important system messages
515 ## notice box for important system messages
518 <li style="display: none">
516 <li style="display: none">
519 <a class="notice-box" href="#openNotice" onclick="showNoticeBox(); return false">
517 <a class="notice-box" href="#openNotice" onclick="showNoticeBox(); return false">
520 <div class="menulabel-notice" >
518 <div class="menulabel-notice" >
521 0
519 0
522 </div>
520 </div>
523 </a>
521 </a>
524 </li>
522 </li>
525
523
526 ## Main filter
524 ## Main filter
527 <li>
525 <li>
528 <div class="menulabel main_filter_box">
526 <div class="menulabel main_filter_box">
529 <div class="main_filter_input_box">
527 <div class="main_filter_input_box">
530 <ul class="searchItems">
528 <ul class="searchItems">
531
529
532 % if c.template_context['search_context']['repo_id']:
530 % if c.template_context['search_context']['repo_id']:
533 <li class="searchTag searchTagFilter searchTagHidable" >
531 <li class="searchTag searchTagFilter searchTagHidable" >
534 ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">
532 ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">
535 <span class="tag">
533 <span class="tag">
536 This repo
534 This repo
537 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-delete"></i></a>
535 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-delete"></i></a>
538 </span>
536 </span>
539 ##</a>
537 ##</a>
540 </li>
538 </li>
541 % elif c.template_context['search_context']['repo_group_id']:
539 % elif c.template_context['search_context']['repo_group_id']:
542 <li class="searchTag searchTagFilter searchTagHidable">
540 <li class="searchTag searchTagFilter searchTagHidable">
543 ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">
541 ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">
544 <span class="tag">
542 <span class="tag">
545 This group
543 This group
546 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-delete"></i></a>
544 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-delete"></i></a>
547 </span>
545 </span>
548 ##</a>
546 ##</a>
549 </li>
547 </li>
550 % endif
548 % endif
551
549
552 <li class="searchTagInput">
550 <li class="searchTagInput">
553 <input class="main_filter_input" id="main_filter" size="15" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" />
551 <input class="main_filter_input" id="main_filter" size="15" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" />
554 </li>
552 </li>
555 <li class="searchTag searchTagHelp">
553 <li class="searchTag searchTagHelp">
556 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
554 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
557 </li>
555 </li>
558 </ul>
556 </ul>
559 </div>
557 </div>
560 </div>
558 </div>
561
559
562 <div id="main_filter_help" style="display: none">
560 <div id="main_filter_help" style="display: none">
563 - Use '/' key to quickly access this field.
561 - Use '/' key to quickly access this field.
564
562
565 - Enter a name of repository, or repository group for quick search.
563 - Enter a name of repository, or repository group for quick search.
566
564
567 - Prefix query to allow special search:
565 - Prefix query to allow special search:
568
566
569 user:admin, to search for usernames, always global
567 user:admin, to search for usernames, always global
570
568
571 user_group:devops, to search for user groups, always global
569 user_group:devops, to search for user groups, always global
572
570
573 commit:efced4, to search for commits, scoped to repositories or groups
571 commit:efced4, to search for commits, scoped to repositories or groups
574
572
575 file:models.py, to search for file paths, scoped to repositories or groups
573 file:models.py, to search for file paths, scoped to repositories or groups
576
574
577 % if c.template_context['search_context']['repo_id']:
575 % if c.template_context['search_context']['repo_id']:
578 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>
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 % elif c.template_context['search_context']['repo_group_id']:
577 % elif c.template_context['search_context']['repo_group_id']:
580 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>
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 % else:
579 % else:
582 For advanced full text search visit: <a href="${h.route_path('search')}">global search</a>
580 For advanced full text search visit: <a href="${h.route_path('search')}">global search</a>
583 % endif
581 % endif
584 </div>
582 </div>
585 </li>
583 </li>
586
584
587 ## ROOT MENU
585 ## ROOT MENU
588 <li class="${is_active('home')}">
586 <li class="${is_active('home')}">
589 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
587 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
590 <div class="menulabel">${_('Home')}</div>
588 <div class="menulabel">${_('Home')}</div>
591 </a>
589 </a>
592 </li>
590 </li>
593
591
594 %if c.rhodecode_user.username != h.DEFAULT_USER:
592 %if c.rhodecode_user.username != h.DEFAULT_USER:
595 <li class="${is_active('journal')}">
593 <li class="${is_active('journal')}">
596 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
594 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
597 <div class="menulabel">${_('Journal')}</div>
595 <div class="menulabel">${_('Journal')}</div>
598 </a>
596 </a>
599 </li>
597 </li>
600 %else:
598 %else:
601 <li class="${is_active('journal')}">
599 <li class="${is_active('journal')}">
602 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
600 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
603 <div class="menulabel">${_('Public journal')}</div>
601 <div class="menulabel">${_('Public journal')}</div>
604 </a>
602 </a>
605 </li>
603 </li>
606 %endif
604 %endif
607
605
608 <li class="${is_active('gists')}">
606 <li class="${is_active('gists')}">
609 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
607 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
610 <div class="menulabel">${_('Gists')}</div>
608 <div class="menulabel">${_('Gists')}</div>
611 </a>
609 </a>
612 </li>
610 </li>
613
611
612 % if c.is_super_admin or c.is_delegated_admin:
614 <li class="${is_active('admin')}">
613 <li class="${is_active('admin')}">
615 <a class="menulink childs" title="${_('Admin settings')}" href="${h.route_path('admin_home')}">
614 <a class="menulink childs" title="${_('Admin settings')}" href="${h.route_path('admin_home')}">
616 <div class="menulabel">${_('Admin')} </div>
615 <div class="menulabel">${_('Admin')} </div>
617 </a>
616 </a>
618 </li>
617 </li>
618 % endif
619
619
620 ## render extra user menu
620 ## render extra user menu
621 ${usermenu(active=(active=='my_account'))}
621 ${usermenu(active=(active=='my_account'))}
622
622
623 % if c.debug_style:
623 % if c.debug_style:
624 <li>
624 <li>
625 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
625 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
626 <div class="menulabel">${_('[Style]')}</div>
626 <div class="menulabel">${_('[Style]')}</div>
627 </a>
627 </a>
628 </li>
628 </li>
629 % endif
629 % endif
630 </ul>
630 </ul>
631
631
632 <script type="text/javascript">
632 <script type="text/javascript">
633 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
633 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
634
634
635 var formatRepoResult = function(result, container, query, escapeMarkup) {
635 var formatRepoResult = function(result, container, query, escapeMarkup) {
636 return function(data, escapeMarkup) {
636 return function(data, escapeMarkup) {
637 if (!data.repo_id){
637 if (!data.repo_id){
638 return data.text; // optgroup text Repositories
638 return data.text; // optgroup text Repositories
639 }
639 }
640
640
641 var tmpl = '';
641 var tmpl = '';
642 var repoType = data['repo_type'];
642 var repoType = data['repo_type'];
643 var repoName = data['text'];
643 var repoName = data['text'];
644
644
645 if(data && data.type == 'repo'){
645 if(data && data.type == 'repo'){
646 if(repoType === 'hg'){
646 if(repoType === 'hg'){
647 tmpl += '<i class="icon-hg"></i> ';
647 tmpl += '<i class="icon-hg"></i> ';
648 }
648 }
649 else if(repoType === 'git'){
649 else if(repoType === 'git'){
650 tmpl += '<i class="icon-git"></i> ';
650 tmpl += '<i class="icon-git"></i> ';
651 }
651 }
652 else if(repoType === 'svn'){
652 else if(repoType === 'svn'){
653 tmpl += '<i class="icon-svn"></i> ';
653 tmpl += '<i class="icon-svn"></i> ';
654 }
654 }
655 if(data['private']){
655 if(data['private']){
656 tmpl += '<i class="icon-lock" ></i> ';
656 tmpl += '<i class="icon-lock" ></i> ';
657 }
657 }
658 else if(visualShowPublicIcon){
658 else if(visualShowPublicIcon){
659 tmpl += '<i class="icon-unlock-alt"></i> ';
659 tmpl += '<i class="icon-unlock-alt"></i> ';
660 }
660 }
661 }
661 }
662 tmpl += escapeMarkup(repoName);
662 tmpl += escapeMarkup(repoName);
663 return tmpl;
663 return tmpl;
664
664
665 }(result, escapeMarkup);
665 }(result, escapeMarkup);
666 };
666 };
667
667
668 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
668 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
669 return function(data, escapeMarkup) {
669 return function(data, escapeMarkup) {
670 if (!data.repo_group_id){
670 if (!data.repo_group_id){
671 return data.text; // optgroup text Repositories
671 return data.text; // optgroup text Repositories
672 }
672 }
673
673
674 var tmpl = '';
674 var tmpl = '';
675 var repoGroupName = data['text'];
675 var repoGroupName = data['text'];
676
676
677 if(data){
677 if(data){
678
678
679 tmpl += '<i class="icon-folder-close"></i> ';
679 tmpl += '<i class="icon-folder-close"></i> ';
680
680
681 }
681 }
682 tmpl += escapeMarkup(repoGroupName);
682 tmpl += escapeMarkup(repoGroupName);
683 return tmpl;
683 return tmpl;
684
684
685 }(result, escapeMarkup);
685 }(result, escapeMarkup);
686 };
686 };
687
687
688 var escapeRegExChars = function (value) {
688 var escapeRegExChars = function (value) {
689 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
689 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
690 };
690 };
691
691
692 var getRepoIcon = function(repo_type) {
692 var getRepoIcon = function(repo_type) {
693 if (repo_type === 'hg') {
693 if (repo_type === 'hg') {
694 return '<i class="icon-hg"></i> ';
694 return '<i class="icon-hg"></i> ';
695 }
695 }
696 else if (repo_type === 'git') {
696 else if (repo_type === 'git') {
697 return '<i class="icon-git"></i> ';
697 return '<i class="icon-git"></i> ';
698 }
698 }
699 else if (repo_type === 'svn') {
699 else if (repo_type === 'svn') {
700 return '<i class="icon-svn"></i> ';
700 return '<i class="icon-svn"></i> ';
701 }
701 }
702 return ''
702 return ''
703 };
703 };
704
704
705 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
705 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
706
706
707 if (value.split(':').length === 2) {
707 if (value.split(':').length === 2) {
708 value = value.split(':')[1]
708 value = value.split(':')[1]
709 }
709 }
710
710
711 var searchType = data['type'];
711 var searchType = data['type'];
712 var valueDisplay = data['value_display'];
712 var valueDisplay = data['value_display'];
713
713
714 var pattern = '(' + escapeRegExChars(value) + ')';
714 var pattern = '(' + escapeRegExChars(value) + ')';
715
715
716 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
716 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
717
717
718 // highlight match
718 // highlight match
719 if (searchType != 'text') {
719 if (searchType != 'text') {
720 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
720 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
721 }
721 }
722
722
723 var icon = '';
723 var icon = '';
724
724
725 if (searchType === 'hint') {
725 if (searchType === 'hint') {
726 icon += '<i class="icon-folder-close"></i> ';
726 icon += '<i class="icon-folder-close"></i> ';
727 }
727 }
728 // full text search
728 // full text search
729 else if (searchType === 'search') {
729 else if (searchType === 'search') {
730 icon += '<i class="icon-more"></i> ';
730 icon += '<i class="icon-more"></i> ';
731 }
731 }
732 // repository
732 // repository
733 else if (searchType === 'repo') {
733 else if (searchType === 'repo') {
734
734
735 var repoIcon = getRepoIcon(data['repo_type']);
735 var repoIcon = getRepoIcon(data['repo_type']);
736 icon += repoIcon;
736 icon += repoIcon;
737
737
738 if (data['private']) {
738 if (data['private']) {
739 icon += '<i class="icon-lock" ></i> ';
739 icon += '<i class="icon-lock" ></i> ';
740 }
740 }
741 else if (visualShowPublicIcon) {
741 else if (visualShowPublicIcon) {
742 icon += '<i class="icon-unlock-alt"></i> ';
742 icon += '<i class="icon-unlock-alt"></i> ';
743 }
743 }
744 }
744 }
745 // repository groups
745 // repository groups
746 else if (searchType === 'repo_group') {
746 else if (searchType === 'repo_group') {
747 icon += '<i class="icon-folder-close"></i> ';
747 icon += '<i class="icon-folder-close"></i> ';
748 }
748 }
749 // user group
749 // user group
750 else if (searchType === 'user_group') {
750 else if (searchType === 'user_group') {
751 icon += '<i class="icon-group"></i> ';
751 icon += '<i class="icon-group"></i> ';
752 }
752 }
753 // user
753 // user
754 else if (searchType === 'user') {
754 else if (searchType === 'user') {
755 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
755 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
756 }
756 }
757 // commit
757 // commit
758 else if (searchType === 'commit') {
758 else if (searchType === 'commit') {
759 var repo_data = data['repo_data'];
759 var repo_data = data['repo_data'];
760 var repoIcon = getRepoIcon(repo_data['repository_type']);
760 var repoIcon = getRepoIcon(repo_data['repository_type']);
761 if (repoIcon) {
761 if (repoIcon) {
762 icon += repoIcon;
762 icon += repoIcon;
763 } else {
763 } else {
764 icon += '<i class="icon-tag"></i>';
764 icon += '<i class="icon-tag"></i>';
765 }
765 }
766 }
766 }
767 // file
767 // file
768 else if (searchType === 'file') {
768 else if (searchType === 'file') {
769 var repo_data = data['repo_data'];
769 var repo_data = data['repo_data'];
770 var repoIcon = getRepoIcon(repo_data['repository_type']);
770 var repoIcon = getRepoIcon(repo_data['repository_type']);
771 if (repoIcon) {
771 if (repoIcon) {
772 icon += repoIcon;
772 icon += repoIcon;
773 } else {
773 } else {
774 icon += '<i class="icon-tag"></i>';
774 icon += '<i class="icon-tag"></i>';
775 }
775 }
776 }
776 }
777 // generic text
777 // generic text
778 else if (searchType === 'text') {
778 else if (searchType === 'text') {
779 icon = '';
779 icon = '';
780 }
780 }
781
781
782 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
782 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
783 return tmpl.format(icon, valueDisplay);
783 return tmpl.format(icon, valueDisplay);
784 };
784 };
785
785
786 var handleSelect = function(element, suggestion) {
786 var handleSelect = function(element, suggestion) {
787 if (suggestion.type === "hint") {
787 if (suggestion.type === "hint") {
788 // we skip action
788 // we skip action
789 $('#main_filter').focus();
789 $('#main_filter').focus();
790 }
790 }
791 else if (suggestion.type === "text") {
791 else if (suggestion.type === "text") {
792 // we skip action
792 // we skip action
793 $('#main_filter').focus();
793 $('#main_filter').focus();
794
794
795 } else {
795 } else {
796 window.location = suggestion['url'];
796 window.location = suggestion['url'];
797 }
797 }
798 };
798 };
799
799
800 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
800 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
801 if (queryLowerCase.split(':').length === 2) {
801 if (queryLowerCase.split(':').length === 2) {
802 queryLowerCase = queryLowerCase.split(':')[1]
802 queryLowerCase = queryLowerCase.split(':')[1]
803 }
803 }
804 if (suggestion.type === "text") {
804 if (suggestion.type === "text") {
805 // special case we don't want to "skip" display for
805 // special case we don't want to "skip" display for
806 return true
806 return true
807 }
807 }
808 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
808 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
809 };
809 };
810
810
811 var cleanContext = {
811 var cleanContext = {
812 repo_view_type: null,
812 repo_view_type: null,
813
813
814 repo_id: null,
814 repo_id: null,
815 repo_name: "",
815 repo_name: "",
816
816
817 repo_group_id: null,
817 repo_group_id: null,
818 repo_group_name: null
818 repo_group_name: null
819 };
819 };
820 var removeGoToFilter = function () {
820 var removeGoToFilter = function () {
821 $('.searchTagHidable').hide();
821 $('.searchTagHidable').hide();
822 $('#main_filter').autocomplete(
822 $('#main_filter').autocomplete(
823 'setOptions', {params:{search_context: cleanContext}});
823 'setOptions', {params:{search_context: cleanContext}});
824 };
824 };
825
825
826 $('#main_filter').autocomplete({
826 $('#main_filter').autocomplete({
827 serviceUrl: pyroutes.url('goto_switcher_data'),
827 serviceUrl: pyroutes.url('goto_switcher_data'),
828 params: {
828 params: {
829 "search_context": templateContext.search_context
829 "search_context": templateContext.search_context
830 },
830 },
831 minChars:2,
831 minChars:2,
832 maxHeight:400,
832 maxHeight:400,
833 deferRequestBy: 300, //miliseconds
833 deferRequestBy: 300, //miliseconds
834 tabDisabled: true,
834 tabDisabled: true,
835 autoSelectFirst: false,
835 autoSelectFirst: false,
836 formatResult: autocompleteMainFilterFormatResult,
836 formatResult: autocompleteMainFilterFormatResult,
837 lookupFilter: autocompleteMainFilterResult,
837 lookupFilter: autocompleteMainFilterResult,
838 onSelect: function (element, suggestion) {
838 onSelect: function (element, suggestion) {
839 handleSelect(element, suggestion);
839 handleSelect(element, suggestion);
840 return false;
840 return false;
841 },
841 },
842 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
842 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
843 if (jqXHR !== 'abort') {
843 if (jqXHR !== 'abort') {
844 alert("Error during search.\nError code: {0}".format(textStatus));
844 alert("Error during search.\nError code: {0}".format(textStatus));
845 window.location = '';
845 window.location = '';
846 }
846 }
847 }
847 }
848 });
848 });
849
849
850 showMainFilterBox = function () {
850 showMainFilterBox = function () {
851 $('#main_filter_help').toggle();
851 $('#main_filter_help').toggle();
852 };
852 };
853
853
854 $('#main_filter').on('keydown.autocomplete', function (e) {
854 $('#main_filter').on('keydown.autocomplete', function (e) {
855
855
856 var BACKSPACE = 8;
856 var BACKSPACE = 8;
857 var el = $(e.currentTarget);
857 var el = $(e.currentTarget);
858 if(e.which === BACKSPACE){
858 if(e.which === BACKSPACE){
859 var inputVal = el.val();
859 var inputVal = el.val();
860 if (inputVal === ""){
860 if (inputVal === ""){
861 removeGoToFilter()
861 removeGoToFilter()
862 }
862 }
863 }
863 }
864 });
864 });
865
865
866 </script>
866 </script>
867 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
867 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
868 </%def>
868 </%def>
869
869
870 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
870 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
871 <div class="modal-dialog">
871 <div class="modal-dialog">
872 <div class="modal-content">
872 <div class="modal-content">
873 <div class="modal-header">
873 <div class="modal-header">
874 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
874 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
875 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
875 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
876 </div>
876 </div>
877 <div class="modal-body">
877 <div class="modal-body">
878 <div class="block-left">
878 <div class="block-left">
879 <table class="keyboard-mappings">
879 <table class="keyboard-mappings">
880 <tbody>
880 <tbody>
881 <tr>
881 <tr>
882 <th></th>
882 <th></th>
883 <th>${_('Site-wide shortcuts')}</th>
883 <th>${_('Site-wide shortcuts')}</th>
884 </tr>
884 </tr>
885 <%
885 <%
886 elems = [
886 elems = [
887 ('/', 'Use quick search box'),
887 ('/', 'Use quick search box'),
888 ('g h', 'Goto home page'),
888 ('g h', 'Goto home page'),
889 ('g g', 'Goto my private gists page'),
889 ('g g', 'Goto my private gists page'),
890 ('g G', 'Goto my public gists page'),
890 ('g G', 'Goto my public gists page'),
891 ('g 0-9', 'Goto bookmarked items from 0-9'),
891 ('g 0-9', 'Goto bookmarked items from 0-9'),
892 ('n r', 'New repository page'),
892 ('n r', 'New repository page'),
893 ('n g', 'New gist page'),
893 ('n g', 'New gist page'),
894 ]
894 ]
895 %>
895 %>
896 %for key, desc in elems:
896 %for key, desc in elems:
897 <tr>
897 <tr>
898 <td class="keys">
898 <td class="keys">
899 <span class="key tag">${key}</span>
899 <span class="key tag">${key}</span>
900 </td>
900 </td>
901 <td>${desc}</td>
901 <td>${desc}</td>
902 </tr>
902 </tr>
903 %endfor
903 %endfor
904 </tbody>
904 </tbody>
905 </table>
905 </table>
906 </div>
906 </div>
907 <div class="block-left">
907 <div class="block-left">
908 <table class="keyboard-mappings">
908 <table class="keyboard-mappings">
909 <tbody>
909 <tbody>
910 <tr>
910 <tr>
911 <th></th>
911 <th></th>
912 <th>${_('Repositories')}</th>
912 <th>${_('Repositories')}</th>
913 </tr>
913 </tr>
914 <%
914 <%
915 elems = [
915 elems = [
916 ('g s', 'Goto summary page'),
916 ('g s', 'Goto summary page'),
917 ('g c', 'Goto changelog page'),
917 ('g c', 'Goto changelog page'),
918 ('g f', 'Goto files page'),
918 ('g f', 'Goto files page'),
919 ('g F', 'Goto files page with file search activated'),
919 ('g F', 'Goto files page with file search activated'),
920 ('g p', 'Goto pull requests page'),
920 ('g p', 'Goto pull requests page'),
921 ('g o', 'Goto repository settings'),
921 ('g o', 'Goto repository settings'),
922 ('g O', 'Goto repository permissions settings'),
922 ('g O', 'Goto repository permissions settings'),
923 ]
923 ]
924 %>
924 %>
925 %for key, desc in elems:
925 %for key, desc in elems:
926 <tr>
926 <tr>
927 <td class="keys">
927 <td class="keys">
928 <span class="key tag">${key}</span>
928 <span class="key tag">${key}</span>
929 </td>
929 </td>
930 <td>${desc}</td>
930 <td>${desc}</td>
931 </tr>
931 </tr>
932 %endfor
932 %endfor
933 </tbody>
933 </tbody>
934 </table>
934 </table>
935 </div>
935 </div>
936 </div>
936 </div>
937 <div class="modal-footer">
937 <div class="modal-footer">
938 </div>
938 </div>
939 </div><!-- /.modal-content -->
939 </div><!-- /.modal-content -->
940 </div><!-- /.modal-dialog -->
940 </div><!-- /.modal-dialog -->
941 </div><!-- /.modal -->
941 </div><!-- /.modal -->
942
942
@@ -1,407 +1,407 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 ## usage:
2 ## usage:
3 ## <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
3 ## <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
4 ## ${comment.comment_block(comment)}
4 ## ${comment.comment_block(comment)}
5 ##
5 ##
6 <%namespace name="base" file="/base/base.mako"/>
6 <%namespace name="base" file="/base/base.mako"/>
7
7
8 <%def name="comment_block(comment, inline=False)">
8 <%def name="comment_block(comment, inline=False)">
9 <% pr_index_ver = comment.get_index_version(getattr(c, 'versions', [])) %>
9 <% pr_index_ver = comment.get_index_version(getattr(c, 'versions', [])) %>
10 <% latest_ver = len(getattr(c, 'versions', [])) %>
10 <% latest_ver = len(getattr(c, 'versions', [])) %>
11 % if inline:
11 % if inline:
12 <% outdated_at_ver = comment.outdated_at_version(getattr(c, 'at_version_num', None)) %>
12 <% outdated_at_ver = comment.outdated_at_version(getattr(c, 'at_version_num', None)) %>
13 % else:
13 % else:
14 <% outdated_at_ver = comment.older_than_version(getattr(c, 'at_version_num', None)) %>
14 <% outdated_at_ver = comment.older_than_version(getattr(c, 'at_version_num', None)) %>
15 % endif
15 % endif
16
16
17
17
18 <div class="comment
18 <div class="comment
19 ${'comment-inline' if inline else 'comment-general'}
19 ${'comment-inline' if inline else 'comment-general'}
20 ${'comment-outdated' if outdated_at_ver else 'comment-current'}"
20 ${'comment-outdated' if outdated_at_ver else 'comment-current'}"
21 id="comment-${comment.comment_id}"
21 id="comment-${comment.comment_id}"
22 line="${comment.line_no}"
22 line="${comment.line_no}"
23 data-comment-id="${comment.comment_id}"
23 data-comment-id="${comment.comment_id}"
24 data-comment-type="${comment.comment_type}"
24 data-comment-type="${comment.comment_type}"
25 data-comment-line-no="${comment.line_no}"
25 data-comment-line-no="${comment.line_no}"
26 data-comment-inline=${h.json.dumps(inline)}
26 data-comment-inline=${h.json.dumps(inline)}
27 style="${'display: none;' if outdated_at_ver else ''}">
27 style="${'display: none;' if outdated_at_ver else ''}">
28
28
29 <div class="meta">
29 <div class="meta">
30 <div class="comment-type-label">
30 <div class="comment-type-label">
31 <div class="comment-label ${comment.comment_type or 'note'}" id="comment-label-${comment.comment_id}" title="line: ${comment.line_no}">
31 <div class="comment-label ${comment.comment_type or 'note'}" id="comment-label-${comment.comment_id}" title="line: ${comment.line_no}">
32 % if comment.comment_type == 'todo':
32 % if comment.comment_type == 'todo':
33 % if comment.resolved:
33 % if comment.resolved:
34 <div class="resolved tooltip" title="${_('Resolved by comment #{}').format(comment.resolved.comment_id)}">
34 <div class="resolved tooltip" title="${_('Resolved by comment #{}').format(comment.resolved.comment_id)}">
35 <a href="#comment-${comment.resolved.comment_id}">${comment.comment_type}</a>
35 <a href="#comment-${comment.resolved.comment_id}">${comment.comment_type}</a>
36 </div>
36 </div>
37 % else:
37 % else:
38 <div class="resolved tooltip" style="display: none">
38 <div class="resolved tooltip" style="display: none">
39 <span>${comment.comment_type}</span>
39 <span>${comment.comment_type}</span>
40 </div>
40 </div>
41 <div class="resolve tooltip" onclick="return Rhodecode.comments.createResolutionComment(${comment.comment_id});" title="${_('Click to resolve this comment')}">
41 <div class="resolve tooltip" onclick="return Rhodecode.comments.createResolutionComment(${comment.comment_id});" title="${_('Click to resolve this comment')}">
42 ${comment.comment_type}
42 ${comment.comment_type}
43 </div>
43 </div>
44 % endif
44 % endif
45 % else:
45 % else:
46 % if comment.resolved_comment:
46 % if comment.resolved_comment:
47 fix
47 fix
48 % else:
48 % else:
49 ${comment.comment_type or 'note'}
49 ${comment.comment_type or 'note'}
50 % endif
50 % endif
51 % endif
51 % endif
52 </div>
52 </div>
53 </div>
53 </div>
54
54
55 <div class="author ${'author-inline' if inline else 'author-general'}">
55 <div class="author ${'author-inline' if inline else 'author-general'}">
56 ${base.gravatar_with_user(comment.author.email, 16)}
56 ${base.gravatar_with_user(comment.author.email, 16)}
57 </div>
57 </div>
58 <div class="date">
58 <div class="date">
59 ${h.age_component(comment.modified_at, time_is_local=True)}
59 ${h.age_component(comment.modified_at, time_is_local=True)}
60 </div>
60 </div>
61 % if inline:
61 % if inline:
62 <span></span>
62 <span></span>
63 % else:
63 % else:
64 <div class="status-change">
64 <div class="status-change">
65 % if comment.pull_request:
65 % if comment.pull_request:
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)}">
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 % if comment.status_change:
67 % if comment.status_change:
68 ${_('pull request #%s') % comment.pull_request.pull_request_id}:
68 ${_('pull request #%s') % comment.pull_request.pull_request_id}:
69 % else:
69 % else:
70 ${_('pull request #%s') % comment.pull_request.pull_request_id}
70 ${_('pull request #%s') % comment.pull_request.pull_request_id}
71 % endif
71 % endif
72 </a>
72 </a>
73 % else:
73 % else:
74 % if comment.status_change:
74 % if comment.status_change:
75 ${_('Status change on commit')}:
75 ${_('Status change on commit')}:
76 % endif
76 % endif
77 % endif
77 % endif
78 </div>
78 </div>
79 % endif
79 % endif
80
80
81 % if comment.status_change:
81 % if comment.status_change:
82 <div class="${'flag_status %s' % comment.status_change[0].status}"></div>
82 <div class="${'flag_status %s' % comment.status_change[0].status}"></div>
83 <div title="${_('Commit status')}" class="changeset-status-lbl">
83 <div title="${_('Commit status')}" class="changeset-status-lbl">
84 ${comment.status_change[0].status_lbl}
84 ${comment.status_change[0].status_lbl}
85 </div>
85 </div>
86 % endif
86 % endif
87
87
88 % if comment.resolved_comment:
88 % if comment.resolved_comment:
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)})">
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 ${_('resolves comment #{}').format(comment.resolved_comment.comment_id)}
90 ${_('resolves comment #{}').format(comment.resolved_comment.comment_id)}
91 </a>
91 </a>
92 % endif
92 % endif
93
93
94 <a class="permalink" href="#comment-${comment.comment_id}"> &para;</a>
94 <a class="permalink" href="#comment-${comment.comment_id}"> &para;</a>
95
95
96 <div class="comment-links-block">
96 <div class="comment-links-block">
97 % if comment.pull_request and comment.pull_request.author.user_id == comment.author.user_id:
97 % if comment.pull_request and comment.pull_request.author.user_id == comment.author.user_id:
98 <span class="tag authortag tooltip" title="${_('Pull request author')}">
98 <span class="tag authortag tooltip" title="${_('Pull request author')}">
99 ${_('author')}
99 ${_('author')}
100 </span>
100 </span>
101 |
101 |
102 % endif
102 % endif
103 % if inline:
103 % if inline:
104 <div class="pr-version-inline">
104 <div class="pr-version-inline">
105 <a href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}">
105 <a href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}">
106 % if outdated_at_ver:
106 % if outdated_at_ver:
107 <code class="pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
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 outdated ${'v{}'.format(pr_index_ver)} |
108 outdated ${'v{}'.format(pr_index_ver)} |
109 </code>
109 </code>
110 % elif pr_index_ver:
110 % elif pr_index_ver:
111 <code class="pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
111 <code class="pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
112 ${'v{}'.format(pr_index_ver)} |
112 ${'v{}'.format(pr_index_ver)} |
113 </code>
113 </code>
114 % endif
114 % endif
115 </a>
115 </a>
116 </div>
116 </div>
117 % else:
117 % else:
118 % if comment.pull_request_version_id and pr_index_ver:
118 % if comment.pull_request_version_id and pr_index_ver:
119 |
119 |
120 <div class="pr-version">
120 <div class="pr-version">
121 % if comment.outdated:
121 % if comment.outdated:
122 <a href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}">
122 <a href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}">
123 ${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}
123 ${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}
124 </a>
124 </a>
125 % else:
125 % else:
126 <div title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
126 <div title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
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)}">
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 <code class="pr-version-num">
128 <code class="pr-version-num">
129 ${'v{}'.format(pr_index_ver)}
129 ${'v{}'.format(pr_index_ver)}
130 </code>
130 </code>
131 </a>
131 </a>
132 </div>
132 </div>
133 % endif
133 % endif
134 </div>
134 </div>
135 % endif
135 % endif
136 % endif
136 % endif
137
137
138 ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
138 ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
139 ## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated
139 ## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated
140 %if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())):
140 %if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())):
141 ## permissions to delete
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 ## TODO: dan: add edit comment here
143 ## TODO: dan: add edit comment here
144 <a onclick="return Rhodecode.comments.deleteComment(this);" class="delete-comment"> ${_('Delete')}</a>
144 <a onclick="return Rhodecode.comments.deleteComment(this);" class="delete-comment"> ${_('Delete')}</a>
145 %else:
145 %else:
146 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
146 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
147 %endif
147 %endif
148 %else:
148 %else:
149 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
149 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
150 %endif
150 %endif
151
151
152 % if outdated_at_ver:
152 % if outdated_at_ver:
153 | <a onclick="return Rhodecode.comments.prevOutdatedComment(this);" class="prev-comment"> ${_('Prev')}</a>
153 | <a onclick="return Rhodecode.comments.prevOutdatedComment(this);" class="prev-comment"> ${_('Prev')}</a>
154 | <a onclick="return Rhodecode.comments.nextOutdatedComment(this);" class="next-comment"> ${_('Next')}</a>
154 | <a onclick="return Rhodecode.comments.nextOutdatedComment(this);" class="next-comment"> ${_('Next')}</a>
155 % else:
155 % else:
156 | <a onclick="return Rhodecode.comments.prevComment(this);" class="prev-comment"> ${_('Prev')}</a>
156 | <a onclick="return Rhodecode.comments.prevComment(this);" class="prev-comment"> ${_('Prev')}</a>
157 | <a onclick="return Rhodecode.comments.nextComment(this);" class="next-comment"> ${_('Next')}</a>
157 | <a onclick="return Rhodecode.comments.nextComment(this);" class="next-comment"> ${_('Next')}</a>
158 % endif
158 % endif
159
159
160 </div>
160 </div>
161 </div>
161 </div>
162 <div class="text">
162 <div class="text">
163 ${h.render(comment.text, renderer=comment.renderer, mentions=True)}
163 ${h.render(comment.text, renderer=comment.renderer, mentions=True)}
164 </div>
164 </div>
165
165
166 </div>
166 </div>
167 </%def>
167 </%def>
168
168
169 ## generate main comments
169 ## generate main comments
170 <%def name="generate_comments(comments, include_pull_request=False, is_pull_request=False)">
170 <%def name="generate_comments(comments, include_pull_request=False, is_pull_request=False)">
171 <div class="general-comments" id="comments">
171 <div class="general-comments" id="comments">
172 %for comment in comments:
172 %for comment in comments:
173 <div id="comment-tr-${comment.comment_id}">
173 <div id="comment-tr-${comment.comment_id}">
174 ## only render comments that are not from pull request, or from
174 ## only render comments that are not from pull request, or from
175 ## pull request and a status change
175 ## pull request and a status change
176 %if not comment.pull_request or (comment.pull_request and comment.status_change) or include_pull_request:
176 %if not comment.pull_request or (comment.pull_request and comment.status_change) or include_pull_request:
177 ${comment_block(comment)}
177 ${comment_block(comment)}
178 %endif
178 %endif
179 </div>
179 </div>
180 %endfor
180 %endfor
181 ## to anchor ajax comments
181 ## to anchor ajax comments
182 <div id="injected_page_comments"></div>
182 <div id="injected_page_comments"></div>
183 </div>
183 </div>
184 </%def>
184 </%def>
185
185
186
186
187 <%def name="comments(post_url, cur_status, is_pull_request=False, is_compare=False, change_status=True, form_extras=None)">
187 <%def name="comments(post_url, cur_status, is_pull_request=False, is_compare=False, change_status=True, form_extras=None)">
188
188
189 <div class="comments">
189 <div class="comments">
190 <%
190 <%
191 if is_pull_request:
191 if is_pull_request:
192 placeholder = _('Leave a comment on this Pull Request.')
192 placeholder = _('Leave a comment on this Pull Request.')
193 elif is_compare:
193 elif is_compare:
194 placeholder = _('Leave a comment on {} commits in this range.').format(len(form_extras))
194 placeholder = _('Leave a comment on {} commits in this range.').format(len(form_extras))
195 else:
195 else:
196 placeholder = _('Leave a comment on this Commit.')
196 placeholder = _('Leave a comment on this Commit.')
197 %>
197 %>
198
198
199 % if c.rhodecode_user.username != h.DEFAULT_USER:
199 % if c.rhodecode_user.username != h.DEFAULT_USER:
200 <div class="js-template" id="cb-comment-general-form-template">
200 <div class="js-template" id="cb-comment-general-form-template">
201 ## template generated for injection
201 ## template generated for injection
202 ${comment_form(form_type='general', review_statuses=c.commit_statuses, form_extras=form_extras)}
202 ${comment_form(form_type='general', review_statuses=c.commit_statuses, form_extras=form_extras)}
203 </div>
203 </div>
204
204
205 <div id="cb-comment-general-form-placeholder" class="comment-form ac">
205 <div id="cb-comment-general-form-placeholder" class="comment-form ac">
206 ## inject form here
206 ## inject form here
207 </div>
207 </div>
208 <script type="text/javascript">
208 <script type="text/javascript">
209 var lineNo = 'general';
209 var lineNo = 'general';
210 var resolvesCommentId = null;
210 var resolvesCommentId = null;
211 var generalCommentForm = Rhodecode.comments.createGeneralComment(
211 var generalCommentForm = Rhodecode.comments.createGeneralComment(
212 lineNo, "${placeholder}", resolvesCommentId);
212 lineNo, "${placeholder}", resolvesCommentId);
213
213
214 // set custom success callback on rangeCommit
214 // set custom success callback on rangeCommit
215 % if is_compare:
215 % if is_compare:
216 generalCommentForm.setHandleFormSubmit(function(o) {
216 generalCommentForm.setHandleFormSubmit(function(o) {
217 var self = generalCommentForm;
217 var self = generalCommentForm;
218
218
219 var text = self.cm.getValue();
219 var text = self.cm.getValue();
220 var status = self.getCommentStatus();
220 var status = self.getCommentStatus();
221 var commentType = self.getCommentType();
221 var commentType = self.getCommentType();
222
222
223 if (text === "" && !status) {
223 if (text === "" && !status) {
224 return;
224 return;
225 }
225 }
226
226
227 // we can pick which commits we want to make the comment by
227 // we can pick which commits we want to make the comment by
228 // selecting them via click on preview pane, this will alter the hidden inputs
228 // selecting them via click on preview pane, this will alter the hidden inputs
229 var cherryPicked = $('#changeset_compare_view_content .compare_select.hl').length > 0;
229 var cherryPicked = $('#changeset_compare_view_content .compare_select.hl').length > 0;
230
230
231 var commitIds = [];
231 var commitIds = [];
232 $('#changeset_compare_view_content .compare_select').each(function(el) {
232 $('#changeset_compare_view_content .compare_select').each(function(el) {
233 var commitId = this.id.replace('row-', '');
233 var commitId = this.id.replace('row-', '');
234 if ($(this).hasClass('hl') || !cherryPicked) {
234 if ($(this).hasClass('hl') || !cherryPicked) {
235 $("input[data-commit-id='{0}']".format(commitId)).val(commitId);
235 $("input[data-commit-id='{0}']".format(commitId)).val(commitId);
236 commitIds.push(commitId);
236 commitIds.push(commitId);
237 } else {
237 } else {
238 $("input[data-commit-id='{0}']".format(commitId)).val('')
238 $("input[data-commit-id='{0}']".format(commitId)).val('')
239 }
239 }
240 });
240 });
241
241
242 self.setActionButtonsDisabled(true);
242 self.setActionButtonsDisabled(true);
243 self.cm.setOption("readOnly", true);
243 self.cm.setOption("readOnly", true);
244 var postData = {
244 var postData = {
245 'text': text,
245 'text': text,
246 'changeset_status': status,
246 'changeset_status': status,
247 'comment_type': commentType,
247 'comment_type': commentType,
248 'commit_ids': commitIds,
248 'commit_ids': commitIds,
249 'csrf_token': CSRF_TOKEN
249 'csrf_token': CSRF_TOKEN
250 };
250 };
251
251
252 var submitSuccessCallback = function(o) {
252 var submitSuccessCallback = function(o) {
253 location.reload(true);
253 location.reload(true);
254 };
254 };
255 var submitFailCallback = function(){
255 var submitFailCallback = function(){
256 self.resetCommentFormState(text)
256 self.resetCommentFormState(text)
257 };
257 };
258 self.submitAjaxPOST(
258 self.submitAjaxPOST(
259 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
259 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
260 });
260 });
261 % endif
261 % endif
262
262
263
263
264 </script>
264 </script>
265 % else:
265 % else:
266 ## form state when not logged in
266 ## form state when not logged in
267 <div class="comment-form ac">
267 <div class="comment-form ac">
268
268
269 <div class="comment-area">
269 <div class="comment-area">
270 <div class="comment-area-header">
270 <div class="comment-area-header">
271 <ul class="nav-links clearfix">
271 <ul class="nav-links clearfix">
272 <li class="active">
272 <li class="active">
273 <a class="disabled" href="#edit-btn" disabled="disabled" onclick="return false">${_('Write')}</a>
273 <a class="disabled" href="#edit-btn" disabled="disabled" onclick="return false">${_('Write')}</a>
274 </li>
274 </li>
275 <li class="">
275 <li class="">
276 <a class="disabled" href="#preview-btn" disabled="disabled" onclick="return false">${_('Preview')}</a>
276 <a class="disabled" href="#preview-btn" disabled="disabled" onclick="return false">${_('Preview')}</a>
277 </li>
277 </li>
278 </ul>
278 </ul>
279 </div>
279 </div>
280
280
281 <div class="comment-area-write" style="display: block;">
281 <div class="comment-area-write" style="display: block;">
282 <div id="edit-container">
282 <div id="edit-container">
283 <div style="padding: 40px 0">
283 <div style="padding: 40px 0">
284 ${_('You need to be logged in to leave comments.')}
284 ${_('You need to be logged in to leave comments.')}
285 <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a>
285 <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a>
286 </div>
286 </div>
287 </div>
287 </div>
288 <div id="preview-container" class="clearfix" style="display: none;">
288 <div id="preview-container" class="clearfix" style="display: none;">
289 <div id="preview-box" class="preview-box"></div>
289 <div id="preview-box" class="preview-box"></div>
290 </div>
290 </div>
291 </div>
291 </div>
292
292
293 <div class="comment-area-footer">
293 <div class="comment-area-footer">
294 <div class="toolbar">
294 <div class="toolbar">
295 <div class="toolbar-text">
295 <div class="toolbar-text">
296 </div>
296 </div>
297 </div>
297 </div>
298 </div>
298 </div>
299 </div>
299 </div>
300
300
301 <div class="comment-footer">
301 <div class="comment-footer">
302 </div>
302 </div>
303
303
304 </div>
304 </div>
305 % endif
305 % endif
306
306
307 <script type="text/javascript">
307 <script type="text/javascript">
308 bindToggleButtons();
308 bindToggleButtons();
309 </script>
309 </script>
310 </div>
310 </div>
311 </%def>
311 </%def>
312
312
313
313
314 <%def name="comment_form(form_type, form_id='', lineno_id='{1}', review_statuses=None, form_extras=None)">
314 <%def name="comment_form(form_type, form_id='', lineno_id='{1}', review_statuses=None, form_extras=None)">
315 ## comment injected based on assumption that user is logged in
315 ## comment injected based on assumption that user is logged in
316
316
317 <form ${'id="{}"'.format(form_id) if form_id else '' |n} action="#" method="GET">
317 <form ${'id="{}"'.format(form_id) if form_id else '' |n} action="#" method="GET">
318
318
319 <div class="comment-area">
319 <div class="comment-area">
320 <div class="comment-area-header">
320 <div class="comment-area-header">
321 <ul class="nav-links clearfix">
321 <ul class="nav-links clearfix">
322 <li class="active">
322 <li class="active">
323 <a href="#edit-btn" tabindex="-1" id="edit-btn_${lineno_id}">${_('Write')}</a>
323 <a href="#edit-btn" tabindex="-1" id="edit-btn_${lineno_id}">${_('Write')}</a>
324 </li>
324 </li>
325 <li class="">
325 <li class="">
326 <a href="#preview-btn" tabindex="-1" id="preview-btn_${lineno_id}">${_('Preview')}</a>
326 <a href="#preview-btn" tabindex="-1" id="preview-btn_${lineno_id}">${_('Preview')}</a>
327 </li>
327 </li>
328 <li class="pull-right">
328 <li class="pull-right">
329 <select class="comment-type" id="comment_type_${lineno_id}" name="comment_type">
329 <select class="comment-type" id="comment_type_${lineno_id}" name="comment_type">
330 % for val in c.visual.comment_types:
330 % for val in c.visual.comment_types:
331 <option value="${val}">${val.upper()}</option>
331 <option value="${val}">${val.upper()}</option>
332 % endfor
332 % endfor
333 </select>
333 </select>
334 </li>
334 </li>
335 </ul>
335 </ul>
336 </div>
336 </div>
337
337
338 <div class="comment-area-write" style="display: block;">
338 <div class="comment-area-write" style="display: block;">
339 <div id="edit-container_${lineno_id}">
339 <div id="edit-container_${lineno_id}">
340 <textarea id="text_${lineno_id}" name="text" class="comment-block-ta ac-input"></textarea>
340 <textarea id="text_${lineno_id}" name="text" class="comment-block-ta ac-input"></textarea>
341 </div>
341 </div>
342 <div id="preview-container_${lineno_id}" class="clearfix" style="display: none;">
342 <div id="preview-container_${lineno_id}" class="clearfix" style="display: none;">
343 <div id="preview-box_${lineno_id}" class="preview-box"></div>
343 <div id="preview-box_${lineno_id}" class="preview-box"></div>
344 </div>
344 </div>
345 </div>
345 </div>
346
346
347 <div class="comment-area-footer">
347 <div class="comment-area-footer">
348 <div class="toolbar">
348 <div class="toolbar">
349 <div class="toolbar-text">
349 <div class="toolbar-text">
350 ${(_('Comments parsed using %s syntax with %s, and %s actions support.') % (
350 ${(_('Comments parsed using %s syntax with %s, and %s actions support.') % (
351 ('<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
351 ('<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
352 ('<span class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user')),
352 ('<span class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user')),
353 ('<span class="tooltip" title="%s">`/`</span>' % _('Start typing with / for certain actions to be triggered via text box.'))
353 ('<span class="tooltip" title="%s">`/`</span>' % _('Start typing with / for certain actions to be triggered via text box.'))
354 )
354 )
355 )|n}
355 )|n}
356 </div>
356 </div>
357 </div>
357 </div>
358 </div>
358 </div>
359 </div>
359 </div>
360
360
361 <div class="comment-footer">
361 <div class="comment-footer">
362
362
363 % if review_statuses:
363 % if review_statuses:
364 <div class="status_box">
364 <div class="status_box">
365 <select id="change_status_${lineno_id}" name="changeset_status">
365 <select id="change_status_${lineno_id}" name="changeset_status">
366 <option></option> ## Placeholder
366 <option></option> ## Placeholder
367 % for status, lbl in review_statuses:
367 % for status, lbl in review_statuses:
368 <option value="${status}" data-status="${status}">${lbl}</option>
368 <option value="${status}" data-status="${status}">${lbl}</option>
369 %if is_pull_request and change_status and status in ('approved', 'rejected'):
369 %if is_pull_request and change_status and status in ('approved', 'rejected'):
370 <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option>
370 <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option>
371 %endif
371 %endif
372 % endfor
372 % endfor
373 </select>
373 </select>
374 </div>
374 </div>
375 % endif
375 % endif
376
376
377 ## inject extra inputs into the form
377 ## inject extra inputs into the form
378 % if form_extras and isinstance(form_extras, (list, tuple)):
378 % if form_extras and isinstance(form_extras, (list, tuple)):
379 <div id="comment_form_extras">
379 <div id="comment_form_extras">
380 % for form_ex_el in form_extras:
380 % for form_ex_el in form_extras:
381 ${form_ex_el|n}
381 ${form_ex_el|n}
382 % endfor
382 % endfor
383 </div>
383 </div>
384 % endif
384 % endif
385
385
386 <div class="action-buttons">
386 <div class="action-buttons">
387 ## inline for has a file, and line-number together with cancel hide button.
387 ## inline for has a file, and line-number together with cancel hide button.
388 % if form_type == 'inline':
388 % if form_type == 'inline':
389 <input type="hidden" name="f_path" value="{0}">
389 <input type="hidden" name="f_path" value="{0}">
390 <input type="hidden" name="line" value="${lineno_id}">
390 <input type="hidden" name="line" value="${lineno_id}">
391 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
391 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
392 ${_('Cancel')}
392 ${_('Cancel')}
393 </button>
393 </button>
394 % endif
394 % endif
395
395
396 % if form_type != 'inline':
396 % if form_type != 'inline':
397 <div class="action-buttons-extra"></div>
397 <div class="action-buttons-extra"></div>
398 % endif
398 % endif
399
399
400 ${h.submit('save', _('Comment'), class_='btn btn-success comment-button-input')}
400 ${h.submit('save', _('Comment'), class_='btn btn-success comment-button-input')}
401
401
402 </div>
402 </div>
403 </div>
403 </div>
404
404
405 </form>
405 </form>
406
406
407 </%def> No newline at end of file
407 </%def>
@@ -1,131 +1,124 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3
3
4 <%def name="menu_bar_subnav()">
4 <%def name="menu_bar_subnav()">
5 % if c.repo_group:
5 % if c.repo_group:
6 ${self.repo_group_menu(active='home')}
6 ${self.repo_group_menu(active='home')}
7 % endif
7 % endif
8 </%def>
8 </%def>
9
9
10
10
11 <%def name="main()">
11 <%def name="main()">
12 <div class="box">
12 <div class="box">
13 <!-- box / title -->
13 <!-- box / title -->
14 <div class="title">
14 <div class="title">
15 %if c.rhodecode_user.username != h.DEFAULT_USER:
15 %if c.rhodecode_user.username != h.DEFAULT_USER:
16 <div class="block-right">
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 %if not c.repo_group:
17 %if not c.repo_group:
25 ## no repository group context here
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 <a href="${h.route_path('repo_new')}" class="btn btn-small btn-success btn-primary">${_('Add Repository')}</a>
20 <a href="${h.route_path('repo_new')}" class="btn btn-small btn-success btn-primary">${_('Add Repository')}</a>
28 %endif
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 <a href="${h.route_path('repo_group_new')}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
24 <a href="${h.route_path('repo_group_new')}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
32 %endif
25 %endif
33 %endif
26 %endif
34 </div>
27 </div>
35 %endif
28 %endif
36 </div>
29 </div>
37 <!-- end box / title -->
30 <!-- end box / title -->
38 <div class="table">
31 <div class="table">
39 <div id="groups_list_wrap">
32 <div id="groups_list_wrap">
40 <table id="group_list_table" class="display" style="width: 100%"></table>
33 <table id="group_list_table" class="display" style="width: 100%"></table>
41 </div>
34 </div>
42 </div>
35 </div>
43
36
44 <div class="table">
37 <div class="table">
45 <div id="repos_list_wrap">
38 <div id="repos_list_wrap">
46 <table id="repo_list_table" class="display" style="width: 100%"></table>
39 <table id="repo_list_table" class="display" style="width: 100%"></table>
47 </div>
40 </div>
48 </div>
41 </div>
49
42
50 ## no repository groups and repos present, show something to the users
43 ## no repository groups and repos present, show something to the users
51 % if c.repo_groups_data == '[]' and c.repos_data == '[]':
44 % if c.repo_groups_data == '[]' and c.repos_data == '[]':
52 <div class="table">
45 <div class="table">
53 <h2 class="no-object-border">
46 <h2 class="no-object-border">
54 ${_('No repositories or repositories groups exists here.')}
47 ${_('No repositories or repositories groups exists here.')}
55 </h2>
48 </h2>
56 </div>
49 </div>
57 % endif
50 % endif
58
51
59 </div>
52 </div>
60 <script>
53 <script>
61 $(document).ready(function() {
54 $(document).ready(function() {
62
55
63 // repo group list
56 // repo group list
64 % if c.repo_groups_data != '[]':
57 % if c.repo_groups_data != '[]':
65 $('#group_list_table').DataTable({
58 $('#group_list_table').DataTable({
66 data: ${c.repo_groups_data|n},
59 data: ${c.repo_groups_data|n},
67 dom: 'rtp',
60 dom: 'rtp',
68 pageLength: ${c.visual.dashboard_items},
61 pageLength: ${c.visual.dashboard_items},
69 order: [[ 0, "asc" ]],
62 order: [[ 0, "asc" ]],
70 columns: [
63 columns: [
71 { data: {"_": "name",
64 { data: {"_": "name",
72 "sort": "name_raw"}, title: "${_('Name')}", className: "truncate-wrap td-grid-name" },
65 "sort": "name_raw"}, title: "${_('Name')}", className: "truncate-wrap td-grid-name" },
73 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
66 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
74 { data: {"_": "desc",
67 { data: {"_": "desc",
75 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
68 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
76 { data: {"_": "last_change",
69 { data: {"_": "last_change",
77 "sort": "last_change_raw",
70 "sort": "last_change_raw",
78 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
71 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
79 { data: {"_": "last_changeset",
72 { data: {"_": "last_changeset",
80 "sort": "last_changeset_raw",
73 "sort": "last_changeset_raw",
81 "type": Number}, title: "", className: "td-hash" },
74 "type": Number}, title: "", className: "td-hash" },
82 { data: {"_": "owner",
75 { data: {"_": "owner",
83 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" }
76 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" }
84 ],
77 ],
85 language: {
78 language: {
86 paginate: DEFAULT_GRID_PAGINATION,
79 paginate: DEFAULT_GRID_PAGINATION,
87 emptyTable: _gettext("No repository groups available yet.")
80 emptyTable: _gettext("No repository groups available yet.")
88 },
81 },
89 "drawCallback": function( settings, json ) {
82 "drawCallback": function( settings, json ) {
90 timeagoActivate();
83 timeagoActivate();
91 quick_repo_menu();
84 quick_repo_menu();
92 }
85 }
93 });
86 });
94 % endif
87 % endif
95
88
96 // repo list
89 // repo list
97 % if c.repos_data != '[]':
90 % if c.repos_data != '[]':
98 $('#repo_list_table').DataTable({
91 $('#repo_list_table').DataTable({
99 data: ${c.repos_data|n},
92 data: ${c.repos_data|n},
100 dom: 'rtp',
93 dom: 'rtp',
101 order: [[ 0, "asc" ]],
94 order: [[ 0, "asc" ]],
102 pageLength: ${c.visual.dashboard_items},
95 pageLength: ${c.visual.dashboard_items},
103 columns: [
96 columns: [
104 { data: {"_": "name",
97 { data: {"_": "name",
105 "sort": "name_raw"}, title: "${_('Name')}", className: "truncate-wrap td-grid-name" },
98 "sort": "name_raw"}, title: "${_('Name')}", className: "truncate-wrap td-grid-name" },
106 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
99 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
107 { data: {"_": "desc",
100 { data: {"_": "desc",
108 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
101 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
109 { data: {"_": "last_change",
102 { data: {"_": "last_change",
110 "sort": "last_change_raw",
103 "sort": "last_change_raw",
111 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
104 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
112 { data: {"_": "last_changeset",
105 { data: {"_": "last_changeset",
113 "sort": "last_changeset_raw",
106 "sort": "last_changeset_raw",
114 "type": Number}, title: "${_('Commit')}", className: "td-hash" },
107 "type": Number}, title: "${_('Commit')}", className: "td-hash" },
115 { data: {"_": "owner",
108 { data: {"_": "owner",
116 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" }
109 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" }
117 ],
110 ],
118 language: {
111 language: {
119 paginate: DEFAULT_GRID_PAGINATION,
112 paginate: DEFAULT_GRID_PAGINATION,
120 emptyTable: _gettext("No repositories available yet.")
113 emptyTable: _gettext("No repositories available yet.")
121 },
114 },
122 "drawCallback": function( settings, json ) {
115 "drawCallback": function( settings, json ) {
123 timeagoActivate();
116 timeagoActivate();
124 quick_repo_menu();
117 quick_repo_menu();
125 }
118 }
126 });
119 });
127 % endif
120 % endif
128
121
129 });
122 });
130 </script>
123 </script>
131 </%def>
124 </%def>
@@ -1,231 +1,231 b''
1 <%def name="refs_counters(branches, closed_branches, tags, bookmarks)">
1 <%def name="refs_counters(branches, closed_branches, tags, bookmarks)">
2 <span class="branchtag tag">
2 <span class="branchtag tag">
3 <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">
3 <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">
4 <i class="icon-branch"></i>${_ungettext(
4 <i class="icon-branch"></i>${_ungettext(
5 '%(num)s Branch','%(num)s Branches', len(branches)) % {'num': len(branches)}}</a>
5 '%(num)s Branch','%(num)s Branches', len(branches)) % {'num': len(branches)}}</a>
6 </span>
6 </span>
7
7
8 %if closed_branches:
8 %if closed_branches:
9 <span class="branchtag tag">
9 <span class="branchtag tag">
10 <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">
10 <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">
11 <i class="icon-branch"></i>${_ungettext(
11 <i class="icon-branch"></i>${_ungettext(
12 '%(num)s Closed Branch', '%(num)s Closed Branches', len(closed_branches)) % {'num': len(closed_branches)}}</a>
12 '%(num)s Closed Branch', '%(num)s Closed Branches', len(closed_branches)) % {'num': len(closed_branches)}}</a>
13 </span>
13 </span>
14 %endif
14 %endif
15
15
16 <span class="tagtag tag">
16 <span class="tagtag tag">
17 <a href="${h.route_path('tags_home',repo_name=c.repo_name)}" class="childs">
17 <a href="${h.route_path('tags_home',repo_name=c.repo_name)}" class="childs">
18 <i class="icon-tag"></i>${_ungettext(
18 <i class="icon-tag"></i>${_ungettext(
19 '%(num)s Tag', '%(num)s Tags', len(tags)) % {'num': len(tags)}}</a>
19 '%(num)s Tag', '%(num)s Tags', len(tags)) % {'num': len(tags)}}</a>
20 </span>
20 </span>
21
21
22 %if bookmarks:
22 %if bookmarks:
23 <span class="booktag tag">
23 <span class="booktag tag">
24 <a href="${h.route_path('bookmarks_home',repo_name=c.repo_name)}" class="childs">
24 <a href="${h.route_path('bookmarks_home',repo_name=c.repo_name)}" class="childs">
25 <i class="icon-bookmark"></i>${_ungettext(
25 <i class="icon-bookmark"></i>${_ungettext(
26 '%(num)s Bookmark', '%(num)s Bookmarks', len(bookmarks)) % {'num': len(bookmarks)}}</a>
26 '%(num)s Bookmark', '%(num)s Bookmarks', len(bookmarks)) % {'num': len(bookmarks)}}</a>
27 </span>
27 </span>
28 %endif
28 %endif
29 </%def>
29 </%def>
30
30
31 <%def name="summary_detail(breadcrumbs_links, show_downloads=True)">
31 <%def name="summary_detail(breadcrumbs_links, show_downloads=True)">
32 <% summary = lambda n:{False:'summary-short'}.get(n) %>
32 <% summary = lambda n:{False:'summary-short'}.get(n) %>
33
33
34 <div id="summary-menu-stats" class="summary-detail">
34 <div id="summary-menu-stats" class="summary-detail">
35 <div class="summary-detail-header">
35 <div class="summary-detail-header">
36 <div class="breadcrumbs files_location">
36 <div class="breadcrumbs files_location">
37 <h4>
37 <h4>
38 ${breadcrumbs_links}
38 ${breadcrumbs_links}
39 </h4>
39 </h4>
40 </div>
40 </div>
41 <div id="summary_details_expand" class="btn-collapse" data-toggle="summary-details">
41 <div id="summary_details_expand" class="btn-collapse" data-toggle="summary-details">
42 ${_('Show More')}
42 ${_('Show More')}
43 </div>
43 </div>
44 </div>
44 </div>
45
45
46 <div class="fieldset">
46 <div class="fieldset">
47
47
48 <div class="left-clone">
48 <div class="left-clone">
49 <select id="clone_option" name="clone_option">
49 <select id="clone_option" name="clone_option">
50 <option value="http" selected="selected">HTTP</option>
50 <option value="http" selected="selected">HTTP</option>
51 <option value="http_id">HTTP UID</option>
51 <option value="http_id">HTTP UID</option>
52 % if c.ssh_enabled:
52 % if c.ssh_enabled:
53 <option value="ssh">SSH</option>
53 <option value="ssh">SSH</option>
54 % endif
54 % endif
55 </select>
55 </select>
56 </div>
56 </div>
57 <div class="right-clone">
57 <div class="right-clone">
58 <%
58 <%
59 maybe_disabled = ''
59 maybe_disabled = ''
60 if h.is_svn_without_proxy(c.rhodecode_db_repo):
60 if h.is_svn_without_proxy(c.rhodecode_db_repo):
61 maybe_disabled = 'disabled'
61 maybe_disabled = 'disabled'
62 %>
62 %>
63
63
64 <span id="clone_option_http">
64 <span id="clone_option_http">
65 <input type="text" class="input-monospace clone_url_input" ${maybe_disabled} readonly="readonly" value="${c.clone_repo_url}"/>
65 <input type="text" class="input-monospace clone_url_input" ${maybe_disabled} readonly="readonly" value="${c.clone_repo_url}"/>
66 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url}" title="${_('Copy the clone url')}"></i>
66 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url}" title="${_('Copy the clone url')}"></i>
67 </span>
67 </span>
68
68
69 <span style="display: none;" id="clone_option_http_id">
69 <span style="display: none;" id="clone_option_http_id">
70 <input type="text" class="input-monospace clone_url_input" ${maybe_disabled} readonly="readonly" value="${c.clone_repo_url_id}"/>
70 <input type="text" class="input-monospace clone_url_input" ${maybe_disabled} readonly="readonly" value="${c.clone_repo_url_id}"/>
71 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url_id}" title="${_('Copy the clone by id url')}"></i>
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 </span>
72 </span>
73
73
74 <span style="display: none;" id="clone_option_ssh">
74 <span style="display: none;" id="clone_option_ssh">
75 <input type="text" class="input-monospace clone_url_input" ${maybe_disabled} readonly="readonly" value="${c.clone_repo_url_ssh}"/>
75 <input type="text" class="input-monospace clone_url_input" ${maybe_disabled} readonly="readonly" value="${c.clone_repo_url_ssh}"/>
76 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url_ssh}" title="${_('Copy the clone by ssh url')}"></i>
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 </span>
77 </span>
78
78
79 % if maybe_disabled:
79 % if maybe_disabled:
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>
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 % endif
81 % endif
82
82
83 </div>
83 </div>
84 </div>
84 </div>
85
85
86 <div class="fieldset">
86 <div class="fieldset">
87 <div class="left-label-summary">
87 <div class="left-label-summary">
88 &nbsp;
88 &nbsp;
89 </div>
89 </div>
90 <div class="right-content">
90 <div class="right-content">
91 <div class="commit-info">
91 <div class="commit-info">
92 <div class="tags">
92 <div class="tags">
93 <% commit_rev = c.rhodecode_db_repo.changeset_cache.get('revision') %>
93 <% commit_rev = c.rhodecode_db_repo.changeset_cache.get('revision') %>
94 % if c.rhodecode_repo:
94 % if c.rhodecode_repo:
95 ${refs_counters(
95 ${refs_counters(
96 c.rhodecode_repo.branches,
96 c.rhodecode_repo.branches,
97 c.rhodecode_repo.branches_closed,
97 c.rhodecode_repo.branches_closed,
98 c.rhodecode_repo.tags,
98 c.rhodecode_repo.tags,
99 c.rhodecode_repo.bookmarks)}
99 c.rhodecode_repo.bookmarks)}
100 % else:
100 % else:
101 ## missing requirements can make c.rhodecode_repo None
101 ## missing requirements can make c.rhodecode_repo None
102 ${refs_counters([], [], [], [])}
102 ${refs_counters([], [], [], [])}
103 % endif
103 % endif
104
104
105 ## commits
105 ## commits
106 <span class="tag">
106 <span class="tag">
107 % if commit_rev == -1:
107 % if commit_rev == -1:
108 ${_ungettext('%(num)s Commit', '%(num)s Commits', 0) % {'num': 0}}
108 ${_ungettext('%(num)s Commit', '%(num)s Commits', 0) % {'num': 0}}
109 % else:
109 % else:
110 <a href="${h.route_path('repo_changelog', repo_name=c.repo_name)}">
110 <a href="${h.route_path('repo_changelog', repo_name=c.repo_name)}">
111 ${_ungettext('%(num)s Commit', '%(num)s Commits', commit_rev) % {'num': commit_rev}}</a>
111 ${_ungettext('%(num)s Commit', '%(num)s Commits', commit_rev) % {'num': commit_rev}}</a>
112 % endif
112 % endif
113 </span>
113 </span>
114
114
115 ## forks
115 ## forks
116 <span class="tag">
116 <span class="tag">
117 <a title="${_('Number of Repository Forks')}" href="${h.route_path('repo_forks_show_all', repo_name=c.repo_name)}">
117 <a title="${_('Number of Repository Forks')}" href="${h.route_path('repo_forks_show_all', repo_name=c.repo_name)}">
118 ${c.repository_forks} ${_ungettext('Fork', 'Forks', c.repository_forks)}</a>
118 ${c.repository_forks} ${_ungettext('Fork', 'Forks', c.repository_forks)}</a>
119 </span>
119 </span>
120
120
121 </div>
121 </div>
122 </div>
122 </div>
123 </div>
123 </div>
124 </div>
124 </div>
125
125
126 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
126 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
127 <div class="left-label-summary">
127 <div class="left-label-summary">
128 ${_('Repository size')}:
128 ${_('Repository size')}:
129 </div>
129 </div>
130 <div class="right-content">
130 <div class="right-content">
131 <div class="commit-info">
131 <div class="commit-info">
132 <div class="tags">
132 <div class="tags">
133 ## repo size
133 ## repo size
134 % if commit_rev == -1:
134 % if commit_rev == -1:
135 <span class="stats-bullet">0 B</span>
135 <span class="stats-bullet">0 B</span>
136 % else:
136 % else:
137 <span>
137 <span>
138 <a href="#showSize" onclick="calculateSize(); $(this).hide(); return false" id="show-repo-size">Show repository size</a>
138 <a href="#showSize" onclick="calculateSize(); $(this).hide(); return false" id="show-repo-size">Show repository size</a>
139 </span>
139 </span>
140 <span class="stats-bullet" id="repo_size_container" style="display:none">
140 <span class="stats-bullet" id="repo_size_container" style="display:none">
141 ${_('Calculating Repository Size...')}
141 ${_('Calculating Repository Size...')}
142 </span>
142 </span>
143 % endif
143 % endif
144 </div>
144 </div>
145 </div>
145 </div>
146 </div>
146 </div>
147 </div>
147 </div>
148
148
149 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
149 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
150 <div class="left-label-summary">
150 <div class="left-label-summary">
151 ${_('Description')}:
151 ${_('Description')}:
152 </div>
152 </div>
153 <div class="right-content">
153 <div class="right-content">
154 <div class="input ${summary(c.show_stats)}">
154 <div class="input ${summary(c.show_stats)}">
155 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
155 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
156 ${dt.repo_desc(c.rhodecode_db_repo.description_safe, c.visual.stylify_metatags)}
156 ${dt.repo_desc(c.rhodecode_db_repo.description_safe, c.visual.stylify_metatags)}
157 </div>
157 </div>
158 </div>
158 </div>
159 </div>
159 </div>
160
160
161 % if show_downloads:
161 % if show_downloads:
162 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
162 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
163 <div class="left-label-summary">
163 <div class="left-label-summary">
164 ${_('Downloads')}:
164 ${_('Downloads')}:
165 </div>
165 </div>
166 <div class="right-content">
166 <div class="right-content">
167 <div class="input ${summary(c.show_stats)} downloads">
167 <div class="input ${summary(c.show_stats)} downloads">
168 % if c.rhodecode_repo and len(c.rhodecode_repo.commit_ids) == 0:
168 % if c.rhodecode_repo and len(c.rhodecode_repo.commit_ids) == 0:
169 <span class="disabled">
169 <span class="disabled">
170 ${_('There are no downloads yet')}
170 ${_('There are no downloads yet')}
171 </span>
171 </span>
172 % elif not c.enable_downloads:
172 % elif not c.enable_downloads:
173 <span class="disabled">
173 <span class="disabled">
174 ${_('Downloads are disabled for this repository')}.
174 ${_('Downloads are disabled for this repository')}.
175 </span>
175 </span>
176 % if h.HasPermissionAll('hg.admin')('enable downloads on from summary'):
176 % if c.is_super_admin:
177 ${h.link_to(_('Enable downloads'),h.route_path('edit_repo',repo_name=c.repo_name, _anchor='repo_enable_downloads'))}
177 ${h.link_to(_('Enable downloads'),h.route_path('edit_repo',repo_name=c.repo_name, _anchor='repo_enable_downloads'))}
178 % endif
178 % endif
179 % else:
179 % else:
180 <span class="enabled">
180 <span class="enabled">
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')}">
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 <i class="icon-archive"></i> tip.zip
182 <i class="icon-archive"></i> tip.zip
183 ## replaced by some JS on select
183 ## replaced by some JS on select
184 </a>
184 </a>
185 </span>
185 </span>
186 ${h.hidden('download_options')}
186 ${h.hidden('download_options')}
187 % endif
187 % endif
188 </div>
188 </div>
189 </div>
189 </div>
190 </div>
190 </div>
191 % endif
191 % endif
192
192
193 ## Statistics
193 ## Statistics
194 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
194 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
195 <div class="left-label-summary">
195 <div class="left-label-summary">
196 ${_('Statistics')}:
196 ${_('Statistics')}:
197 </div>
197 </div>
198 <div class="right-content">
198 <div class="right-content">
199 <div class="input ${summary(c.show_stats)} statistics">
199 <div class="input ${summary(c.show_stats)} statistics">
200 % if c.show_stats:
200 % if c.show_stats:
201 <div id="lang_stats" class="enabled">
201 <div id="lang_stats" class="enabled">
202 ${_('Calculating Code Statistics...')}
202 ${_('Calculating Code Statistics...')}
203 </div>
203 </div>
204 % else:
204 % else:
205 <span class="disabled">
205 <span class="disabled">
206 ${_('Statistics are disabled for this repository')}.
206 ${_('Statistics are disabled for this repository')}.
207 </span>
207 </span>
208 % if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
208 % if c.is_super_admin:
209 ${h.link_to(_('Enable statistics'),h.route_path('edit_repo',repo_name=c.repo_name, _anchor='repo_enable_statistics'))}
209 ${h.link_to(_('Enable statistics'),h.route_path('edit_repo',repo_name=c.repo_name, _anchor='repo_enable_statistics'))}
210 % endif
210 % endif
211 % endif
211 % endif
212 </div>
212 </div>
213
213
214 </div>
214 </div>
215 </div>
215 </div>
216
216
217 </div><!--end summary-detail-->
217 </div><!--end summary-detail-->
218 </%def>
218 </%def>
219
219
220 <%def name="summary_stats(gravatar_function)">
220 <%def name="summary_stats(gravatar_function)">
221 <div class="sidebar-right">
221 <div class="sidebar-right">
222 <div class="summary-detail-header">
222 <div class="summary-detail-header">
223 <h4 class="item">
223 <h4 class="item">
224 ${_('Owner')}
224 ${_('Owner')}
225 </h4>
225 </h4>
226 </div>
226 </div>
227 <div class="sidebar-right-content">
227 <div class="sidebar-right-content">
228 ${gravatar_function(c.rhodecode_db_repo.user.email, 16)}
228 ${gravatar_function(c.rhodecode_db_repo.user.email, 16)}
229 </div>
229 </div>
230 </div><!--end sidebar-right-->
230 </div><!--end sidebar-right-->
231 </%def>
231 </%def>
@@ -1,70 +1,70 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <div class="panel panel-default user-profile">
3 <div class="panel panel-default user-profile">
4 <div class="panel-heading">
4 <div class="panel-heading">
5 <h3 class="panel-title">${_('User group profile')}</h3>
5 <h3 class="panel-title">${_('User group profile')}</h3>
6 %if h.HasPermissionAny('hg.admin')():
6 %if c.is_super_admin:
7 ${h.link_to(_('Edit'), h.route_path('edit_user_group', user_group_id=c.user_group.users_group_id), class_='panel-edit')}
7 ${h.link_to(_('Edit'), h.route_path('edit_user_group', user_group_id=c.user_group.users_group_id), class_='panel-edit')}
8 %endif
8 %endif
9 </div>
9 </div>
10
10
11 <div class="panel-body user-profile-content">
11 <div class="panel-body user-profile-content">
12
12
13 <div class="fieldset">
13 <div class="fieldset">
14 <div class="left-label">
14 <div class="left-label">
15 ${_('Group Name')}:
15 ${_('Group Name')}:
16 </div>
16 </div>
17 <div class="right-content">
17 <div class="right-content">
18 ${c.user_group.users_group_name}
18 ${c.user_group.users_group_name}
19 </div>
19 </div>
20 </div>
20 </div>
21 <div class="fieldset">
21 <div class="fieldset">
22 <div class="left-label">
22 <div class="left-label">
23 ${_('Owner')}:
23 ${_('Owner')}:
24 </div>
24 </div>
25 <div class="group_member">
25 <div class="group_member">
26 ${base.gravatar(c.user_group.user.email, 16)}
26 ${base.gravatar(c.user_group.user.email, 16)}
27 <span class="username user">${h.link_to_user(c.user_group.user)}</span>
27 <span class="username user">${h.link_to_user(c.user_group.user)}</span>
28
28
29 </div>
29 </div>
30 </div>
30 </div>
31 <div class="fieldset">
31 <div class="fieldset">
32 <div class="left-label">
32 <div class="left-label">
33 ${_('Active')}:
33 ${_('Active')}:
34 </div>
34 </div>
35 <div class="right-content">
35 <div class="right-content">
36 ${c.user_group.users_group_active}
36 ${c.user_group.users_group_active}
37 </div>
37 </div>
38 </div>
38 </div>
39 % if not c.anonymous:
39 % if not c.anonymous:
40 <div class="fieldset">
40 <div class="fieldset">
41 <div class="left-label">
41 <div class="left-label">
42 ${_('Members')}:
42 ${_('Members')}:
43 </div>
43 </div>
44 <div class="right-content">
44 <div class="right-content">
45 <table id="group_members_placeholder" class="rctable group_members">
45 <table id="group_members_placeholder" class="rctable group_members">
46 <th>${_('Username')}</th>
46 <th>${_('Username')}</th>
47 % if c.group_members:
47 % if c.group_members:
48 % for user in c.group_members:
48 % for user in c.group_members:
49 <tr>
49 <tr>
50 <td id="member_user_${user.user_id}" class="td-author">
50 <td id="member_user_${user.user_id}" class="td-author">
51 <div class="group_member">
51 <div class="group_member">
52 ${base.gravatar(user.email, 16)}
52 ${base.gravatar(user.email, 16)}
53 <span class="username user">${h.link_to(h.person(user), h.route_path('user_edit',user_id=user.user_id))}</span>
53 <span class="username user">${h.link_to(h.person(user), h.route_path('user_edit',user_id=user.user_id))}</span>
54 <input type="hidden" name="__start__" value="member:mapping">
54 <input type="hidden" name="__start__" value="member:mapping">
55 <input type="hidden" name="member_user_id" value="${user.user_id}">
55 <input type="hidden" name="member_user_id" value="${user.user_id}">
56 <input type="hidden" name="type" value="existing" id="member_${user.user_id}">
56 <input type="hidden" name="type" value="existing" id="member_${user.user_id}">
57 <input type="hidden" name="__end__" value="member:mapping">
57 <input type="hidden" name="__end__" value="member:mapping">
58 </div>
58 </div>
59 </td>
59 </td>
60 </tr>
60 </tr>
61 % endfor
61 % endfor
62 % else:
62 % else:
63 <tr><td colspan="2">${_('No members yet')}</td></tr>
63 <tr><td colspan="2">${_('No members yet')}</td></tr>
64 % endif
64 % endif
65 </table>
65 </table>
66 </div>
66 </div>
67 </div>
67 </div>
68 % endif
68 % endif
69 </div>
69 </div>
70 </div> No newline at end of file
70 </div>
@@ -1,58 +1,58 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <div class="panel panel-default user-profile">
3 <div class="panel panel-default user-profile">
4 <div class="panel-heading">
4 <div class="panel-heading">
5 <h3 class="panel-title">${_('User Profile')}</h3>
5 <h3 class="panel-title">${_('User Profile')}</h3>
6 %if h.HasPermissionAny('hg.admin')():
6 %if c.is_super_admin:
7 ${h.link_to(_('Edit'), h.route_path('user_edit', user_id=c.user.user_id), class_='panel-edit')}
7 ${h.link_to(_('Edit'), h.route_path('user_edit', user_id=c.user.user_id), class_='panel-edit')}
8 %endif
8 %endif
9 </div>
9 </div>
10
10
11 <div class="panel-body user-profile-content">
11 <div class="panel-body user-profile-content">
12 <div class="fieldset">
12 <div class="fieldset">
13 <div class="left-label">
13 <div class="left-label">
14 ${_('Photo')}:
14 ${_('Photo')}:
15 </div>
15 </div>
16 <div class="right-content">
16 <div class="right-content">
17 %if c.visual.use_gravatar:
17 %if c.visual.use_gravatar:
18 ${base.gravatar(c.user.email, 100)}
18 ${base.gravatar(c.user.email, 100)}
19 %else:
19 %else:
20 ${base.gravatar(c.user.email, 20)}
20 ${base.gravatar(c.user.email, 20)}
21 ${_('Avatars are disabled')}
21 ${_('Avatars are disabled')}
22 %endif
22 %endif
23 </div>
23 </div>
24 </div>
24 </div>
25 <div class="fieldset">
25 <div class="fieldset">
26 <div class="left-label">
26 <div class="left-label">
27 ${_('Username')}:
27 ${_('Username')}:
28 </div>
28 </div>
29 <div class="right-content">
29 <div class="right-content">
30 ${c.user.username}
30 ${c.user.username}
31 </div>
31 </div>
32 </div>
32 </div>
33 <div class="fieldset">
33 <div class="fieldset">
34 <div class="left-label">
34 <div class="left-label">
35 ${_('First name')}:
35 ${_('First name')}:
36 </div>
36 </div>
37 <div class="right-content">
37 <div class="right-content">
38 ${c.user.first_name}
38 ${c.user.first_name}
39 </div>
39 </div>
40 </div>
40 </div>
41 <div class="fieldset">
41 <div class="fieldset">
42 <div class="left-label">
42 <div class="left-label">
43 ${_('Last name')}:
43 ${_('Last name')}:
44 </div>
44 </div>
45 <div class="right-content">
45 <div class="right-content">
46 ${c.user.last_name}
46 ${c.user.last_name}
47 </div>
47 </div>
48 </div>
48 </div>
49 <div class="fieldset">
49 <div class="fieldset">
50 <div class="left-label">
50 <div class="left-label">
51 ${_('Email')}:
51 ${_('Email')}:
52 </div>
52 </div>
53 <div class="right-content">
53 <div class="right-content">
54 ${c.user.email or _('Missing email, please update your user email address.')}
54 ${c.user.email or _('Missing email, please update your user email address.')}
55 </div>
55 </div>
56 </div>
56 </div>
57 </div>
57 </div>
58 </div> No newline at end of file
58 </div>
General Comments 0
You need to be logged in to leave comments. Login now