##// END OF EJS Templates
apps: modernize for python3
super-admin -
r5093:525812a8 default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,19 +1,17 b''
1
2
3 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -1,860 +1,858 b''
1
2
3 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 import time
19 import time
22 import logging
20 import logging
23 import operator
21 import operator
24
22
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
23 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
26
24
27 from rhodecode.lib import helpers as h, diffs, rc_cache
25 from rhodecode.lib import helpers as h, diffs, rc_cache
28 from rhodecode.lib.str_utils import safe_str
26 from rhodecode.lib.str_utils import safe_str
29 from rhodecode.lib.utils import repo_name_slug
27 from rhodecode.lib.utils import repo_name_slug
30 from rhodecode.lib.utils2 import (
28 from rhodecode.lib.utils2 import (
31 StrictAttributeDict, str2bool, safe_int, datetime_to_time)
29 StrictAttributeDict, str2bool, safe_int, datetime_to_time)
32 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
30 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
33 from rhodecode.lib.vcs.backends.base import EmptyCommit
31 from rhodecode.lib.vcs.backends.base import EmptyCommit
34 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
32 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
35 from rhodecode.model import repo
33 from rhodecode.model import repo
36 from rhodecode.model import repo_group
34 from rhodecode.model import repo_group
37 from rhodecode.model import user_group
35 from rhodecode.model import user_group
38 from rhodecode.model import user
36 from rhodecode.model import user
39 from rhodecode.model.db import User
37 from rhodecode.model.db import User
40 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.scm import ScmModel
41 from rhodecode.model.settings import VcsSettingsModel, IssueTrackerSettingsModel
39 from rhodecode.model.settings import VcsSettingsModel, IssueTrackerSettingsModel
42 from rhodecode.model.repo import ReadmeFinder
40 from rhodecode.model.repo import ReadmeFinder
43
41
44 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
45
43
46
44
47 ADMIN_PREFIX = '/_admin'
45 ADMIN_PREFIX = '/_admin'
48 STATIC_FILE_PREFIX = '/_static'
46 STATIC_FILE_PREFIX = '/_static'
49
47
50 URL_NAME_REQUIREMENTS = {
48 URL_NAME_REQUIREMENTS = {
51 # group name can have a slash in them, but they must not end with a slash
49 # group name can have a slash in them, but they must not end with a slash
52 'group_name': r'.*?[^/]',
50 'group_name': r'.*?[^/]',
53 'repo_group_name': r'.*?[^/]',
51 'repo_group_name': r'.*?[^/]',
54 # repo names can have a slash in them, but they must not end with a slash
52 # repo names can have a slash in them, but they must not end with a slash
55 'repo_name': r'.*?[^/]',
53 'repo_name': r'.*?[^/]',
56 # file path eats up everything at the end
54 # file path eats up everything at the end
57 'f_path': r'.*',
55 'f_path': r'.*',
58 # reference types
56 # reference types
59 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
57 'source_ref_type': r'(branch|book|tag|rev|\%\(source_ref_type\)s)',
60 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
58 'target_ref_type': r'(branch|book|tag|rev|\%\(target_ref_type\)s)',
61 }
59 }
62
60
63
61
64 def add_route_with_slash(config,name, pattern, **kw):
62 def add_route_with_slash(config,name, pattern, **kw):
65 config.add_route(name, pattern, **kw)
63 config.add_route(name, pattern, **kw)
66 if not pattern.endswith('/'):
64 if not pattern.endswith('/'):
67 config.add_route(name + '_slash', pattern + '/', **kw)
65 config.add_route(name + '_slash', pattern + '/', **kw)
68
66
69
67
70 def add_route_requirements(route_path, requirements=None):
68 def add_route_requirements(route_path, requirements=None):
71 """
69 """
72 Adds regex requirements to pyramid routes using a mapping dict
70 Adds regex requirements to pyramid routes using a mapping dict
73 e.g::
71 e.g::
74 add_route_requirements('{repo_name}/settings')
72 add_route_requirements('{repo_name}/settings')
75 """
73 """
76 requirements = requirements or URL_NAME_REQUIREMENTS
74 requirements = requirements or URL_NAME_REQUIREMENTS
77 for key, regex in list(requirements.items()):
75 for key, regex in list(requirements.items()):
78 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
76 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
79 return route_path
77 return route_path
80
78
81
79
82 def get_format_ref_id(repo):
80 def get_format_ref_id(repo):
83 """Returns a `repo` specific reference formatter function"""
81 """Returns a `repo` specific reference formatter function"""
84 if h.is_svn(repo):
82 if h.is_svn(repo):
85 return _format_ref_id_svn
83 return _format_ref_id_svn
86 else:
84 else:
87 return _format_ref_id
85 return _format_ref_id
88
86
89
87
90 def _format_ref_id(name, raw_id):
88 def _format_ref_id(name, raw_id):
91 """Default formatting of a given reference `name`"""
89 """Default formatting of a given reference `name`"""
92 return name
90 return name
93
91
94
92
95 def _format_ref_id_svn(name, raw_id):
93 def _format_ref_id_svn(name, raw_id):
96 """Special way of formatting a reference for Subversion including path"""
94 """Special way of formatting a reference for Subversion including path"""
97 return '%s@%s' % (name, raw_id)
95 return '{}@{}'.format(name, raw_id)
98
96
99
97
100 class TemplateArgs(StrictAttributeDict):
98 class TemplateArgs(StrictAttributeDict):
101 pass
99 pass
102
100
103
101
104 class BaseAppView(object):
102 class BaseAppView(object):
105
103
106 def __init__(self, context, request):
104 def __init__(self, context, request):
107 self.request = request
105 self.request = request
108 self.context = context
106 self.context = context
109 self.session = request.session
107 self.session = request.session
110 if not hasattr(request, 'user'):
108 if not hasattr(request, 'user'):
111 # NOTE(marcink): edge case, we ended up in matched route
109 # NOTE(marcink): edge case, we ended up in matched route
112 # but probably of web-app context, e.g API CALL/VCS CALL
110 # but probably of web-app context, e.g API CALL/VCS CALL
113 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
111 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
114 log.warning('Unable to process request `%s` in this scope', request)
112 log.warning('Unable to process request `%s` in this scope', request)
115 raise HTTPBadRequest()
113 raise HTTPBadRequest()
116
114
117 self._rhodecode_user = request.user # auth user
115 self._rhodecode_user = request.user # auth user
118 self._rhodecode_db_user = self._rhodecode_user.get_instance()
116 self._rhodecode_db_user = self._rhodecode_user.get_instance()
119 self._maybe_needs_password_change(
117 self._maybe_needs_password_change(
120 request.matched_route.name, self._rhodecode_db_user)
118 request.matched_route.name, self._rhodecode_db_user)
121
119
122 def _maybe_needs_password_change(self, view_name, user_obj):
120 def _maybe_needs_password_change(self, view_name, user_obj):
123
121
124 dont_check_views = [
122 dont_check_views = [
125 'channelstream_connect',
123 'channelstream_connect',
126 'ops_ping'
124 'ops_ping'
127 ]
125 ]
128 if view_name in dont_check_views:
126 if view_name in dont_check_views:
129 return
127 return
130
128
131 log.debug('Checking if user %s needs password change on view %s',
129 log.debug('Checking if user %s needs password change on view %s',
132 user_obj, view_name)
130 user_obj, view_name)
133
131
134 skip_user_views = [
132 skip_user_views = [
135 'logout', 'login',
133 'logout', 'login',
136 'my_account_password', 'my_account_password_update'
134 'my_account_password', 'my_account_password_update'
137 ]
135 ]
138
136
139 if not user_obj:
137 if not user_obj:
140 return
138 return
141
139
142 if user_obj.username == User.DEFAULT_USER:
140 if user_obj.username == User.DEFAULT_USER:
143 return
141 return
144
142
145 now = time.time()
143 now = time.time()
146 should_change = user_obj.user_data.get('force_password_change')
144 should_change = user_obj.user_data.get('force_password_change')
147 change_after = safe_int(should_change) or 0
145 change_after = safe_int(should_change) or 0
148 if should_change and now > change_after:
146 if should_change and now > change_after:
149 log.debug('User %s requires password change', user_obj)
147 log.debug('User %s requires password change', user_obj)
150 h.flash('You are required to change your password', 'warning',
148 h.flash('You are required to change your password', 'warning',
151 ignore_duplicate=True)
149 ignore_duplicate=True)
152
150
153 if view_name not in skip_user_views:
151 if view_name not in skip_user_views:
154 raise HTTPFound(
152 raise HTTPFound(
155 self.request.route_path('my_account_password'))
153 self.request.route_path('my_account_password'))
156
154
157 def _log_creation_exception(self, e, repo_name):
155 def _log_creation_exception(self, e, repo_name):
158 _ = self.request.translate
156 _ = self.request.translate
159 reason = None
157 reason = None
160 if len(e.args) == 2:
158 if len(e.args) == 2:
161 reason = e.args[1]
159 reason = e.args[1]
162
160
163 if reason == 'INVALID_CERTIFICATE':
161 if reason == 'INVALID_CERTIFICATE':
164 log.exception(
162 log.exception(
165 'Exception creating a repository: invalid certificate')
163 'Exception creating a repository: invalid certificate')
166 msg = (_('Error creating repository %s: invalid certificate')
164 msg = (_('Error creating repository %s: invalid certificate')
167 % repo_name)
165 % repo_name)
168 else:
166 else:
169 log.exception("Exception creating a repository")
167 log.exception("Exception creating a repository")
170 msg = (_('Error creating repository %s')
168 msg = (_('Error creating repository %s')
171 % repo_name)
169 % repo_name)
172 return msg
170 return msg
173
171
174 def _get_local_tmpl_context(self, include_app_defaults=True):
172 def _get_local_tmpl_context(self, include_app_defaults=True):
175 c = TemplateArgs()
173 c = TemplateArgs()
176 c.auth_user = self.request.user
174 c.auth_user = self.request.user
177 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
175 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
178 c.rhodecode_user = self.request.user
176 c.rhodecode_user = self.request.user
179
177
180 if include_app_defaults:
178 if include_app_defaults:
181 from rhodecode.lib.base import attach_context_attributes
179 from rhodecode.lib.base import attach_context_attributes
182 attach_context_attributes(c, self.request, self.request.user.user_id)
180 attach_context_attributes(c, self.request, self.request.user.user_id)
183
181
184 c.is_super_admin = c.auth_user.is_admin
182 c.is_super_admin = c.auth_user.is_admin
185
183
186 c.can_create_repo = c.is_super_admin
184 c.can_create_repo = c.is_super_admin
187 c.can_create_repo_group = c.is_super_admin
185 c.can_create_repo_group = c.is_super_admin
188 c.can_create_user_group = c.is_super_admin
186 c.can_create_user_group = c.is_super_admin
189
187
190 c.is_delegated_admin = False
188 c.is_delegated_admin = False
191
189
192 if not c.auth_user.is_default and not c.is_super_admin:
190 if not c.auth_user.is_default and not c.is_super_admin:
193 c.can_create_repo = h.HasPermissionAny('hg.create.repository')(
191 c.can_create_repo = h.HasPermissionAny('hg.create.repository')(
194 user=self.request.user)
192 user=self.request.user)
195 repositories = c.auth_user.repositories_admin or c.can_create_repo
193 repositories = c.auth_user.repositories_admin or c.can_create_repo
196
194
197 c.can_create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')(
195 c.can_create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')(
198 user=self.request.user)
196 user=self.request.user)
199 repository_groups = c.auth_user.repository_groups_admin or c.can_create_repo_group
197 repository_groups = c.auth_user.repository_groups_admin or c.can_create_repo_group
200
198
201 c.can_create_user_group = h.HasPermissionAny('hg.usergroup.create.true')(
199 c.can_create_user_group = h.HasPermissionAny('hg.usergroup.create.true')(
202 user=self.request.user)
200 user=self.request.user)
203 user_groups = c.auth_user.user_groups_admin or c.can_create_user_group
201 user_groups = c.auth_user.user_groups_admin or c.can_create_user_group
204 # delegated admin can create, or manage some objects
202 # delegated admin can create, or manage some objects
205 c.is_delegated_admin = repositories or repository_groups or user_groups
203 c.is_delegated_admin = repositories or repository_groups or user_groups
206 return c
204 return c
207
205
208 def _get_template_context(self, tmpl_args, **kwargs):
206 def _get_template_context(self, tmpl_args, **kwargs):
209
207
210 local_tmpl_args = {
208 local_tmpl_args = {
211 'defaults': {},
209 'defaults': {},
212 'errors': {},
210 'errors': {},
213 'c': tmpl_args
211 'c': tmpl_args
214 }
212 }
215 local_tmpl_args.update(kwargs)
213 local_tmpl_args.update(kwargs)
216 return local_tmpl_args
214 return local_tmpl_args
217
215
218 def load_default_context(self):
216 def load_default_context(self):
219 """
217 """
220 example:
218 example:
221
219
222 def load_default_context(self):
220 def load_default_context(self):
223 c = self._get_local_tmpl_context()
221 c = self._get_local_tmpl_context()
224 c.custom_var = 'foobar'
222 c.custom_var = 'foobar'
225
223
226 return c
224 return c
227 """
225 """
228 raise NotImplementedError('Needs implementation in view class')
226 raise NotImplementedError('Needs implementation in view class')
229
227
230
228
231 class RepoAppView(BaseAppView):
229 class RepoAppView(BaseAppView):
232
230
233 def __init__(self, context, request):
231 def __init__(self, context, request):
234 super(RepoAppView, self).__init__(context, request)
232 super().__init__(context, request)
235 self.db_repo = request.db_repo
233 self.db_repo = request.db_repo
236 self.db_repo_name = self.db_repo.repo_name
234 self.db_repo_name = self.db_repo.repo_name
237 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
235 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
238 self.db_repo_artifacts = ScmModel().get_artifacts(self.db_repo)
236 self.db_repo_artifacts = ScmModel().get_artifacts(self.db_repo)
239 self.db_repo_patterns = IssueTrackerSettingsModel(repo=self.db_repo)
237 self.db_repo_patterns = IssueTrackerSettingsModel(repo=self.db_repo)
240
238
241 def _handle_missing_requirements(self, error):
239 def _handle_missing_requirements(self, error):
242 log.error(
240 log.error(
243 'Requirements are missing for repository %s: %s',
241 'Requirements are missing for repository %s: %s',
244 self.db_repo_name, safe_str(error))
242 self.db_repo_name, safe_str(error))
245
243
246 def _prepare_and_set_clone_url(self, c):
244 def _prepare_and_set_clone_url(self, c):
247 username = ''
245 username = ''
248 if self._rhodecode_user.username != User.DEFAULT_USER:
246 if self._rhodecode_user.username != User.DEFAULT_USER:
249 username = self._rhodecode_user.username
247 username = self._rhodecode_user.username
250
248
251 _def_clone_uri = c.clone_uri_tmpl
249 _def_clone_uri = c.clone_uri_tmpl
252 _def_clone_uri_id = c.clone_uri_id_tmpl
250 _def_clone_uri_id = c.clone_uri_id_tmpl
253 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
251 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
254
252
255 c.clone_repo_url = self.db_repo.clone_url(
253 c.clone_repo_url = self.db_repo.clone_url(
256 user=username, uri_tmpl=_def_clone_uri)
254 user=username, uri_tmpl=_def_clone_uri)
257 c.clone_repo_url_id = self.db_repo.clone_url(
255 c.clone_repo_url_id = self.db_repo.clone_url(
258 user=username, uri_tmpl=_def_clone_uri_id)
256 user=username, uri_tmpl=_def_clone_uri_id)
259 c.clone_repo_url_ssh = self.db_repo.clone_url(
257 c.clone_repo_url_ssh = self.db_repo.clone_url(
260 uri_tmpl=_def_clone_uri_ssh, ssh=True)
258 uri_tmpl=_def_clone_uri_ssh, ssh=True)
261
259
262 def _get_local_tmpl_context(self, include_app_defaults=True):
260 def _get_local_tmpl_context(self, include_app_defaults=True):
263 _ = self.request.translate
261 _ = self.request.translate
264 c = super(RepoAppView, self)._get_local_tmpl_context(
262 c = super()._get_local_tmpl_context(
265 include_app_defaults=include_app_defaults)
263 include_app_defaults=include_app_defaults)
266
264
267 # register common vars for this type of view
265 # register common vars for this type of view
268 c.rhodecode_db_repo = self.db_repo
266 c.rhodecode_db_repo = self.db_repo
269 c.repo_name = self.db_repo_name
267 c.repo_name = self.db_repo_name
270 c.repository_pull_requests = self.db_repo_pull_requests
268 c.repository_pull_requests = self.db_repo_pull_requests
271 c.repository_artifacts = self.db_repo_artifacts
269 c.repository_artifacts = self.db_repo_artifacts
272 c.repository_is_user_following = ScmModel().is_following_repo(
270 c.repository_is_user_following = ScmModel().is_following_repo(
273 self.db_repo_name, self._rhodecode_user.user_id)
271 self.db_repo_name, self._rhodecode_user.user_id)
274 self.path_filter = PathFilter(None)
272 self.path_filter = PathFilter(None)
275
273
276 c.repository_requirements_missing = {}
274 c.repository_requirements_missing = {}
277 try:
275 try:
278 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
276 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
279 # NOTE(marcink):
277 # NOTE(marcink):
280 # comparison to None since if it's an object __bool__ is expensive to
278 # comparison to None since if it's an object __bool__ is expensive to
281 # calculate
279 # calculate
282 if self.rhodecode_vcs_repo is not None:
280 if self.rhodecode_vcs_repo is not None:
283 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
281 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
284 c.auth_user.username)
282 c.auth_user.username)
285 self.path_filter = PathFilter(path_perms)
283 self.path_filter = PathFilter(path_perms)
286 except RepositoryRequirementError as e:
284 except RepositoryRequirementError as e:
287 c.repository_requirements_missing = {'error': str(e)}
285 c.repository_requirements_missing = {'error': str(e)}
288 self._handle_missing_requirements(e)
286 self._handle_missing_requirements(e)
289 self.rhodecode_vcs_repo = None
287 self.rhodecode_vcs_repo = None
290
288
291 c.path_filter = self.path_filter # used by atom_feed_entry.mako
289 c.path_filter = self.path_filter # used by atom_feed_entry.mako
292
290
293 if self.rhodecode_vcs_repo is None:
291 if self.rhodecode_vcs_repo is None:
294 # unable to fetch this repo as vcs instance, report back to user
292 # unable to fetch this repo as vcs instance, report back to user
295 log.debug('Repository was not found on filesystem, check if it exists or is not damaged')
293 log.debug('Repository was not found on filesystem, check if it exists or is not damaged')
296 h.flash(_(
294 h.flash(_(
297 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
295 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
298 "Please check if it exist, or is not damaged.") %
296 "Please check if it exist, or is not damaged.") %
299 {'repo_name': c.repo_name},
297 {'repo_name': c.repo_name},
300 category='error', ignore_duplicate=True)
298 category='error', ignore_duplicate=True)
301 if c.repository_requirements_missing:
299 if c.repository_requirements_missing:
302 route = self.request.matched_route.name
300 route = self.request.matched_route.name
303 if route.startswith(('edit_repo', 'repo_summary')):
301 if route.startswith(('edit_repo', 'repo_summary')):
304 # allow summary and edit repo on missing requirements
302 # allow summary and edit repo on missing requirements
305 return c
303 return c
306
304
307 raise HTTPFound(
305 raise HTTPFound(
308 h.route_path('repo_summary', repo_name=self.db_repo_name))
306 h.route_path('repo_summary', repo_name=self.db_repo_name))
309
307
310 else: # redirect if we don't show missing requirements
308 else: # redirect if we don't show missing requirements
311 raise HTTPFound(h.route_path('home'))
309 raise HTTPFound(h.route_path('home'))
312
310
313 c.has_origin_repo_read_perm = False
311 c.has_origin_repo_read_perm = False
314 if self.db_repo.fork:
312 if self.db_repo.fork:
315 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
313 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
316 'repository.write', 'repository.read', 'repository.admin')(
314 'repository.write', 'repository.read', 'repository.admin')(
317 self.db_repo.fork.repo_name, 'summary fork link')
315 self.db_repo.fork.repo_name, 'summary fork link')
318
316
319 return c
317 return c
320
318
321 def _get_f_path_unchecked(self, matchdict, default=None):
319 def _get_f_path_unchecked(self, matchdict, default=None):
322 """
320 """
323 Should only be used by redirects, everything else should call _get_f_path
321 Should only be used by redirects, everything else should call _get_f_path
324 """
322 """
325 f_path = matchdict.get('f_path')
323 f_path = matchdict.get('f_path')
326 if f_path:
324 if f_path:
327 # fix for multiple initial slashes that causes errors for GIT
325 # fix for multiple initial slashes that causes errors for GIT
328 return f_path.lstrip('/')
326 return f_path.lstrip('/')
329
327
330 return default
328 return default
331
329
332 def _get_f_path(self, matchdict, default=None):
330 def _get_f_path(self, matchdict, default=None):
333 f_path_match = self._get_f_path_unchecked(matchdict, default)
331 f_path_match = self._get_f_path_unchecked(matchdict, default)
334 return self.path_filter.assert_path_permissions(f_path_match)
332 return self.path_filter.assert_path_permissions(f_path_match)
335
333
336 def _get_general_setting(self, target_repo, settings_key, default=False):
334 def _get_general_setting(self, target_repo, settings_key, default=False):
337 settings_model = VcsSettingsModel(repo=target_repo)
335 settings_model = VcsSettingsModel(repo=target_repo)
338 settings = settings_model.get_general_settings()
336 settings = settings_model.get_general_settings()
339 return settings.get(settings_key, default)
337 return settings.get(settings_key, default)
340
338
341 def _get_repo_setting(self, target_repo, settings_key, default=False):
339 def _get_repo_setting(self, target_repo, settings_key, default=False):
342 settings_model = VcsSettingsModel(repo=target_repo)
340 settings_model = VcsSettingsModel(repo=target_repo)
343 settings = settings_model.get_repo_settings_inherited()
341 settings = settings_model.get_repo_settings_inherited()
344 return settings.get(settings_key, default)
342 return settings.get(settings_key, default)
345
343
346 def _get_readme_data(self, db_repo, renderer_type, commit_id=None, path='/'):
344 def _get_readme_data(self, db_repo, renderer_type, commit_id=None, path='/'):
347 log.debug('Looking for README file at path %s', path)
345 log.debug('Looking for README file at path %s', path)
348 if commit_id:
346 if commit_id:
349 landing_commit_id = commit_id
347 landing_commit_id = commit_id
350 else:
348 else:
351 landing_commit = db_repo.get_landing_commit()
349 landing_commit = db_repo.get_landing_commit()
352 if isinstance(landing_commit, EmptyCommit):
350 if isinstance(landing_commit, EmptyCommit):
353 return None, None
351 return None, None
354 landing_commit_id = landing_commit.raw_id
352 landing_commit_id = landing_commit.raw_id
355
353
356 cache_namespace_uid = 'repo.{}'.format(db_repo.repo_id)
354 cache_namespace_uid = f'repo.{db_repo.repo_id}'
357 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid, use_async_runner=True)
355 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid, use_async_runner=True)
358 start = time.time()
356 start = time.time()
359
357
360 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
358 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
361 def generate_repo_readme(repo_id, _commit_id, _repo_name, _readme_search_path, _renderer_type):
359 def generate_repo_readme(repo_id, _commit_id, _repo_name, _readme_search_path, _renderer_type):
362 readme_data = None
360 readme_data = None
363 readme_filename = None
361 readme_filename = None
364
362
365 commit = db_repo.get_commit(_commit_id)
363 commit = db_repo.get_commit(_commit_id)
366 log.debug("Searching for a README file at commit %s.", _commit_id)
364 log.debug("Searching for a README file at commit %s.", _commit_id)
367 readme_node = ReadmeFinder(_renderer_type).search(commit, path=_readme_search_path)
365 readme_node = ReadmeFinder(_renderer_type).search(commit, path=_readme_search_path)
368
366
369 if readme_node:
367 if readme_node:
370 log.debug('Found README node: %s', readme_node)
368 log.debug('Found README node: %s', readme_node)
371 relative_urls = {
369 relative_urls = {
372 'raw': h.route_path(
370 'raw': h.route_path(
373 'repo_file_raw', repo_name=_repo_name,
371 'repo_file_raw', repo_name=_repo_name,
374 commit_id=commit.raw_id, f_path=readme_node.path),
372 commit_id=commit.raw_id, f_path=readme_node.path),
375 'standard': h.route_path(
373 'standard': h.route_path(
376 'repo_files', repo_name=_repo_name,
374 'repo_files', repo_name=_repo_name,
377 commit_id=commit.raw_id, f_path=readme_node.path),
375 commit_id=commit.raw_id, f_path=readme_node.path),
378 }
376 }
379
377
380 readme_data = self._render_readme_or_none(commit, readme_node, relative_urls)
378 readme_data = self._render_readme_or_none(commit, readme_node, relative_urls)
381 readme_filename = readme_node.str_path
379 readme_filename = readme_node.str_path
382
380
383 return readme_data, readme_filename
381 return readme_data, readme_filename
384
382
385 readme_data, readme_filename = generate_repo_readme(
383 readme_data, readme_filename = generate_repo_readme(
386 db_repo.repo_id, landing_commit_id, db_repo.repo_name, path, renderer_type,)
384 db_repo.repo_id, landing_commit_id, db_repo.repo_name, path, renderer_type,)
387
385
388 compute_time = time.time() - start
386 compute_time = time.time() - start
389 log.debug('Repo README for path %s generated and computed in %.4fs',
387 log.debug('Repo README for path %s generated and computed in %.4fs',
390 path, compute_time)
388 path, compute_time)
391 return readme_data, readme_filename
389 return readme_data, readme_filename
392
390
393 def _render_readme_or_none(self, commit, readme_node, relative_urls):
391 def _render_readme_or_none(self, commit, readme_node, relative_urls):
394 log.debug('Found README file `%s` rendering...', readme_node.path)
392 log.debug('Found README file `%s` rendering...', readme_node.path)
395 renderer = MarkupRenderer()
393 renderer = MarkupRenderer()
396 try:
394 try:
397 html_source = renderer.render(
395 html_source = renderer.render(
398 readme_node.str_content, filename=readme_node.path)
396 readme_node.str_content, filename=readme_node.path)
399 if relative_urls:
397 if relative_urls:
400 return relative_links(html_source, relative_urls)
398 return relative_links(html_source, relative_urls)
401 return html_source
399 return html_source
402 except Exception:
400 except Exception:
403 log.exception("Exception while trying to render the README")
401 log.exception("Exception while trying to render the README")
404
402
405 def get_recache_flag(self):
403 def get_recache_flag(self):
406 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
404 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
407 flag_val = self.request.GET.get(flag_name)
405 flag_val = self.request.GET.get(flag_name)
408 if str2bool(flag_val):
406 if str2bool(flag_val):
409 return True
407 return True
410 return False
408 return False
411
409
412 def get_commit_preload_attrs(cls):
410 def get_commit_preload_attrs(cls):
413 pre_load = ['author', 'branch', 'date', 'message', 'parents',
411 pre_load = ['author', 'branch', 'date', 'message', 'parents',
414 'obsolete', 'phase', 'hidden']
412 'obsolete', 'phase', 'hidden']
415 return pre_load
413 return pre_load
416
414
417
415
418 class PathFilter(object):
416 class PathFilter(object):
419
417
420 # Expects and instance of BasePathPermissionChecker or None
418 # Expects and instance of BasePathPermissionChecker or None
421 def __init__(self, permission_checker):
419 def __init__(self, permission_checker):
422 self.permission_checker = permission_checker
420 self.permission_checker = permission_checker
423
421
424 def assert_path_permissions(self, path):
422 def assert_path_permissions(self, path):
425 if self.path_access_allowed(path):
423 if self.path_access_allowed(path):
426 return path
424 return path
427 raise HTTPForbidden()
425 raise HTTPForbidden()
428
426
429 def path_access_allowed(self, path):
427 def path_access_allowed(self, path):
430 log.debug('Checking ACL permissions for PathFilter for `%s`', path)
428 log.debug('Checking ACL permissions for PathFilter for `%s`', path)
431 if self.permission_checker:
429 if self.permission_checker:
432 has_access = path and self.permission_checker.has_access(path)
430 has_access = path and self.permission_checker.has_access(path)
433 log.debug('ACL Permissions checker enabled, ACL Check has_access: %s', has_access)
431 log.debug('ACL Permissions checker enabled, ACL Check has_access: %s', has_access)
434 return has_access
432 return has_access
435
433
436 log.debug('ACL permissions checker not enabled, skipping...')
434 log.debug('ACL permissions checker not enabled, skipping...')
437 return True
435 return True
438
436
439 def filter_patchset(self, patchset):
437 def filter_patchset(self, patchset):
440 if not self.permission_checker or not patchset:
438 if not self.permission_checker or not patchset:
441 return patchset, False
439 return patchset, False
442 had_filtered = False
440 had_filtered = False
443 filtered_patchset = []
441 filtered_patchset = []
444 for patch in patchset:
442 for patch in patchset:
445 filename = patch.get('filename', None)
443 filename = patch.get('filename', None)
446 if not filename or self.permission_checker.has_access(filename):
444 if not filename or self.permission_checker.has_access(filename):
447 filtered_patchset.append(patch)
445 filtered_patchset.append(patch)
448 else:
446 else:
449 had_filtered = True
447 had_filtered = True
450 if had_filtered:
448 if had_filtered:
451 if isinstance(patchset, diffs.LimitedDiffContainer):
449 if isinstance(patchset, diffs.LimitedDiffContainer):
452 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
450 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
453 return filtered_patchset, True
451 return filtered_patchset, True
454 else:
452 else:
455 return patchset, False
453 return patchset, False
456
454
457 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
455 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
458
456
459 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
457 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
460 result = diffset.render_patchset(
458 result = diffset.render_patchset(
461 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
459 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
462 result.has_hidden_changes = has_hidden_changes
460 result.has_hidden_changes = has_hidden_changes
463 return result
461 return result
464
462
465 def get_raw_patch(self, diff_processor):
463 def get_raw_patch(self, diff_processor):
466 if self.permission_checker is None:
464 if self.permission_checker is None:
467 return diff_processor.as_raw()
465 return diff_processor.as_raw()
468 elif self.permission_checker.has_full_access:
466 elif self.permission_checker.has_full_access:
469 return diff_processor.as_raw()
467 return diff_processor.as_raw()
470 else:
468 else:
471 return '# Repository has user-specific filters, raw patch generation is disabled.'
469 return '# Repository has user-specific filters, raw patch generation is disabled.'
472
470
473 @property
471 @property
474 def is_enabled(self):
472 def is_enabled(self):
475 return self.permission_checker is not None
473 return self.permission_checker is not None
476
474
477
475
478 class RepoGroupAppView(BaseAppView):
476 class RepoGroupAppView(BaseAppView):
479 def __init__(self, context, request):
477 def __init__(self, context, request):
480 super(RepoGroupAppView, self).__init__(context, request)
478 super().__init__(context, request)
481 self.db_repo_group = request.db_repo_group
479 self.db_repo_group = request.db_repo_group
482 self.db_repo_group_name = self.db_repo_group.group_name
480 self.db_repo_group_name = self.db_repo_group.group_name
483
481
484 def _get_local_tmpl_context(self, include_app_defaults=True):
482 def _get_local_tmpl_context(self, include_app_defaults=True):
485 _ = self.request.translate
483 _ = self.request.translate
486 c = super(RepoGroupAppView, self)._get_local_tmpl_context(
484 c = super()._get_local_tmpl_context(
487 include_app_defaults=include_app_defaults)
485 include_app_defaults=include_app_defaults)
488 c.repo_group = self.db_repo_group
486 c.repo_group = self.db_repo_group
489 return c
487 return c
490
488
491 def _revoke_perms_on_yourself(self, form_result):
489 def _revoke_perms_on_yourself(self, form_result):
492 _updates = [u for u in form_result['perm_updates'] if self._rhodecode_user.user_id == int(u[0])]
490 _updates = [u for u in form_result['perm_updates'] if self._rhodecode_user.user_id == int(u[0])]
493 _additions = [u for u in form_result['perm_additions'] if self._rhodecode_user.user_id == int(u[0])]
491 _additions = [u for u in form_result['perm_additions'] if self._rhodecode_user.user_id == int(u[0])]
494 _deletions = [u for u in form_result['perm_deletions'] if self._rhodecode_user.user_id == int(u[0])]
492 _deletions = [u for u in form_result['perm_deletions'] if self._rhodecode_user.user_id == int(u[0])]
495 admin_perm = 'group.admin'
493 admin_perm = 'group.admin'
496 if _updates and _updates[0][1] != admin_perm or \
494 if _updates and _updates[0][1] != admin_perm or \
497 _additions and _additions[0][1] != admin_perm or \
495 _additions and _additions[0][1] != admin_perm or \
498 _deletions and _deletions[0][1] != admin_perm:
496 _deletions and _deletions[0][1] != admin_perm:
499 return True
497 return True
500 return False
498 return False
501
499
502
500
503 class UserGroupAppView(BaseAppView):
501 class UserGroupAppView(BaseAppView):
504 def __init__(self, context, request):
502 def __init__(self, context, request):
505 super(UserGroupAppView, self).__init__(context, request)
503 super().__init__(context, request)
506 self.db_user_group = request.db_user_group
504 self.db_user_group = request.db_user_group
507 self.db_user_group_name = self.db_user_group.users_group_name
505 self.db_user_group_name = self.db_user_group.users_group_name
508
506
509
507
510 class UserAppView(BaseAppView):
508 class UserAppView(BaseAppView):
511 def __init__(self, context, request):
509 def __init__(self, context, request):
512 super(UserAppView, self).__init__(context, request)
510 super().__init__(context, request)
513 self.db_user = request.db_user
511 self.db_user = request.db_user
514 self.db_user_id = self.db_user.user_id
512 self.db_user_id = self.db_user.user_id
515
513
516 _ = self.request.translate
514 _ = self.request.translate
517 if not request.db_user_supports_default:
515 if not request.db_user_supports_default:
518 if self.db_user.username == User.DEFAULT_USER:
516 if self.db_user.username == User.DEFAULT_USER:
519 h.flash(_("Editing user `{}` is disabled.".format(
517 h.flash(_("Editing user `{}` is disabled.".format(
520 User.DEFAULT_USER)), category='warning')
518 User.DEFAULT_USER)), category='warning')
521 raise HTTPFound(h.route_path('users'))
519 raise HTTPFound(h.route_path('users'))
522
520
523
521
524 class DataGridAppView(object):
522 class DataGridAppView(object):
525 """
523 """
526 Common class to have re-usable grid rendering components
524 Common class to have re-usable grid rendering components
527 """
525 """
528
526
529 def _extract_ordering(self, request, column_map=None):
527 def _extract_ordering(self, request, column_map=None):
530 column_map = column_map or {}
528 column_map = column_map or {}
531 column_index = safe_int(request.GET.get('order[0][column]'))
529 column_index = safe_int(request.GET.get('order[0][column]'))
532 order_dir = request.GET.get(
530 order_dir = request.GET.get(
533 'order[0][dir]', 'desc')
531 'order[0][dir]', 'desc')
534 order_by = request.GET.get(
532 order_by = request.GET.get(
535 'columns[%s][data][sort]' % column_index, 'name_raw')
533 'columns[%s][data][sort]' % column_index, 'name_raw')
536
534
537 # translate datatable to DB columns
535 # translate datatable to DB columns
538 order_by = column_map.get(order_by) or order_by
536 order_by = column_map.get(order_by) or order_by
539
537
540 search_q = request.GET.get('search[value]')
538 search_q = request.GET.get('search[value]')
541 return search_q, order_by, order_dir
539 return search_q, order_by, order_dir
542
540
543 def _extract_chunk(self, request):
541 def _extract_chunk(self, request):
544 start = safe_int(request.GET.get('start'), 0)
542 start = safe_int(request.GET.get('start'), 0)
545 length = safe_int(request.GET.get('length'), 25)
543 length = safe_int(request.GET.get('length'), 25)
546 draw = safe_int(request.GET.get('draw'))
544 draw = safe_int(request.GET.get('draw'))
547 return draw, start, length
545 return draw, start, length
548
546
549 def _get_order_col(self, order_by, model):
547 def _get_order_col(self, order_by, model):
550 if isinstance(order_by, str):
548 if isinstance(order_by, str):
551 try:
549 try:
552 return operator.attrgetter(order_by)(model)
550 return operator.attrgetter(order_by)(model)
553 except AttributeError:
551 except AttributeError:
554 return None
552 return None
555 else:
553 else:
556 return order_by
554 return order_by
557
555
558
556
559 class BaseReferencesView(RepoAppView):
557 class BaseReferencesView(RepoAppView):
560 """
558 """
561 Base for reference view for branches, tags and bookmarks.
559 Base for reference view for branches, tags and bookmarks.
562 """
560 """
563 def load_default_context(self):
561 def load_default_context(self):
564 c = self._get_local_tmpl_context()
562 c = self._get_local_tmpl_context()
565 return c
563 return c
566
564
567 def load_refs_context(self, ref_items, partials_template):
565 def load_refs_context(self, ref_items, partials_template):
568 _render = self.request.get_partial_renderer(partials_template)
566 _render = self.request.get_partial_renderer(partials_template)
569 pre_load = ["author", "date", "message", "parents"]
567 pre_load = ["author", "date", "message", "parents"]
570
568
571 is_svn = h.is_svn(self.rhodecode_vcs_repo)
569 is_svn = h.is_svn(self.rhodecode_vcs_repo)
572 is_hg = h.is_hg(self.rhodecode_vcs_repo)
570 is_hg = h.is_hg(self.rhodecode_vcs_repo)
573
571
574 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
572 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
575
573
576 closed_refs = {}
574 closed_refs = {}
577 if is_hg:
575 if is_hg:
578 closed_refs = self.rhodecode_vcs_repo.branches_closed
576 closed_refs = self.rhodecode_vcs_repo.branches_closed
579
577
580 data = []
578 data = []
581 for ref_name, commit_id in ref_items:
579 for ref_name, commit_id in ref_items:
582 commit = self.rhodecode_vcs_repo.get_commit(
580 commit = self.rhodecode_vcs_repo.get_commit(
583 commit_id=commit_id, pre_load=pre_load)
581 commit_id=commit_id, pre_load=pre_load)
584 closed = ref_name in closed_refs
582 closed = ref_name in closed_refs
585
583
586 # TODO: johbo: Unify generation of reference links
584 # TODO: johbo: Unify generation of reference links
587 use_commit_id = '/' in ref_name or is_svn
585 use_commit_id = '/' in ref_name or is_svn
588
586
589 if use_commit_id:
587 if use_commit_id:
590 files_url = h.route_path(
588 files_url = h.route_path(
591 'repo_files',
589 'repo_files',
592 repo_name=self.db_repo_name,
590 repo_name=self.db_repo_name,
593 f_path=ref_name if is_svn else '',
591 f_path=ref_name if is_svn else '',
594 commit_id=commit_id,
592 commit_id=commit_id,
595 _query=dict(at=ref_name)
593 _query=dict(at=ref_name)
596 )
594 )
597
595
598 else:
596 else:
599 files_url = h.route_path(
597 files_url = h.route_path(
600 'repo_files',
598 'repo_files',
601 repo_name=self.db_repo_name,
599 repo_name=self.db_repo_name,
602 f_path=ref_name if is_svn else '',
600 f_path=ref_name if is_svn else '',
603 commit_id=ref_name,
601 commit_id=ref_name,
604 _query=dict(at=ref_name)
602 _query=dict(at=ref_name)
605 )
603 )
606
604
607 data.append({
605 data.append({
608 "name": _render('name', ref_name, files_url, closed),
606 "name": _render('name', ref_name, files_url, closed),
609 "name_raw": ref_name,
607 "name_raw": ref_name,
610 "date": _render('date', commit.date),
608 "date": _render('date', commit.date),
611 "date_raw": datetime_to_time(commit.date),
609 "date_raw": datetime_to_time(commit.date),
612 "author": _render('author', commit.author),
610 "author": _render('author', commit.author),
613 "commit": _render(
611 "commit": _render(
614 'commit', commit.message, commit.raw_id, commit.idx),
612 'commit', commit.message, commit.raw_id, commit.idx),
615 "commit_raw": commit.idx,
613 "commit_raw": commit.idx,
616 "compare": _render(
614 "compare": _render(
617 'compare', format_ref_id(ref_name, commit.raw_id)),
615 'compare', format_ref_id(ref_name, commit.raw_id)),
618 })
616 })
619
617
620 return data
618 return data
621
619
622
620
623 class RepoRoutePredicate(object):
621 class RepoRoutePredicate(object):
624 def __init__(self, val, config):
622 def __init__(self, val, config):
625 self.val = val
623 self.val = val
626
624
627 def text(self):
625 def text(self):
628 return f'repo_route = {self.val}'
626 return f'repo_route = {self.val}'
629
627
630 phash = text
628 phash = text
631
629
632 def __call__(self, info, request):
630 def __call__(self, info, request):
633 if hasattr(request, 'vcs_call'):
631 if hasattr(request, 'vcs_call'):
634 # skip vcs calls
632 # skip vcs calls
635 return
633 return
636
634
637 repo_name = info['match']['repo_name']
635 repo_name = info['match']['repo_name']
638
636
639 repo_name_parts = repo_name.split('/')
637 repo_name_parts = repo_name.split('/')
640 repo_slugs = [x for x in (repo_name_slug(x) for x in repo_name_parts)]
638 repo_slugs = [x for x in (repo_name_slug(x) for x in repo_name_parts)]
641
639
642 if repo_name_parts != repo_slugs:
640 if repo_name_parts != repo_slugs:
643 # short-skip if the repo-name doesn't follow slug rule
641 # short-skip if the repo-name doesn't follow slug rule
644 log.warning('repo_name: %s is different than slug %s', repo_name_parts, repo_slugs)
642 log.warning('repo_name: %s is different than slug %s', repo_name_parts, repo_slugs)
645 return False
643 return False
646
644
647 repo_model = repo.RepoModel()
645 repo_model = repo.RepoModel()
648
646
649 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
647 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
650
648
651 def redirect_if_creating(route_info, db_repo):
649 def redirect_if_creating(route_info, db_repo):
652 skip_views = ['edit_repo_advanced_delete']
650 skip_views = ['edit_repo_advanced_delete']
653 route = route_info['route']
651 route = route_info['route']
654 # we should skip delete view so we can actually "remove" repositories
652 # we should skip delete view so we can actually "remove" repositories
655 # if they get stuck in creating state.
653 # if they get stuck in creating state.
656 if route.name in skip_views:
654 if route.name in skip_views:
657 return
655 return
658
656
659 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
657 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
660 repo_creating_url = request.route_path(
658 repo_creating_url = request.route_path(
661 'repo_creating', repo_name=db_repo.repo_name)
659 'repo_creating', repo_name=db_repo.repo_name)
662 raise HTTPFound(repo_creating_url)
660 raise HTTPFound(repo_creating_url)
663
661
664 if by_name_match:
662 if by_name_match:
665 # register this as request object we can re-use later
663 # register this as request object we can re-use later
666 request.db_repo = by_name_match
664 request.db_repo = by_name_match
667 request.db_repo_name = request.db_repo.repo_name
665 request.db_repo_name = request.db_repo.repo_name
668
666
669 redirect_if_creating(info, by_name_match)
667 redirect_if_creating(info, by_name_match)
670 return True
668 return True
671
669
672 by_id_match = repo_model.get_repo_by_id(repo_name)
670 by_id_match = repo_model.get_repo_by_id(repo_name)
673 if by_id_match:
671 if by_id_match:
674 request.db_repo = by_id_match
672 request.db_repo = by_id_match
675 request.db_repo_name = request.db_repo.repo_name
673 request.db_repo_name = request.db_repo.repo_name
676 redirect_if_creating(info, by_id_match)
674 redirect_if_creating(info, by_id_match)
677 return True
675 return True
678
676
679 return False
677 return False
680
678
681
679
682 class RepoForbidArchivedRoutePredicate(object):
680 class RepoForbidArchivedRoutePredicate(object):
683 def __init__(self, val, config):
681 def __init__(self, val, config):
684 self.val = val
682 self.val = val
685
683
686 def text(self):
684 def text(self):
687 return f'repo_forbid_archived = {self.val}'
685 return f'repo_forbid_archived = {self.val}'
688
686
689 phash = text
687 phash = text
690
688
691 def __call__(self, info, request):
689 def __call__(self, info, request):
692 _ = request.translate
690 _ = request.translate
693 rhodecode_db_repo = request.db_repo
691 rhodecode_db_repo = request.db_repo
694
692
695 log.debug(
693 log.debug(
696 '%s checking if archived flag for repo for %s',
694 '%s checking if archived flag for repo for %s',
697 self.__class__.__name__, rhodecode_db_repo.repo_name)
695 self.__class__.__name__, rhodecode_db_repo.repo_name)
698
696
699 if rhodecode_db_repo.archived:
697 if rhodecode_db_repo.archived:
700 log.warning('Current view is not supported for archived repo:%s',
698 log.warning('Current view is not supported for archived repo:%s',
701 rhodecode_db_repo.repo_name)
699 rhodecode_db_repo.repo_name)
702
700
703 h.flash(
701 h.flash(
704 h.literal(_('Action not supported for archived repository.')),
702 h.literal(_('Action not supported for archived repository.')),
705 category='warning')
703 category='warning')
706 summary_url = request.route_path(
704 summary_url = request.route_path(
707 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
705 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
708 raise HTTPFound(summary_url)
706 raise HTTPFound(summary_url)
709 return True
707 return True
710
708
711
709
712 class RepoTypeRoutePredicate(object):
710 class RepoTypeRoutePredicate(object):
713 def __init__(self, val, config):
711 def __init__(self, val, config):
714 self.val = val or ['hg', 'git', 'svn']
712 self.val = val or ['hg', 'git', 'svn']
715
713
716 def text(self):
714 def text(self):
717 return f'repo_accepted_type = {self.val}'
715 return f'repo_accepted_type = {self.val}'
718
716
719 phash = text
717 phash = text
720
718
721 def __call__(self, info, request):
719 def __call__(self, info, request):
722 if hasattr(request, 'vcs_call'):
720 if hasattr(request, 'vcs_call'):
723 # skip vcs calls
721 # skip vcs calls
724 return
722 return
725
723
726 rhodecode_db_repo = request.db_repo
724 rhodecode_db_repo = request.db_repo
727
725
728 log.debug(
726 log.debug(
729 '%s checking repo type for %s in %s',
727 '%s checking repo type for %s in %s',
730 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
728 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
731
729
732 if rhodecode_db_repo.repo_type in self.val:
730 if rhodecode_db_repo.repo_type in self.val:
733 return True
731 return True
734 else:
732 else:
735 log.warning('Current view is not supported for repo type:%s',
733 log.warning('Current view is not supported for repo type:%s',
736 rhodecode_db_repo.repo_type)
734 rhodecode_db_repo.repo_type)
737 return False
735 return False
738
736
739
737
740 class RepoGroupRoutePredicate(object):
738 class RepoGroupRoutePredicate(object):
741 def __init__(self, val, config):
739 def __init__(self, val, config):
742 self.val = val
740 self.val = val
743
741
744 def text(self):
742 def text(self):
745 return f'repo_group_route = {self.val}'
743 return f'repo_group_route = {self.val}'
746
744
747 phash = text
745 phash = text
748
746
749 def __call__(self, info, request):
747 def __call__(self, info, request):
750 if hasattr(request, 'vcs_call'):
748 if hasattr(request, 'vcs_call'):
751 # skip vcs calls
749 # skip vcs calls
752 return
750 return
753
751
754 repo_group_name = info['match']['repo_group_name']
752 repo_group_name = info['match']['repo_group_name']
755
753
756 repo_group_name_parts = repo_group_name.split('/')
754 repo_group_name_parts = repo_group_name.split('/')
757 repo_group_slugs = [x for x in [repo_name_slug(x) for x in repo_group_name_parts]]
755 repo_group_slugs = [x for x in [repo_name_slug(x) for x in repo_group_name_parts]]
758 if repo_group_name_parts != repo_group_slugs:
756 if repo_group_name_parts != repo_group_slugs:
759 # short-skip if the repo-name doesn't follow slug rule
757 # short-skip if the repo-name doesn't follow slug rule
760 log.warning('repo_group_name: %s is different than slug %s', repo_group_name_parts, repo_group_slugs)
758 log.warning('repo_group_name: %s is different than slug %s', repo_group_name_parts, repo_group_slugs)
761 return False
759 return False
762
760
763 repo_group_model = repo_group.RepoGroupModel()
761 repo_group_model = repo_group.RepoGroupModel()
764 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
762 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
765
763
766 if by_name_match:
764 if by_name_match:
767 # register this as request object we can re-use later
765 # register this as request object we can re-use later
768 request.db_repo_group = by_name_match
766 request.db_repo_group = by_name_match
769 request.db_repo_group_name = request.db_repo_group.group_name
767 request.db_repo_group_name = request.db_repo_group.group_name
770 return True
768 return True
771
769
772 return False
770 return False
773
771
774
772
775 class UserGroupRoutePredicate(object):
773 class UserGroupRoutePredicate(object):
776 def __init__(self, val, config):
774 def __init__(self, val, config):
777 self.val = val
775 self.val = val
778
776
779 def text(self):
777 def text(self):
780 return f'user_group_route = {self.val}'
778 return f'user_group_route = {self.val}'
781
779
782 phash = text
780 phash = text
783
781
784 def __call__(self, info, request):
782 def __call__(self, info, request):
785 if hasattr(request, 'vcs_call'):
783 if hasattr(request, 'vcs_call'):
786 # skip vcs calls
784 # skip vcs calls
787 return
785 return
788
786
789 user_group_id = info['match']['user_group_id']
787 user_group_id = info['match']['user_group_id']
790 user_group_model = user_group.UserGroup()
788 user_group_model = user_group.UserGroup()
791 by_id_match = user_group_model.get(user_group_id, cache=False)
789 by_id_match = user_group_model.get(user_group_id, cache=False)
792
790
793 if by_id_match:
791 if by_id_match:
794 # register this as request object we can re-use later
792 # register this as request object we can re-use later
795 request.db_user_group = by_id_match
793 request.db_user_group = by_id_match
796 return True
794 return True
797
795
798 return False
796 return False
799
797
800
798
801 class UserRoutePredicateBase(object):
799 class UserRoutePredicateBase(object):
802 supports_default = None
800 supports_default = None
803
801
804 def __init__(self, val, config):
802 def __init__(self, val, config):
805 self.val = val
803 self.val = val
806
804
807 def text(self):
805 def text(self):
808 raise NotImplementedError()
806 raise NotImplementedError()
809
807
810 def __call__(self, info, request):
808 def __call__(self, info, request):
811 if hasattr(request, 'vcs_call'):
809 if hasattr(request, 'vcs_call'):
812 # skip vcs calls
810 # skip vcs calls
813 return
811 return
814
812
815 user_id = info['match']['user_id']
813 user_id = info['match']['user_id']
816 user_model = user.User()
814 user_model = user.User()
817 by_id_match = user_model.get(user_id, cache=False)
815 by_id_match = user_model.get(user_id, cache=False)
818
816
819 if by_id_match:
817 if by_id_match:
820 # register this as request object we can re-use later
818 # register this as request object we can re-use later
821 request.db_user = by_id_match
819 request.db_user = by_id_match
822 request.db_user_supports_default = self.supports_default
820 request.db_user_supports_default = self.supports_default
823 return True
821 return True
824
822
825 return False
823 return False
826
824
827
825
828 class UserRoutePredicate(UserRoutePredicateBase):
826 class UserRoutePredicate(UserRoutePredicateBase):
829 supports_default = False
827 supports_default = False
830
828
831 def text(self):
829 def text(self):
832 return f'user_route = {self.val}'
830 return f'user_route = {self.val}'
833
831
834 phash = text
832 phash = text
835
833
836
834
837 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
835 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
838 supports_default = True
836 supports_default = True
839
837
840 def text(self):
838 def text(self):
841 return f'user_with_default_route = {self.val}'
839 return f'user_with_default_route = {self.val}'
842
840
843 phash = text
841 phash = text
844
842
845
843
846 def includeme(config):
844 def includeme(config):
847 config.add_route_predicate(
845 config.add_route_predicate(
848 'repo_route', RepoRoutePredicate)
846 'repo_route', RepoRoutePredicate)
849 config.add_route_predicate(
847 config.add_route_predicate(
850 'repo_accepted_types', RepoTypeRoutePredicate)
848 'repo_accepted_types', RepoTypeRoutePredicate)
851 config.add_route_predicate(
849 config.add_route_predicate(
852 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
850 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
853 config.add_route_predicate(
851 config.add_route_predicate(
854 'repo_group_route', RepoGroupRoutePredicate)
852 'repo_group_route', RepoGroupRoutePredicate)
855 config.add_route_predicate(
853 config.add_route_predicate(
856 'user_group_route', UserGroupRoutePredicate)
854 'user_group_route', UserGroupRoutePredicate)
857 config.add_route_predicate(
855 config.add_route_predicate(
858 'user_route_with_default', UserRouteWithDefaultPredicate)
856 'user_route_with_default', UserRouteWithDefaultPredicate)
859 config.add_route_predicate(
857 config.add_route_predicate(
860 'user_route', UserRoutePredicate)
858 'user_route', UserRoutePredicate)
@@ -1,29 +1,27 b''
1
2
3 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 from zope.interface import Interface
19 from zope.interface import Interface
22
20
23
21
24 class IAdminNavigationRegistry(Interface):
22 class IAdminNavigationRegistry(Interface):
25 """
23 """
26 Interface for the admin navigation registry. Currently this is only
24 Interface for the admin navigation registry. Currently this is only
27 used to register and retrieve it via pyramids registry.
25 used to register and retrieve it via pyramids registry.
28 """
26 """
29 pass
27 pass
@@ -1,55 +1,53 b''
1
2
3 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 import logging
19 import logging
22
20
23 from rhodecode import events
21 from rhodecode import events
24 from rhodecode.lib import rc_cache
22 from rhodecode.lib import rc_cache
25
23
26 log = logging.getLogger(__name__)
24 log = logging.getLogger(__name__)
27
25
28 # names of namespaces used for different permission related cached
26 # names of namespaces used for different permission related cached
29 # during flush operation we need to take care of all those
27 # during flush operation we need to take care of all those
30 cache_namespaces = [
28 cache_namespaces = [
31 'cache_user_auth.{}',
29 'cache_user_auth.{}',
32 'cache_user_repo_acl_ids.{}',
30 'cache_user_repo_acl_ids.{}',
33 'cache_user_user_group_acl_ids.{}',
31 'cache_user_user_group_acl_ids.{}',
34 'cache_user_repo_group_acl_ids.{}'
32 'cache_user_repo_group_acl_ids.{}'
35 ]
33 ]
36
34
37
35
38 def trigger_user_permission_flush(event):
36 def trigger_user_permission_flush(event):
39 """
37 """
40 Subscriber to the `UserPermissionsChange`. This triggers the
38 Subscriber to the `UserPermissionsChange`. This triggers the
41 automatic flush of permission caches, so the users affected receive new permissions
39 automatic flush of permission caches, so the users affected receive new permissions
42 Right Away
40 Right Away
43 """
41 """
44
42
45 affected_user_ids = set(event.user_ids)
43 affected_user_ids = set(event.user_ids)
46 for user_id in affected_user_ids:
44 for user_id in affected_user_ids:
47 for cache_namespace_uid_tmpl in cache_namespaces:
45 for cache_namespace_uid_tmpl in cache_namespaces:
48 cache_namespace_uid = cache_namespace_uid_tmpl.format(user_id)
46 cache_namespace_uid = cache_namespace_uid_tmpl.format(user_id)
49 del_keys = rc_cache.clear_cache_namespace('cache_perms', cache_namespace_uid, method=rc_cache.CLEAR_INVALIDATE)
47 del_keys = rc_cache.clear_cache_namespace('cache_perms', cache_namespace_uid, method=rc_cache.CLEAR_INVALIDATE)
50 log.debug('Invalidated %s cache keys for user_id: %s and namespace %s',
48 log.debug('Invalidated %s cache keys for user_id: %s and namespace %s',
51 del_keys, user_id, cache_namespace_uid)
49 del_keys, user_id, cache_namespace_uid)
52
50
53
51
54 def includeme(config):
52 def includeme(config):
55 config.add_subscriber(trigger_user_permission_flush, events.UserPermissionsChange)
53 config.add_subscriber(trigger_user_permission_flush, events.UserPermissionsChange)
@@ -1,19 +1,17 b''
1
2
3 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -1,40 +1,38 b''
1
2
3 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 import logging
19 import logging
22
20
23 from rhodecode.apps._base import BaseAppView, DataGridAppView
21 from rhodecode.apps._base import BaseAppView, DataGridAppView
24 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
22 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
25
23
26 log = logging.getLogger(__name__)
24 log = logging.getLogger(__name__)
27
25
28
26
29 class AdminArtifactsView(BaseAppView, DataGridAppView):
27 class AdminArtifactsView(BaseAppView, DataGridAppView):
30
28
31 def load_default_context(self):
29 def load_default_context(self):
32 c = self._get_local_tmpl_context()
30 c = self._get_local_tmpl_context()
33 return c
31 return c
34
32
35 @LoginRequired()
33 @LoginRequired()
36 @HasPermissionAllDecorator('hg.admin')
34 @HasPermissionAllDecorator('hg.admin')
37 def artifacts(self):
35 def artifacts(self):
38 c = self.load_default_context()
36 c = self.load_default_context()
39 c.active = 'artifacts'
37 c.active = 'artifacts'
40 return self._get_template_context(c)
38 return self._get_template_context(c)
@@ -1,87 +1,85 b''
1
2
3 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 import logging
19 import logging
22
20
23 from pyramid.httpexceptions import HTTPNotFound
21 from pyramid.httpexceptions import HTTPNotFound
24
22
25 from rhodecode.apps._base import BaseAppView
23 from rhodecode.apps._base import BaseAppView
26 from rhodecode.model.db import joinedload, UserLog
24 from rhodecode.model.db import joinedload, UserLog
27 from rhodecode.lib.user_log_filter import user_log_filter
25 from rhodecode.lib.user_log_filter import user_log_filter
28 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
26 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
29 from rhodecode.lib.utils2 import safe_int
27 from rhodecode.lib.utils2 import safe_int
30 from rhodecode.lib.helpers import SqlPage
28 from rhodecode.lib.helpers import SqlPage
31
29
32 log = logging.getLogger(__name__)
30 log = logging.getLogger(__name__)
33
31
34
32
35 class AdminAuditLogsView(BaseAppView):
33 class AdminAuditLogsView(BaseAppView):
36
34
37 def load_default_context(self):
35 def load_default_context(self):
38 c = self._get_local_tmpl_context()
36 c = self._get_local_tmpl_context()
39 return c
37 return c
40
38
41 @LoginRequired()
39 @LoginRequired()
42 @HasPermissionAllDecorator('hg.admin')
40 @HasPermissionAllDecorator('hg.admin')
43 def admin_audit_logs(self):
41 def admin_audit_logs(self):
44 c = self.load_default_context()
42 c = self.load_default_context()
45
43
46 users_log = UserLog.query()\
44 users_log = UserLog.query()\
47 .options(joinedload(UserLog.user))\
45 .options(joinedload(UserLog.user))\
48 .options(joinedload(UserLog.repository))
46 .options(joinedload(UserLog.repository))
49
47
50 # FILTERING
48 # FILTERING
51 c.search_term = self.request.GET.get('filter')
49 c.search_term = self.request.GET.get('filter')
52 try:
50 try:
53 users_log = user_log_filter(users_log, c.search_term)
51 users_log = user_log_filter(users_log, c.search_term)
54 except Exception:
52 except Exception:
55 # we want this to crash for now
53 # we want this to crash for now
56 raise
54 raise
57
55
58 users_log = users_log.order_by(UserLog.action_date.desc())
56 users_log = users_log.order_by(UserLog.action_date.desc())
59
57
60 p = safe_int(self.request.GET.get('page', 1), 1)
58 p = safe_int(self.request.GET.get('page', 1), 1)
61
59
62 def url_generator(page_num):
60 def url_generator(page_num):
63 query_params = {
61 query_params = {
64 'page': page_num
62 'page': page_num
65 }
63 }
66 if c.search_term:
64 if c.search_term:
67 query_params['filter'] = c.search_term
65 query_params['filter'] = c.search_term
68 return self.request.current_route_path(_query=query_params)
66 return self.request.current_route_path(_query=query_params)
69
67
70 c.audit_logs = SqlPage(users_log, page=p, items_per_page=10,
68 c.audit_logs = SqlPage(users_log, page=p, items_per_page=10,
71 url_maker=url_generator)
69 url_maker=url_generator)
72 return self._get_template_context(c)
70 return self._get_template_context(c)
73
71
74 @LoginRequired()
72 @LoginRequired()
75 @HasPermissionAllDecorator('hg.admin')
73 @HasPermissionAllDecorator('hg.admin')
76 def admin_audit_log_entry(self):
74 def admin_audit_log_entry(self):
77 c = self.load_default_context()
75 c = self.load_default_context()
78 audit_log_id = self.request.matchdict['audit_log_id']
76 audit_log_id = self.request.matchdict['audit_log_id']
79
77
80 c.audit_log_entry = UserLog.query()\
78 c.audit_log_entry = UserLog.query()\
81 .options(joinedload(UserLog.user))\
79 .options(joinedload(UserLog.user))\
82 .options(joinedload(UserLog.repository))\
80 .options(joinedload(UserLog.repository))\
83 .filter(UserLog.user_log_id == audit_log_id).scalar()
81 .filter(UserLog.user_log_id == audit_log_id).scalar()
84 if not c.audit_log_entry:
82 if not c.audit_log_entry:
85 raise HTTPNotFound()
83 raise HTTPNotFound()
86
84
87 return self._get_template_context(c)
85 return self._get_template_context(c)
@@ -1,103 +1,101 b''
1
2
3 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 import logging
19 import logging
22
20
23 import formencode
21 import formencode
24 import formencode.htmlfill
22 import formencode.htmlfill
25
23
26 from pyramid.httpexceptions import HTTPFound
24 from pyramid.httpexceptions import HTTPFound
27 from pyramid.renderers import render
25 from pyramid.renderers import render
28 from pyramid.response import Response
26 from pyramid.response import Response
29
27
30 from rhodecode.apps._base import BaseAppView
28 from rhodecode.apps._base import BaseAppView
31 from rhodecode.lib.auth import (
29 from rhodecode.lib.auth import (
32 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
30 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
33 from rhodecode.lib import helpers as h
31 from rhodecode.lib import helpers as h
34 from rhodecode.model.forms import DefaultsForm
32 from rhodecode.model.forms import DefaultsForm
35 from rhodecode.model.meta import Session
33 from rhodecode.model.meta import Session
36 from rhodecode import BACKENDS
34 from rhodecode import BACKENDS
37 from rhodecode.model.settings import SettingsModel
35 from rhodecode.model.settings import SettingsModel
38
36
39 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
40
38
41
39
42 class AdminDefaultSettingsView(BaseAppView):
40 class AdminDefaultSettingsView(BaseAppView):
43
41
44 def load_default_context(self):
42 def load_default_context(self):
45 c = self._get_local_tmpl_context()
43 c = self._get_local_tmpl_context()
46 return c
44 return c
47
45
48 @LoginRequired()
46 @LoginRequired()
49 @HasPermissionAllDecorator('hg.admin')
47 @HasPermissionAllDecorator('hg.admin')
50 def defaults_repository_show(self):
48 def defaults_repository_show(self):
51 c = self.load_default_context()
49 c = self.load_default_context()
52 c.backends = BACKENDS.keys()
50 c.backends = BACKENDS.keys()
53 c.active = 'repositories'
51 c.active = 'repositories'
54 defaults = SettingsModel().get_default_repo_settings()
52 defaults = SettingsModel().get_default_repo_settings()
55
53
56 data = render(
54 data = render(
57 'rhodecode:templates/admin/defaults/defaults.mako',
55 'rhodecode:templates/admin/defaults/defaults.mako',
58 self._get_template_context(c), self.request)
56 self._get_template_context(c), self.request)
59 html = formencode.htmlfill.render(
57 html = formencode.htmlfill.render(
60 data,
58 data,
61 defaults=defaults,
59 defaults=defaults,
62 encoding="UTF-8",
60 encoding="UTF-8",
63 force_defaults=False
61 force_defaults=False
64 )
62 )
65 return Response(html)
63 return Response(html)
66
64
67 @LoginRequired()
65 @LoginRequired()
68 @HasPermissionAllDecorator('hg.admin')
66 @HasPermissionAllDecorator('hg.admin')
69 @CSRFRequired()
67 @CSRFRequired()
70 def defaults_repository_update(self):
68 def defaults_repository_update(self):
71 _ = self.request.translate
69 _ = self.request.translate
72 c = self.load_default_context()
70 c = self.load_default_context()
73 c.active = 'repositories'
71 c.active = 'repositories'
74 form = DefaultsForm(self.request.translate)()
72 form = DefaultsForm(self.request.translate)()
75
73
76 try:
74 try:
77 form_result = form.to_python(dict(self.request.POST))
75 form_result = form.to_python(dict(self.request.POST))
78 for k, v in form_result.items():
76 for k, v in form_result.items():
79 setting = SettingsModel().create_or_update_setting(k, v)
77 setting = SettingsModel().create_or_update_setting(k, v)
80 Session().add(setting)
78 Session().add(setting)
81 Session().commit()
79 Session().commit()
82 h.flash(_('Default settings updated successfully'),
80 h.flash(_('Default settings updated successfully'),
83 category='success')
81 category='success')
84
82
85 except formencode.Invalid as errors:
83 except formencode.Invalid as errors:
86 data = render(
84 data = render(
87 'rhodecode:templates/admin/defaults/defaults.mako',
85 'rhodecode:templates/admin/defaults/defaults.mako',
88 self._get_template_context(c), self.request)
86 self._get_template_context(c), self.request)
89 html = formencode.htmlfill.render(
87 html = formencode.htmlfill.render(
90 data,
88 data,
91 defaults=errors.value,
89 defaults=errors.value,
92 errors=errors.unpack_errors() or {},
90 errors=errors.unpack_errors() or {},
93 prefix_error=False,
91 prefix_error=False,
94 encoding="UTF-8",
92 encoding="UTF-8",
95 force_defaults=False
93 force_defaults=False
96 )
94 )
97 return Response(html)
95 return Response(html)
98 except Exception:
96 except Exception:
99 log.exception('Exception in update action')
97 log.exception('Exception in update action')
100 h.flash(_('Error occurred during update of default values'),
98 h.flash(_('Error occurred during update of default values'),
101 category='error')
99 category='error')
102
100
103 raise HTTPFound(h.route_path('admin_defaults_repositories'))
101 raise HTTPFound(h.route_path('admin_defaults_repositories'))
@@ -1,161 +1,159 b''
1
2
3 # Copyright (C) 2018-2023 RhodeCode GmbH
1 # Copyright (C) 2018-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 import os
18 import os
21 import logging
19 import logging
22
20
23 from pyramid.httpexceptions import HTTPFound
21 from pyramid.httpexceptions import HTTPFound
24
22
25 from rhodecode.apps._base import BaseAppView
23 from rhodecode.apps._base import BaseAppView
26 from rhodecode.apps._base.navigation import navigation_list
24 from rhodecode.apps._base.navigation import navigation_list
27 from rhodecode.lib import helpers as h
25 from rhodecode.lib import helpers as h
28 from rhodecode.lib.auth import (
26 from rhodecode.lib.auth import (
29 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
27 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
30 from rhodecode.lib.utils2 import time_to_utcdatetime, safe_int
28 from rhodecode.lib.utils2 import time_to_utcdatetime, safe_int
31 from rhodecode.lib import exc_tracking
29 from rhodecode.lib import exc_tracking
32
30
33 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
34
32
35
33
36 class ExceptionsTrackerView(BaseAppView):
34 class ExceptionsTrackerView(BaseAppView):
37 def load_default_context(self):
35 def load_default_context(self):
38 c = self._get_local_tmpl_context()
36 c = self._get_local_tmpl_context()
39 c.navlist = navigation_list(self.request)
37 c.navlist = navigation_list(self.request)
40 return c
38 return c
41
39
42 def count_all_exceptions(self):
40 def count_all_exceptions(self):
43 exc_store_path = exc_tracking.get_exc_store()
41 exc_store_path = exc_tracking.get_exc_store()
44 count = 0
42 count = 0
45 for fname in os.listdir(exc_store_path):
43 for fname in os.listdir(exc_store_path):
46 parts = fname.split('_', 2)
44 parts = fname.split('_', 2)
47 if not len(parts) == 3:
45 if not len(parts) == 3:
48 continue
46 continue
49 count +=1
47 count +=1
50 return count
48 return count
51
49
52 def get_all_exceptions(self, read_metadata=False, limit=None, type_filter=None):
50 def get_all_exceptions(self, read_metadata=False, limit=None, type_filter=None):
53 exc_store_path = exc_tracking.get_exc_store()
51 exc_store_path = exc_tracking.get_exc_store()
54 exception_list = []
52 exception_list = []
55
53
56 def key_sorter(val):
54 def key_sorter(val):
57 try:
55 try:
58 return val.split('_')[-1]
56 return val.split('_')[-1]
59 except Exception:
57 except Exception:
60 return 0
58 return 0
61
59
62 for fname in reversed(sorted(os.listdir(exc_store_path), key=key_sorter)):
60 for fname in reversed(sorted(os.listdir(exc_store_path), key=key_sorter)):
63
61
64 parts = fname.split('_', 2)
62 parts = fname.split('_', 2)
65 if not len(parts) == 3:
63 if not len(parts) == 3:
66 continue
64 continue
67
65
68 exc_id, app_type, exc_timestamp = parts
66 exc_id, app_type, exc_timestamp = parts
69
67
70 exc = {'exc_id': exc_id, 'app_type': app_type, 'exc_type': 'unknown',
68 exc = {'exc_id': exc_id, 'app_type': app_type, 'exc_type': 'unknown',
71 'exc_utc_date': '', 'exc_timestamp': exc_timestamp}
69 'exc_utc_date': '', 'exc_timestamp': exc_timestamp}
72
70
73 if read_metadata:
71 if read_metadata:
74 full_path = os.path.join(exc_store_path, fname)
72 full_path = os.path.join(exc_store_path, fname)
75 if not os.path.isfile(full_path):
73 if not os.path.isfile(full_path):
76 continue
74 continue
77 try:
75 try:
78 # we can read our metadata
76 # we can read our metadata
79 with open(full_path, 'rb') as f:
77 with open(full_path, 'rb') as f:
80 exc_metadata = exc_tracking.exc_unserialize(f.read())
78 exc_metadata = exc_tracking.exc_unserialize(f.read())
81 exc.update(exc_metadata)
79 exc.update(exc_metadata)
82 except Exception:
80 except Exception:
83 log.exception('Failed to read exc data from:{}'.format(full_path))
81 log.exception(f'Failed to read exc data from:{full_path}')
84 pass
82 pass
85 # convert our timestamp to a date obj, for nicer representation
83 # convert our timestamp to a date obj, for nicer representation
86 exc['exc_utc_date'] = time_to_utcdatetime(exc['exc_timestamp'])
84 exc['exc_utc_date'] = time_to_utcdatetime(exc['exc_timestamp'])
87
85
88 type_present = exc.get('exc_type')
86 type_present = exc.get('exc_type')
89 if type_filter:
87 if type_filter:
90 if type_present and type_present == type_filter:
88 if type_present and type_present == type_filter:
91 exception_list.append(exc)
89 exception_list.append(exc)
92 else:
90 else:
93 exception_list.append(exc)
91 exception_list.append(exc)
94
92
95 if limit and len(exception_list) >= limit:
93 if limit and len(exception_list) >= limit:
96 break
94 break
97 return exception_list
95 return exception_list
98
96
99 @LoginRequired()
97 @LoginRequired()
100 @HasPermissionAllDecorator('hg.admin')
98 @HasPermissionAllDecorator('hg.admin')
101 def browse_exceptions(self):
99 def browse_exceptions(self):
102 _ = self.request.translate
100 _ = self.request.translate
103 c = self.load_default_context()
101 c = self.load_default_context()
104 c.active = 'exceptions_browse'
102 c.active = 'exceptions_browse'
105 c.limit = safe_int(self.request.GET.get('limit')) or 50
103 c.limit = safe_int(self.request.GET.get('limit')) or 50
106 c.type_filter = self.request.GET.get('type_filter')
104 c.type_filter = self.request.GET.get('type_filter')
107 c.next_limit = c.limit + 50
105 c.next_limit = c.limit + 50
108 c.exception_list = self.get_all_exceptions(
106 c.exception_list = self.get_all_exceptions(
109 read_metadata=True, limit=c.limit, type_filter=c.type_filter)
107 read_metadata=True, limit=c.limit, type_filter=c.type_filter)
110 c.exception_list_count = self.count_all_exceptions()
108 c.exception_list_count = self.count_all_exceptions()
111 c.exception_store_dir = exc_tracking.get_exc_store()
109 c.exception_store_dir = exc_tracking.get_exc_store()
112 return self._get_template_context(c)
110 return self._get_template_context(c)
113
111
114 @LoginRequired()
112 @LoginRequired()
115 @HasPermissionAllDecorator('hg.admin')
113 @HasPermissionAllDecorator('hg.admin')
116 def exception_show(self):
114 def exception_show(self):
117 _ = self.request.translate
115 _ = self.request.translate
118 c = self.load_default_context()
116 c = self.load_default_context()
119
117
120 c.active = 'exceptions'
118 c.active = 'exceptions'
121 c.exception_id = self.request.matchdict['exception_id']
119 c.exception_id = self.request.matchdict['exception_id']
122 c.traceback = exc_tracking.read_exception(c.exception_id, prefix=None)
120 c.traceback = exc_tracking.read_exception(c.exception_id, prefix=None)
123 return self._get_template_context(c)
121 return self._get_template_context(c)
124
122
125 @LoginRequired()
123 @LoginRequired()
126 @HasPermissionAllDecorator('hg.admin')
124 @HasPermissionAllDecorator('hg.admin')
127 @CSRFRequired()
125 @CSRFRequired()
128 def exception_delete_all(self):
126 def exception_delete_all(self):
129 _ = self.request.translate
127 _ = self.request.translate
130 c = self.load_default_context()
128 c = self.load_default_context()
131 type_filter = self.request.POST.get('type_filter')
129 type_filter = self.request.POST.get('type_filter')
132
130
133 c.active = 'exceptions'
131 c.active = 'exceptions'
134 all_exc = self.get_all_exceptions(read_metadata=bool(type_filter), type_filter=type_filter)
132 all_exc = self.get_all_exceptions(read_metadata=bool(type_filter), type_filter=type_filter)
135 exc_count = 0
133 exc_count = 0
136
134
137 for exc in all_exc:
135 for exc in all_exc:
138 if type_filter:
136 if type_filter:
139 if exc.get('exc_type') == type_filter:
137 if exc.get('exc_type') == type_filter:
140 exc_tracking.delete_exception(exc['exc_id'], prefix=None)
138 exc_tracking.delete_exception(exc['exc_id'], prefix=None)
141 exc_count += 1
139 exc_count += 1
142 else:
140 else:
143 exc_tracking.delete_exception(exc['exc_id'], prefix=None)
141 exc_tracking.delete_exception(exc['exc_id'], prefix=None)
144 exc_count += 1
142 exc_count += 1
145
143
146 h.flash(_('Removed {} Exceptions').format(exc_count), category='success')
144 h.flash(_('Removed {} Exceptions').format(exc_count), category='success')
147 raise HTTPFound(h.route_path('admin_settings_exception_tracker'))
145 raise HTTPFound(h.route_path('admin_settings_exception_tracker'))
148
146
149 @LoginRequired()
147 @LoginRequired()
150 @HasPermissionAllDecorator('hg.admin')
148 @HasPermissionAllDecorator('hg.admin')
151 @CSRFRequired()
149 @CSRFRequired()
152 def exception_delete(self):
150 def exception_delete(self):
153 _ = self.request.translate
151 _ = self.request.translate
154 c = self.load_default_context()
152 c = self.load_default_context()
155
153
156 c.active = 'exceptions'
154 c.active = 'exceptions'
157 c.exception_id = self.request.matchdict['exception_id']
155 c.exception_id = self.request.matchdict['exception_id']
158 exc_tracking.delete_exception(c.exception_id, prefix=None)
156 exc_tracking.delete_exception(c.exception_id, prefix=None)
159
157
160 h.flash(_('Removed Exception {}').format(c.exception_id), category='success')
158 h.flash(_('Removed Exception {}').format(c.exception_id), category='success')
161 raise HTTPFound(h.route_path('admin_settings_exception_tracker'))
159 raise HTTPFound(h.route_path('admin_settings_exception_tracker'))
@@ -1,72 +1,70 b''
1
2
3 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 import logging
19 import logging
22
20
23 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
21 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
24
22
25 from rhodecode.apps._base import BaseAppView
23 from rhodecode.apps._base import BaseAppView
26 from rhodecode.lib import helpers as h
24 from rhodecode.lib import helpers as h
27 from rhodecode.lib.auth import (LoginRequired, NotAnonymous, HasRepoPermissionAny)
25 from rhodecode.lib.auth import (LoginRequired, NotAnonymous, HasRepoPermissionAny)
28 from rhodecode.model.db import PullRequest
26 from rhodecode.model.db import PullRequest
29
27
30
28
31 log = logging.getLogger(__name__)
29 log = logging.getLogger(__name__)
32
30
33
31
34 class AdminMainView(BaseAppView):
32 class AdminMainView(BaseAppView):
35 def load_default_context(self):
33 def load_default_context(self):
36 c = self._get_local_tmpl_context()
34 c = self._get_local_tmpl_context()
37 return c
35 return c
38
36
39 @LoginRequired()
37 @LoginRequired()
40 @NotAnonymous()
38 @NotAnonymous()
41 def admin_main(self):
39 def admin_main(self):
42 c = self.load_default_context()
40 c = self.load_default_context()
43 c.active = 'admin'
41 c.active = 'admin'
44
42
45 if not (c.is_super_admin or c.is_delegated_admin):
43 if not (c.is_super_admin or c.is_delegated_admin):
46 raise HTTPNotFound()
44 raise HTTPNotFound()
47
45
48 return self._get_template_context(c)
46 return self._get_template_context(c)
49
47
50 @LoginRequired()
48 @LoginRequired()
51 def pull_requests(self):
49 def pull_requests(self):
52 """
50 """
53 Global redirect for Pull Requests
51 Global redirect for Pull Requests
54 pull_request_id: id of pull requests in the system
52 pull_request_id: id of pull requests in the system
55 """
53 """
56
54
57 pull_request = PullRequest.get_or_404(
55 pull_request = PullRequest.get_or_404(
58 self.request.matchdict['pull_request_id'])
56 self.request.matchdict['pull_request_id'])
59 pull_request_id = pull_request.pull_request_id
57 pull_request_id = pull_request.pull_request_id
60
58
61 repo_name = pull_request.target_repo.repo_name
59 repo_name = pull_request.target_repo.repo_name
62 # NOTE(marcink):
60 # NOTE(marcink):
63 # check permissions so we don't redirect to repo that we don't have access to
61 # check permissions so we don't redirect to repo that we don't have access to
64 # exposing it's name
62 # exposing it's name
65 target_repo_perm = HasRepoPermissionAny(
63 target_repo_perm = HasRepoPermissionAny(
66 'repository.read', 'repository.write', 'repository.admin')(repo_name)
64 'repository.read', 'repository.write', 'repository.admin')(repo_name)
67 if not target_repo_perm:
65 if not target_repo_perm:
68 raise HTTPNotFound()
66 raise HTTPNotFound()
69
67
70 raise HTTPFound(
68 raise HTTPFound(
71 h.route_path('pullrequest_show', repo_name=repo_name,
69 h.route_path('pullrequest_show', repo_name=repo_name,
72 pull_request_id=pull_request_id))
70 pull_request_id=pull_request_id))
@@ -1,46 +1,44 b''
1
2
3 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 import collections
19 import collections
22 import logging
20 import logging
23
21
24 from rhodecode.apps._base import BaseAppView
22 from rhodecode.apps._base import BaseAppView
25 from rhodecode.apps._base.navigation import navigation_list
23 from rhodecode.apps._base.navigation import navigation_list
26 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
24 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
27 from rhodecode.lib.utils import read_opensource_licenses
25 from rhodecode.lib.utils import read_opensource_licenses
28
26
29 log = logging.getLogger(__name__)
27 log = logging.getLogger(__name__)
30
28
31
29
32 class OpenSourceLicensesAdminSettingsView(BaseAppView):
30 class OpenSourceLicensesAdminSettingsView(BaseAppView):
33
31
34 def load_default_context(self):
32 def load_default_context(self):
35 c = self._get_local_tmpl_context()
33 c = self._get_local_tmpl_context()
36 return c
34 return c
37
35
38 @LoginRequired()
36 @LoginRequired()
39 @HasPermissionAllDecorator('hg.admin')
37 @HasPermissionAllDecorator('hg.admin')
40 def open_source_licenses(self):
38 def open_source_licenses(self):
41 c = self.load_default_context()
39 c = self.load_default_context()
42 c.active = 'open_source'
40 c.active = 'open_source'
43 c.navlist = navigation_list(self.request)
41 c.navlist = navigation_list(self.request)
44 c.opensource_licenses = sorted(
42 c.opensource_licenses = sorted(
45 read_opensource_licenses(), key=lambda d: d["name"])
43 read_opensource_licenses(), key=lambda d: d["name"])
46 return self._get_template_context(c)
44 return self._get_template_context(c)
@@ -1,479 +1,477 b''
1
2
3 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 import re
19 import re
22 import logging
20 import logging
23 import formencode
21 import formencode
24 import formencode.htmlfill
22 import formencode.htmlfill
25 import datetime
23 import datetime
26 from pyramid.interfaces import IRoutesMapper
24 from pyramid.interfaces import IRoutesMapper
27
25
28 from pyramid.httpexceptions import HTTPFound
26 from pyramid.httpexceptions import HTTPFound
29 from pyramid.renderers import render
27 from pyramid.renderers import render
30 from pyramid.response import Response
28 from pyramid.response import Response
31
29
32 from rhodecode.apps._base import BaseAppView, DataGridAppView
30 from rhodecode.apps._base import BaseAppView, DataGridAppView
33 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
31 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
34 from rhodecode import events
32 from rhodecode import events
35
33
36 from rhodecode.lib import helpers as h
34 from rhodecode.lib import helpers as h
37 from rhodecode.lib.auth import (
35 from rhodecode.lib.auth import (
38 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
36 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
39 from rhodecode.lib.utils2 import aslist, safe_str
37 from rhodecode.lib.utils2 import aslist, safe_str
40 from rhodecode.model.db import (
38 from rhodecode.model.db import (
41 or_, coalesce, User, UserIpMap, UserSshKeys)
39 or_, coalesce, User, UserIpMap, UserSshKeys)
42 from rhodecode.model.forms import (
40 from rhodecode.model.forms import (
43 ApplicationPermissionsForm, ObjectPermissionsForm, UserPermissionsForm)
41 ApplicationPermissionsForm, ObjectPermissionsForm, UserPermissionsForm)
44 from rhodecode.model.meta import Session
42 from rhodecode.model.meta import Session
45 from rhodecode.model.permission import PermissionModel
43 from rhodecode.model.permission import PermissionModel
46 from rhodecode.model.settings import SettingsModel
44 from rhodecode.model.settings import SettingsModel
47
45
48
46
49 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
50
48
51
49
52 class AdminPermissionsView(BaseAppView, DataGridAppView):
50 class AdminPermissionsView(BaseAppView, DataGridAppView):
53 def load_default_context(self):
51 def load_default_context(self):
54 c = self._get_local_tmpl_context()
52 c = self._get_local_tmpl_context()
55 PermissionModel().set_global_permission_choices(
53 PermissionModel().set_global_permission_choices(
56 c, gettext_translator=self.request.translate)
54 c, gettext_translator=self.request.translate)
57 return c
55 return c
58
56
59 @LoginRequired()
57 @LoginRequired()
60 @HasPermissionAllDecorator('hg.admin')
58 @HasPermissionAllDecorator('hg.admin')
61 def permissions_application(self):
59 def permissions_application(self):
62 c = self.load_default_context()
60 c = self.load_default_context()
63 c.active = 'application'
61 c.active = 'application'
64
62
65 c.user = User.get_default_user(refresh=True)
63 c.user = User.get_default_user(refresh=True)
66
64
67 app_settings = c.rc_config
65 app_settings = c.rc_config
68
66
69 defaults = {
67 defaults = {
70 'anonymous': c.user.active,
68 'anonymous': c.user.active,
71 'default_register_message': app_settings.get(
69 'default_register_message': app_settings.get(
72 'rhodecode_register_message')
70 'rhodecode_register_message')
73 }
71 }
74 defaults.update(c.user.get_default_perms())
72 defaults.update(c.user.get_default_perms())
75
73
76 data = render('rhodecode:templates/admin/permissions/permissions.mako',
74 data = render('rhodecode:templates/admin/permissions/permissions.mako',
77 self._get_template_context(c), self.request)
75 self._get_template_context(c), self.request)
78 html = formencode.htmlfill.render(
76 html = formencode.htmlfill.render(
79 data,
77 data,
80 defaults=defaults,
78 defaults=defaults,
81 encoding="UTF-8",
79 encoding="UTF-8",
82 force_defaults=False
80 force_defaults=False
83 )
81 )
84 return Response(html)
82 return Response(html)
85
83
86 @LoginRequired()
84 @LoginRequired()
87 @HasPermissionAllDecorator('hg.admin')
85 @HasPermissionAllDecorator('hg.admin')
88 @CSRFRequired()
86 @CSRFRequired()
89 def permissions_application_update(self):
87 def permissions_application_update(self):
90 _ = self.request.translate
88 _ = self.request.translate
91 c = self.load_default_context()
89 c = self.load_default_context()
92 c.active = 'application'
90 c.active = 'application'
93
91
94 _form = ApplicationPermissionsForm(
92 _form = ApplicationPermissionsForm(
95 self.request.translate,
93 self.request.translate,
96 [x[0] for x in c.register_choices],
94 [x[0] for x in c.register_choices],
97 [x[0] for x in c.password_reset_choices],
95 [x[0] for x in c.password_reset_choices],
98 [x[0] for x in c.extern_activate_choices])()
96 [x[0] for x in c.extern_activate_choices])()
99
97
100 try:
98 try:
101 form_result = _form.to_python(dict(self.request.POST))
99 form_result = _form.to_python(dict(self.request.POST))
102 form_result.update({'perm_user_name': User.DEFAULT_USER})
100 form_result.update({'perm_user_name': User.DEFAULT_USER})
103 PermissionModel().update_application_permissions(form_result)
101 PermissionModel().update_application_permissions(form_result)
104
102
105 settings = [
103 settings = [
106 ('register_message', 'default_register_message'),
104 ('register_message', 'default_register_message'),
107 ]
105 ]
108 for setting, form_key in settings:
106 for setting, form_key in settings:
109 sett = SettingsModel().create_or_update_setting(
107 sett = SettingsModel().create_or_update_setting(
110 setting, form_result[form_key])
108 setting, form_result[form_key])
111 Session().add(sett)
109 Session().add(sett)
112
110
113 Session().commit()
111 Session().commit()
114 h.flash(_('Application permissions updated successfully'),
112 h.flash(_('Application permissions updated successfully'),
115 category='success')
113 category='success')
116
114
117 except formencode.Invalid as errors:
115 except formencode.Invalid as errors:
118 defaults = errors.value
116 defaults = errors.value
119
117
120 data = render(
118 data = render(
121 'rhodecode:templates/admin/permissions/permissions.mako',
119 'rhodecode:templates/admin/permissions/permissions.mako',
122 self._get_template_context(c), self.request)
120 self._get_template_context(c), self.request)
123 html = formencode.htmlfill.render(
121 html = formencode.htmlfill.render(
124 data,
122 data,
125 defaults=defaults,
123 defaults=defaults,
126 errors=errors.unpack_errors() or {},
124 errors=errors.unpack_errors() or {},
127 prefix_error=False,
125 prefix_error=False,
128 encoding="UTF-8",
126 encoding="UTF-8",
129 force_defaults=False
127 force_defaults=False
130 )
128 )
131 return Response(html)
129 return Response(html)
132
130
133 except Exception:
131 except Exception:
134 log.exception("Exception during update of permissions")
132 log.exception("Exception during update of permissions")
135 h.flash(_('Error occurred during update of permissions'),
133 h.flash(_('Error occurred during update of permissions'),
136 category='error')
134 category='error')
137
135
138 affected_user_ids = [User.get_default_user_id()]
136 affected_user_ids = [User.get_default_user_id()]
139 PermissionModel().trigger_permission_flush(affected_user_ids)
137 PermissionModel().trigger_permission_flush(affected_user_ids)
140
138
141 raise HTTPFound(h.route_path('admin_permissions_application'))
139 raise HTTPFound(h.route_path('admin_permissions_application'))
142
140
143 @LoginRequired()
141 @LoginRequired()
144 @HasPermissionAllDecorator('hg.admin')
142 @HasPermissionAllDecorator('hg.admin')
145 def permissions_objects(self):
143 def permissions_objects(self):
146 c = self.load_default_context()
144 c = self.load_default_context()
147 c.active = 'objects'
145 c.active = 'objects'
148
146
149 c.user = User.get_default_user(refresh=True)
147 c.user = User.get_default_user(refresh=True)
150 defaults = {}
148 defaults = {}
151 defaults.update(c.user.get_default_perms())
149 defaults.update(c.user.get_default_perms())
152
150
153 data = render(
151 data = render(
154 'rhodecode:templates/admin/permissions/permissions.mako',
152 'rhodecode:templates/admin/permissions/permissions.mako',
155 self._get_template_context(c), self.request)
153 self._get_template_context(c), self.request)
156 html = formencode.htmlfill.render(
154 html = formencode.htmlfill.render(
157 data,
155 data,
158 defaults=defaults,
156 defaults=defaults,
159 encoding="UTF-8",
157 encoding="UTF-8",
160 force_defaults=False
158 force_defaults=False
161 )
159 )
162 return Response(html)
160 return Response(html)
163
161
164 @LoginRequired()
162 @LoginRequired()
165 @HasPermissionAllDecorator('hg.admin')
163 @HasPermissionAllDecorator('hg.admin')
166 @CSRFRequired()
164 @CSRFRequired()
167 def permissions_objects_update(self):
165 def permissions_objects_update(self):
168 _ = self.request.translate
166 _ = self.request.translate
169 c = self.load_default_context()
167 c = self.load_default_context()
170 c.active = 'objects'
168 c.active = 'objects'
171
169
172 _form = ObjectPermissionsForm(
170 _form = ObjectPermissionsForm(
173 self.request.translate,
171 self.request.translate,
174 [x[0] for x in c.repo_perms_choices],
172 [x[0] for x in c.repo_perms_choices],
175 [x[0] for x in c.group_perms_choices],
173 [x[0] for x in c.group_perms_choices],
176 [x[0] for x in c.user_group_perms_choices],
174 [x[0] for x in c.user_group_perms_choices],
177 )()
175 )()
178
176
179 try:
177 try:
180 form_result = _form.to_python(dict(self.request.POST))
178 form_result = _form.to_python(dict(self.request.POST))
181 form_result.update({'perm_user_name': User.DEFAULT_USER})
179 form_result.update({'perm_user_name': User.DEFAULT_USER})
182 PermissionModel().update_object_permissions(form_result)
180 PermissionModel().update_object_permissions(form_result)
183
181
184 Session().commit()
182 Session().commit()
185 h.flash(_('Object permissions updated successfully'),
183 h.flash(_('Object permissions updated successfully'),
186 category='success')
184 category='success')
187
185
188 except formencode.Invalid as errors:
186 except formencode.Invalid as errors:
189 defaults = errors.value
187 defaults = errors.value
190
188
191 data = render(
189 data = render(
192 'rhodecode:templates/admin/permissions/permissions.mako',
190 'rhodecode:templates/admin/permissions/permissions.mako',
193 self._get_template_context(c), self.request)
191 self._get_template_context(c), self.request)
194 html = formencode.htmlfill.render(
192 html = formencode.htmlfill.render(
195 data,
193 data,
196 defaults=defaults,
194 defaults=defaults,
197 errors=errors.unpack_errors() or {},
195 errors=errors.unpack_errors() or {},
198 prefix_error=False,
196 prefix_error=False,
199 encoding="UTF-8",
197 encoding="UTF-8",
200 force_defaults=False
198 force_defaults=False
201 )
199 )
202 return Response(html)
200 return Response(html)
203 except Exception:
201 except Exception:
204 log.exception("Exception during update of permissions")
202 log.exception("Exception during update of permissions")
205 h.flash(_('Error occurred during update of permissions'),
203 h.flash(_('Error occurred during update of permissions'),
206 category='error')
204 category='error')
207
205
208 affected_user_ids = [User.get_default_user_id()]
206 affected_user_ids = [User.get_default_user_id()]
209 PermissionModel().trigger_permission_flush(affected_user_ids)
207 PermissionModel().trigger_permission_flush(affected_user_ids)
210
208
211 raise HTTPFound(h.route_path('admin_permissions_object'))
209 raise HTTPFound(h.route_path('admin_permissions_object'))
212
210
213 @LoginRequired()
211 @LoginRequired()
214 @HasPermissionAllDecorator('hg.admin')
212 @HasPermissionAllDecorator('hg.admin')
215 def permissions_branch(self):
213 def permissions_branch(self):
216 c = self.load_default_context()
214 c = self.load_default_context()
217 c.active = 'branch'
215 c.active = 'branch'
218
216
219 c.user = User.get_default_user(refresh=True)
217 c.user = User.get_default_user(refresh=True)
220 defaults = {}
218 defaults = {}
221 defaults.update(c.user.get_default_perms())
219 defaults.update(c.user.get_default_perms())
222
220
223 data = render(
221 data = render(
224 'rhodecode:templates/admin/permissions/permissions.mako',
222 'rhodecode:templates/admin/permissions/permissions.mako',
225 self._get_template_context(c), self.request)
223 self._get_template_context(c), self.request)
226 html = formencode.htmlfill.render(
224 html = formencode.htmlfill.render(
227 data,
225 data,
228 defaults=defaults,
226 defaults=defaults,
229 encoding="UTF-8",
227 encoding="UTF-8",
230 force_defaults=False
228 force_defaults=False
231 )
229 )
232 return Response(html)
230 return Response(html)
233
231
234 @LoginRequired()
232 @LoginRequired()
235 @HasPermissionAllDecorator('hg.admin')
233 @HasPermissionAllDecorator('hg.admin')
236 def permissions_global(self):
234 def permissions_global(self):
237 c = self.load_default_context()
235 c = self.load_default_context()
238 c.active = 'global'
236 c.active = 'global'
239
237
240 c.user = User.get_default_user(refresh=True)
238 c.user = User.get_default_user(refresh=True)
241 defaults = {}
239 defaults = {}
242 defaults.update(c.user.get_default_perms())
240 defaults.update(c.user.get_default_perms())
243
241
244 data = render(
242 data = render(
245 'rhodecode:templates/admin/permissions/permissions.mako',
243 'rhodecode:templates/admin/permissions/permissions.mako',
246 self._get_template_context(c), self.request)
244 self._get_template_context(c), self.request)
247 html = formencode.htmlfill.render(
245 html = formencode.htmlfill.render(
248 data,
246 data,
249 defaults=defaults,
247 defaults=defaults,
250 encoding="UTF-8",
248 encoding="UTF-8",
251 force_defaults=False
249 force_defaults=False
252 )
250 )
253 return Response(html)
251 return Response(html)
254
252
255 @LoginRequired()
253 @LoginRequired()
256 @HasPermissionAllDecorator('hg.admin')
254 @HasPermissionAllDecorator('hg.admin')
257 @CSRFRequired()
255 @CSRFRequired()
258 def permissions_global_update(self):
256 def permissions_global_update(self):
259 _ = self.request.translate
257 _ = self.request.translate
260 c = self.load_default_context()
258 c = self.load_default_context()
261 c.active = 'global'
259 c.active = 'global'
262
260
263 _form = UserPermissionsForm(
261 _form = UserPermissionsForm(
264 self.request.translate,
262 self.request.translate,
265 [x[0] for x in c.repo_create_choices],
263 [x[0] for x in c.repo_create_choices],
266 [x[0] for x in c.repo_create_on_write_choices],
264 [x[0] for x in c.repo_create_on_write_choices],
267 [x[0] for x in c.repo_group_create_choices],
265 [x[0] for x in c.repo_group_create_choices],
268 [x[0] for x in c.user_group_create_choices],
266 [x[0] for x in c.user_group_create_choices],
269 [x[0] for x in c.fork_choices],
267 [x[0] for x in c.fork_choices],
270 [x[0] for x in c.inherit_default_permission_choices])()
268 [x[0] for x in c.inherit_default_permission_choices])()
271
269
272 try:
270 try:
273 form_result = _form.to_python(dict(self.request.POST))
271 form_result = _form.to_python(dict(self.request.POST))
274 form_result.update({'perm_user_name': User.DEFAULT_USER})
272 form_result.update({'perm_user_name': User.DEFAULT_USER})
275 PermissionModel().update_user_permissions(form_result)
273 PermissionModel().update_user_permissions(form_result)
276
274
277 Session().commit()
275 Session().commit()
278 h.flash(_('Global permissions updated successfully'),
276 h.flash(_('Global permissions updated successfully'),
279 category='success')
277 category='success')
280
278
281 except formencode.Invalid as errors:
279 except formencode.Invalid as errors:
282 defaults = errors.value
280 defaults = errors.value
283
281
284 data = render(
282 data = render(
285 'rhodecode:templates/admin/permissions/permissions.mako',
283 'rhodecode:templates/admin/permissions/permissions.mako',
286 self._get_template_context(c), self.request)
284 self._get_template_context(c), self.request)
287 html = formencode.htmlfill.render(
285 html = formencode.htmlfill.render(
288 data,
286 data,
289 defaults=defaults,
287 defaults=defaults,
290 errors=errors.unpack_errors() or {},
288 errors=errors.unpack_errors() or {},
291 prefix_error=False,
289 prefix_error=False,
292 encoding="UTF-8",
290 encoding="UTF-8",
293 force_defaults=False
291 force_defaults=False
294 )
292 )
295 return Response(html)
293 return Response(html)
296 except Exception:
294 except Exception:
297 log.exception("Exception during update of permissions")
295 log.exception("Exception during update of permissions")
298 h.flash(_('Error occurred during update of permissions'),
296 h.flash(_('Error occurred during update of permissions'),
299 category='error')
297 category='error')
300
298
301 affected_user_ids = [User.get_default_user_id()]
299 affected_user_ids = [User.get_default_user_id()]
302 PermissionModel().trigger_permission_flush(affected_user_ids)
300 PermissionModel().trigger_permission_flush(affected_user_ids)
303
301
304 raise HTTPFound(h.route_path('admin_permissions_global'))
302 raise HTTPFound(h.route_path('admin_permissions_global'))
305
303
306 @LoginRequired()
304 @LoginRequired()
307 @HasPermissionAllDecorator('hg.admin')
305 @HasPermissionAllDecorator('hg.admin')
308 def permissions_ips(self):
306 def permissions_ips(self):
309 c = self.load_default_context()
307 c = self.load_default_context()
310 c.active = 'ips'
308 c.active = 'ips'
311
309
312 c.user = User.get_default_user(refresh=True)
310 c.user = User.get_default_user(refresh=True)
313 c.user_ip_map = (
311 c.user_ip_map = (
314 UserIpMap.query().filter(UserIpMap.user == c.user).all())
312 UserIpMap.query().filter(UserIpMap.user == c.user).all())
315
313
316 return self._get_template_context(c)
314 return self._get_template_context(c)
317
315
318 @LoginRequired()
316 @LoginRequired()
319 @HasPermissionAllDecorator('hg.admin')
317 @HasPermissionAllDecorator('hg.admin')
320 def permissions_overview(self):
318 def permissions_overview(self):
321 c = self.load_default_context()
319 c = self.load_default_context()
322 c.active = 'perms'
320 c.active = 'perms'
323
321
324 c.user = User.get_default_user(refresh=True)
322 c.user = User.get_default_user(refresh=True)
325 c.perm_user = c.user.AuthUser()
323 c.perm_user = c.user.AuthUser()
326 return self._get_template_context(c)
324 return self._get_template_context(c)
327
325
328 @LoginRequired()
326 @LoginRequired()
329 @HasPermissionAllDecorator('hg.admin')
327 @HasPermissionAllDecorator('hg.admin')
330 def auth_token_access(self):
328 def auth_token_access(self):
331 from rhodecode import CONFIG
329 from rhodecode import CONFIG
332
330
333 c = self.load_default_context()
331 c = self.load_default_context()
334 c.active = 'auth_token_access'
332 c.active = 'auth_token_access'
335
333
336 c.user = User.get_default_user(refresh=True)
334 c.user = User.get_default_user(refresh=True)
337 c.perm_user = c.user.AuthUser()
335 c.perm_user = c.user.AuthUser()
338
336
339 mapper = self.request.registry.queryUtility(IRoutesMapper)
337 mapper = self.request.registry.queryUtility(IRoutesMapper)
340 c.view_data = []
338 c.view_data = []
341
339
342 _argument_prog = re.compile(r'\{(.*?)\}|:\((.*)\)')
340 _argument_prog = re.compile(r'\{(.*?)\}|:\((.*)\)')
343 introspector = self.request.registry.introspector
341 introspector = self.request.registry.introspector
344
342
345 view_intr = {}
343 view_intr = {}
346 for view_data in introspector.get_category('views'):
344 for view_data in introspector.get_category('views'):
347 intr = view_data['introspectable']
345 intr = view_data['introspectable']
348
346
349 if 'route_name' in intr and intr['attr']:
347 if 'route_name' in intr and intr['attr']:
350 view_intr[intr['route_name']] = '{}:{}'.format(
348 view_intr[intr['route_name']] = '{}:{}'.format(
351 str(intr['derived_callable'].__name__), intr['attr']
349 str(intr['derived_callable'].__name__), intr['attr']
352 )
350 )
353
351
354 c.whitelist_key = 'api_access_controllers_whitelist'
352 c.whitelist_key = 'api_access_controllers_whitelist'
355 c.whitelist_file = CONFIG.get('__file__')
353 c.whitelist_file = CONFIG.get('__file__')
356 whitelist_views = aslist(
354 whitelist_views = aslist(
357 CONFIG.get(c.whitelist_key), sep=',')
355 CONFIG.get(c.whitelist_key), sep=',')
358
356
359 for route_info in mapper.get_routes():
357 for route_info in mapper.get_routes():
360 if not route_info.name.startswith('__'):
358 if not route_info.name.startswith('__'):
361 routepath = route_info.pattern
359 routepath = route_info.pattern
362
360
363 def replace(matchobj):
361 def replace(matchobj):
364 if matchobj.group(1):
362 if matchobj.group(1):
365 return "{%s}" % matchobj.group(1).split(':')[0]
363 return "{%s}" % matchobj.group(1).split(':')[0]
366 else:
364 else:
367 return "{%s}" % matchobj.group(2)
365 return "{%s}" % matchobj.group(2)
368
366
369 routepath = _argument_prog.sub(replace, routepath)
367 routepath = _argument_prog.sub(replace, routepath)
370
368
371 if not routepath.startswith('/'):
369 if not routepath.startswith('/'):
372 routepath = '/' + routepath
370 routepath = '/' + routepath
373
371
374 view_fqn = view_intr.get(route_info.name, 'NOT AVAILABLE')
372 view_fqn = view_intr.get(route_info.name, 'NOT AVAILABLE')
375 active = view_fqn in whitelist_views
373 active = view_fqn in whitelist_views
376 c.view_data.append((route_info.name, view_fqn, routepath, active))
374 c.view_data.append((route_info.name, view_fqn, routepath, active))
377
375
378 c.whitelist_views = whitelist_views
376 c.whitelist_views = whitelist_views
379 return self._get_template_context(c)
377 return self._get_template_context(c)
380
378
381 def ssh_enabled(self):
379 def ssh_enabled(self):
382 return self.request.registry.settings.get(
380 return self.request.registry.settings.get(
383 'ssh.generate_authorized_keyfile')
381 'ssh.generate_authorized_keyfile')
384
382
385 @LoginRequired()
383 @LoginRequired()
386 @HasPermissionAllDecorator('hg.admin')
384 @HasPermissionAllDecorator('hg.admin')
387 def ssh_keys(self):
385 def ssh_keys(self):
388 c = self.load_default_context()
386 c = self.load_default_context()
389 c.active = 'ssh_keys'
387 c.active = 'ssh_keys'
390 c.ssh_enabled = self.ssh_enabled()
388 c.ssh_enabled = self.ssh_enabled()
391 return self._get_template_context(c)
389 return self._get_template_context(c)
392
390
393 @LoginRequired()
391 @LoginRequired()
394 @HasPermissionAllDecorator('hg.admin')
392 @HasPermissionAllDecorator('hg.admin')
395 def ssh_keys_data(self):
393 def ssh_keys_data(self):
396 _ = self.request.translate
394 _ = self.request.translate
397 self.load_default_context()
395 self.load_default_context()
398 column_map = {
396 column_map = {
399 'fingerprint': 'ssh_key_fingerprint',
397 'fingerprint': 'ssh_key_fingerprint',
400 'username': User.username
398 'username': User.username
401 }
399 }
402 draw, start, limit = self._extract_chunk(self.request)
400 draw, start, limit = self._extract_chunk(self.request)
403 search_q, order_by, order_dir = self._extract_ordering(
401 search_q, order_by, order_dir = self._extract_ordering(
404 self.request, column_map=column_map)
402 self.request, column_map=column_map)
405
403
406 ssh_keys_data_total_count = UserSshKeys.query()\
404 ssh_keys_data_total_count = UserSshKeys.query()\
407 .count()
405 .count()
408
406
409 # json generate
407 # json generate
410 base_q = UserSshKeys.query().join(UserSshKeys.user)
408 base_q = UserSshKeys.query().join(UserSshKeys.user)
411
409
412 if search_q:
410 if search_q:
413 like_expression = u'%{}%'.format(safe_str(search_q))
411 like_expression = f'%{safe_str(search_q)}%'
414 base_q = base_q.filter(or_(
412 base_q = base_q.filter(or_(
415 User.username.ilike(like_expression),
413 User.username.ilike(like_expression),
416 UserSshKeys.ssh_key_fingerprint.ilike(like_expression),
414 UserSshKeys.ssh_key_fingerprint.ilike(like_expression),
417 ))
415 ))
418
416
419 users_data_total_filtered_count = base_q.count()
417 users_data_total_filtered_count = base_q.count()
420
418
421 sort_col = self._get_order_col(order_by, UserSshKeys)
419 sort_col = self._get_order_col(order_by, UserSshKeys)
422 if sort_col:
420 if sort_col:
423 if order_dir == 'asc':
421 if order_dir == 'asc':
424 # handle null values properly to order by NULL last
422 # handle null values properly to order by NULL last
425 if order_by in ['created_on']:
423 if order_by in ['created_on']:
426 sort_col = coalesce(sort_col, datetime.date.max)
424 sort_col = coalesce(sort_col, datetime.date.max)
427 sort_col = sort_col.asc()
425 sort_col = sort_col.asc()
428 else:
426 else:
429 # handle null values properly to order by NULL last
427 # handle null values properly to order by NULL last
430 if order_by in ['created_on']:
428 if order_by in ['created_on']:
431 sort_col = coalesce(sort_col, datetime.date.min)
429 sort_col = coalesce(sort_col, datetime.date.min)
432 sort_col = sort_col.desc()
430 sort_col = sort_col.desc()
433
431
434 base_q = base_q.order_by(sort_col)
432 base_q = base_q.order_by(sort_col)
435 base_q = base_q.offset(start).limit(limit)
433 base_q = base_q.offset(start).limit(limit)
436
434
437 ssh_keys = base_q.all()
435 ssh_keys = base_q.all()
438
436
439 ssh_keys_data = []
437 ssh_keys_data = []
440 for ssh_key in ssh_keys:
438 for ssh_key in ssh_keys:
441 ssh_keys_data.append({
439 ssh_keys_data.append({
442 "username": h.gravatar_with_user(self.request, ssh_key.user.username),
440 "username": h.gravatar_with_user(self.request, ssh_key.user.username),
443 "fingerprint": ssh_key.ssh_key_fingerprint,
441 "fingerprint": ssh_key.ssh_key_fingerprint,
444 "description": ssh_key.description,
442 "description": ssh_key.description,
445 "created_on": h.format_date(ssh_key.created_on),
443 "created_on": h.format_date(ssh_key.created_on),
446 "accessed_on": h.format_date(ssh_key.accessed_on),
444 "accessed_on": h.format_date(ssh_key.accessed_on),
447 "action": h.link_to(
445 "action": h.link_to(
448 _('Edit'), h.route_path('edit_user_ssh_keys',
446 _('Edit'), h.route_path('edit_user_ssh_keys',
449 user_id=ssh_key.user.user_id))
447 user_id=ssh_key.user.user_id))
450 })
448 })
451
449
452 data = ({
450 data = ({
453 'draw': draw,
451 'draw': draw,
454 'data': ssh_keys_data,
452 'data': ssh_keys_data,
455 'recordsTotal': ssh_keys_data_total_count,
453 'recordsTotal': ssh_keys_data_total_count,
456 'recordsFiltered': users_data_total_filtered_count,
454 'recordsFiltered': users_data_total_filtered_count,
457 })
455 })
458
456
459 return data
457 return data
460
458
461 @LoginRequired()
459 @LoginRequired()
462 @HasPermissionAllDecorator('hg.admin')
460 @HasPermissionAllDecorator('hg.admin')
463 @CSRFRequired()
461 @CSRFRequired()
464 def ssh_keys_update(self):
462 def ssh_keys_update(self):
465 _ = self.request.translate
463 _ = self.request.translate
466 self.load_default_context()
464 self.load_default_context()
467
465
468 ssh_enabled = self.ssh_enabled()
466 ssh_enabled = self.ssh_enabled()
469 key_file = self.request.registry.settings.get(
467 key_file = self.request.registry.settings.get(
470 'ssh.authorized_keys_file_path')
468 'ssh.authorized_keys_file_path')
471 if ssh_enabled:
469 if ssh_enabled:
472 events.trigger(SshKeyFileChangeEvent(), self.request.registry)
470 events.trigger(SshKeyFileChangeEvent(), self.request.registry)
473 h.flash(_('Updated SSH keys file: {}').format(key_file),
471 h.flash(_('Updated SSH keys file: {}').format(key_file),
474 category='success')
472 category='success')
475 else:
473 else:
476 h.flash(_('SSH key support is disabled in .ini file'),
474 h.flash(_('SSH key support is disabled in .ini file'),
477 category='warning')
475 category='warning')
478
476
479 raise HTTPFound(h.route_path('admin_permissions_ssh_keys'))
477 raise HTTPFound(h.route_path('admin_permissions_ssh_keys'))
@@ -1,170 +1,168 b''
1
2
3 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 import logging
19 import logging
22
20
23 import psutil
21 import psutil
24 import signal
22 import signal
25
23
26
24
27 from rhodecode.apps._base import BaseAppView
25 from rhodecode.apps._base import BaseAppView
28 from rhodecode.apps._base.navigation import navigation_list
26 from rhodecode.apps._base.navigation import navigation_list
29 from rhodecode.lib import system_info
27 from rhodecode.lib import system_info
30 from rhodecode.lib.auth import (
28 from rhodecode.lib.auth import (
31 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
29 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
32 from rhodecode.lib.utils2 import safe_int, StrictAttributeDict
30 from rhodecode.lib.utils2 import safe_int, StrictAttributeDict
33
31
34 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
35
33
36
34
37 class AdminProcessManagementView(BaseAppView):
35 class AdminProcessManagementView(BaseAppView):
38 def load_default_context(self):
36 def load_default_context(self):
39 c = self._get_local_tmpl_context()
37 c = self._get_local_tmpl_context()
40 return c
38 return c
41
39
42 def _format_proc(self, proc, with_children=False):
40 def _format_proc(self, proc, with_children=False):
43 try:
41 try:
44 mem = proc.memory_info()
42 mem = proc.memory_info()
45 proc_formatted = StrictAttributeDict({
43 proc_formatted = StrictAttributeDict({
46 'pid': proc.pid,
44 'pid': proc.pid,
47 'name': proc.name(),
45 'name': proc.name(),
48 'mem_rss': mem.rss,
46 'mem_rss': mem.rss,
49 'mem_vms': mem.vms,
47 'mem_vms': mem.vms,
50 'cpu_percent': proc.cpu_percent(interval=0.1),
48 'cpu_percent': proc.cpu_percent(interval=0.1),
51 'create_time': proc.create_time(),
49 'create_time': proc.create_time(),
52 'cmd': ' '.join(proc.cmdline()),
50 'cmd': ' '.join(proc.cmdline()),
53 })
51 })
54
52
55 if with_children:
53 if with_children:
56 proc_formatted.update({
54 proc_formatted.update({
57 'children': [self._format_proc(x)
55 'children': [self._format_proc(x)
58 for x in proc.children(recursive=True)]
56 for x in proc.children(recursive=True)]
59 })
57 })
60 except Exception:
58 except Exception:
61 log.exception('Failed to load proc')
59 log.exception('Failed to load proc')
62 proc_formatted = None
60 proc_formatted = None
63 return proc_formatted
61 return proc_formatted
64
62
65 def get_processes(self):
63 def get_processes(self):
66 proc_list = []
64 proc_list = []
67 for p in psutil.process_iter():
65 for p in psutil.process_iter():
68 if 'gunicorn' in p.name():
66 if 'gunicorn' in p.name():
69 proc = self._format_proc(p, with_children=True)
67 proc = self._format_proc(p, with_children=True)
70 if proc:
68 if proc:
71 proc_list.append(proc)
69 proc_list.append(proc)
72
70
73 return proc_list
71 return proc_list
74
72
75 def get_workers(self):
73 def get_workers(self):
76 workers = None
74 workers = None
77 try:
75 try:
78 rc_config = system_info.rhodecode_config().value['config']
76 rc_config = system_info.rhodecode_config().value['config']
79 workers = rc_config['server:main'].get('workers')
77 workers = rc_config['server:main'].get('workers')
80 except Exception:
78 except Exception:
81 pass
79 pass
82
80
83 return workers or '?'
81 return workers or '?'
84
82
85 @LoginRequired()
83 @LoginRequired()
86 @HasPermissionAllDecorator('hg.admin')
84 @HasPermissionAllDecorator('hg.admin')
87 def process_management(self):
85 def process_management(self):
88 _ = self.request.translate
86 _ = self.request.translate
89 c = self.load_default_context()
87 c = self.load_default_context()
90
88
91 c.active = 'process_management'
89 c.active = 'process_management'
92 c.navlist = navigation_list(self.request)
90 c.navlist = navigation_list(self.request)
93 c.gunicorn_processes = self.get_processes()
91 c.gunicorn_processes = self.get_processes()
94 c.gunicorn_workers = self.get_workers()
92 c.gunicorn_workers = self.get_workers()
95 return self._get_template_context(c)
93 return self._get_template_context(c)
96
94
97 @LoginRequired()
95 @LoginRequired()
98 @HasPermissionAllDecorator('hg.admin')
96 @HasPermissionAllDecorator('hg.admin')
99 def process_management_data(self):
97 def process_management_data(self):
100 _ = self.request.translate
98 _ = self.request.translate
101 c = self.load_default_context()
99 c = self.load_default_context()
102 c.gunicorn_processes = self.get_processes()
100 c.gunicorn_processes = self.get_processes()
103 return self._get_template_context(c)
101 return self._get_template_context(c)
104
102
105 @LoginRequired()
103 @LoginRequired()
106 @HasPermissionAllDecorator('hg.admin')
104 @HasPermissionAllDecorator('hg.admin')
107 @CSRFRequired()
105 @CSRFRequired()
108 def process_management_signal(self):
106 def process_management_signal(self):
109 pids = self.request.json.get('pids', [])
107 pids = self.request.json.get('pids', [])
110 result = []
108 result = []
111
109
112 def on_terminate(proc):
110 def on_terminate(proc):
113 msg = "terminated"
111 msg = "terminated"
114 result.append(msg)
112 result.append(msg)
115
113
116 procs = []
114 procs = []
117 for pid in pids:
115 for pid in pids:
118 pid = safe_int(pid)
116 pid = safe_int(pid)
119 if pid:
117 if pid:
120 try:
118 try:
121 proc = psutil.Process(pid)
119 proc = psutil.Process(pid)
122 except psutil.NoSuchProcess:
120 except psutil.NoSuchProcess:
123 continue
121 continue
124
122
125 children = proc.children(recursive=True)
123 children = proc.children(recursive=True)
126 if children:
124 if children:
127 log.warning('Wont kill Master Process')
125 log.warning('Wont kill Master Process')
128 else:
126 else:
129 procs.append(proc)
127 procs.append(proc)
130
128
131 for p in procs:
129 for p in procs:
132 try:
130 try:
133 p.terminate()
131 p.terminate()
134 except psutil.AccessDenied as e:
132 except psutil.AccessDenied as e:
135 log.warning('Access denied: {}'.format(e))
133 log.warning(f'Access denied: {e}')
136
134
137 gone, alive = psutil.wait_procs(procs, timeout=10, callback=on_terminate)
135 gone, alive = psutil.wait_procs(procs, timeout=10, callback=on_terminate)
138 for p in alive:
136 for p in alive:
139 try:
137 try:
140 p.kill()
138 p.kill()
141 except psutil.AccessDenied as e:
139 except psutil.AccessDenied as e:
142 log.warning('Access denied: {}'.format(e))
140 log.warning(f'Access denied: {e}')
143
141
144 return {'result': result}
142 return {'result': result}
145
143
146 @LoginRequired()
144 @LoginRequired()
147 @HasPermissionAllDecorator('hg.admin')
145 @HasPermissionAllDecorator('hg.admin')
148 @CSRFRequired()
146 @CSRFRequired()
149 def process_management_master_signal(self):
147 def process_management_master_signal(self):
150 pid_data = self.request.json.get('pid_data', {})
148 pid_data = self.request.json.get('pid_data', {})
151 pid = safe_int(pid_data['pid'])
149 pid = safe_int(pid_data['pid'])
152 action = pid_data['action']
150 action = pid_data['action']
153 if pid:
151 if pid:
154 try:
152 try:
155 proc = psutil.Process(pid)
153 proc = psutil.Process(pid)
156 except psutil.NoSuchProcess:
154 except psutil.NoSuchProcess:
157 return {'result': 'failure_no_such_process'}
155 return {'result': 'failure_no_such_process'}
158
156
159 children = proc.children(recursive=True)
157 children = proc.children(recursive=True)
160 if children:
158 if children:
161 # master process
159 # master process
162 if action == '+' and len(children) <= 20:
160 if action == '+' and len(children) <= 20:
163 proc.send_signal(signal.SIGTTIN)
161 proc.send_signal(signal.SIGTTIN)
164 elif action == '-' and len(children) >= 2:
162 elif action == '-' and len(children) >= 2:
165 proc.send_signal(signal.SIGTTOU)
163 proc.send_signal(signal.SIGTTOU)
166 else:
164 else:
167 return {'result': 'failure_wrong_action'}
165 return {'result': 'failure_wrong_action'}
168 return {'result': 'success'}
166 return {'result': 'success'}
169
167
170 return {'result': 'failure_not_master'}
168 return {'result': 'failure_not_master'}
@@ -1,358 +1,356 b''
1
2
3 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 import datetime
18 import datetime
21 import logging
19 import logging
22 import time
20 import time
23
21
24 import formencode
22 import formencode
25 import formencode.htmlfill
23 import formencode.htmlfill
26
24
27 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
28
26
29 from pyramid.renderers import render
27 from pyramid.renderers import render
30 from pyramid.response import Response
28 from pyramid.response import Response
31 from sqlalchemy.orm import aliased
29 from sqlalchemy.orm import aliased
32
30
33 from rhodecode import events
31 from rhodecode import events
34 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 from rhodecode.apps._base import BaseAppView, DataGridAppView
35
33
36 from rhodecode.lib.auth import (
34 from rhodecode.lib.auth import (
37 LoginRequired, CSRFRequired, NotAnonymous,
35 LoginRequired, CSRFRequired, NotAnonymous,
38 HasPermissionAny, HasRepoGroupPermissionAny)
36 HasPermissionAny, HasRepoGroupPermissionAny)
39 from rhodecode.lib import helpers as h, audit_logger
37 from rhodecode.lib import helpers as h, audit_logger
40 from rhodecode.lib.str_utils import safe_int, safe_str
38 from rhodecode.lib.str_utils import safe_int, safe_str
41 from rhodecode.model.forms import RepoGroupForm
39 from rhodecode.model.forms import RepoGroupForm
42 from rhodecode.model.permission import PermissionModel
40 from rhodecode.model.permission import PermissionModel
43 from rhodecode.model.repo_group import RepoGroupModel
41 from rhodecode.model.repo_group import RepoGroupModel
44 from rhodecode.model.scm import RepoGroupList
42 from rhodecode.model.scm import RepoGroupList
45 from rhodecode.model.db import (
43 from rhodecode.model.db import (
46 or_, count, func, in_filter_generator, Session, RepoGroup, User, Repository)
44 or_, count, func, in_filter_generator, Session, RepoGroup, User, Repository)
47
45
48 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
49
47
50
48
51 class AdminRepoGroupsView(BaseAppView, DataGridAppView):
49 class AdminRepoGroupsView(BaseAppView, DataGridAppView):
52
50
53 def load_default_context(self):
51 def load_default_context(self):
54 c = self._get_local_tmpl_context()
52 c = self._get_local_tmpl_context()
55
53
56 return c
54 return c
57
55
58 def _load_form_data(self, c):
56 def _load_form_data(self, c):
59 allow_empty_group = False
57 allow_empty_group = False
60
58
61 if self._can_create_repo_group():
59 if self._can_create_repo_group():
62 # we're global admin, we're ok and we can create TOP level groups
60 # we're global admin, we're ok and we can create TOP level groups
63 allow_empty_group = True
61 allow_empty_group = True
64
62
65 # override the choices for this form, we need to filter choices
63 # override the choices for this form, we need to filter choices
66 # and display only those we have ADMIN right
64 # and display only those we have ADMIN right
67 groups_with_admin_rights = RepoGroupList(
65 groups_with_admin_rights = RepoGroupList(
68 RepoGroup.query().all(),
66 RepoGroup.query().all(),
69 perm_set=['group.admin'], extra_kwargs=dict(user=self._rhodecode_user))
67 perm_set=['group.admin'], extra_kwargs=dict(user=self._rhodecode_user))
70 c.repo_groups = RepoGroup.groups_choices(
68 c.repo_groups = RepoGroup.groups_choices(
71 groups=groups_with_admin_rights,
69 groups=groups_with_admin_rights,
72 show_empty_group=allow_empty_group)
70 show_empty_group=allow_empty_group)
73 c.personal_repo_group = self._rhodecode_user.personal_repo_group
71 c.personal_repo_group = self._rhodecode_user.personal_repo_group
74
72
75 def _can_create_repo_group(self, parent_group_id=None):
73 def _can_create_repo_group(self, parent_group_id=None):
76 is_admin = HasPermissionAny('hg.admin')('group create controller')
74 is_admin = HasPermissionAny('hg.admin')('group create controller')
77 create_repo_group = HasPermissionAny(
75 create_repo_group = HasPermissionAny(
78 'hg.repogroup.create.true')('group create controller')
76 'hg.repogroup.create.true')('group create controller')
79 if is_admin or (create_repo_group and not parent_group_id):
77 if is_admin or (create_repo_group and not parent_group_id):
80 # we're global admin, or we have global repo group create
78 # we're global admin, or we have global repo group create
81 # permission
79 # permission
82 # we're ok and we can create TOP level groups
80 # we're ok and we can create TOP level groups
83 return True
81 return True
84 elif parent_group_id:
82 elif parent_group_id:
85 # we check the permission if we can write to parent group
83 # we check the permission if we can write to parent group
86 group = RepoGroup.get(parent_group_id)
84 group = RepoGroup.get(parent_group_id)
87 group_name = group.group_name if group else None
85 group_name = group.group_name if group else None
88 if HasRepoGroupPermissionAny('group.admin')(
86 if HasRepoGroupPermissionAny('group.admin')(
89 group_name, 'check if user is an admin of group'):
87 group_name, 'check if user is an admin of group'):
90 # we're an admin of passed in group, we're ok.
88 # we're an admin of passed in group, we're ok.
91 return True
89 return True
92 else:
90 else:
93 return False
91 return False
94 return False
92 return False
95
93
96 # permission check in data loading of
94 # permission check in data loading of
97 # `repo_group_list_data` via RepoGroupList
95 # `repo_group_list_data` via RepoGroupList
98 @LoginRequired()
96 @LoginRequired()
99 @NotAnonymous()
97 @NotAnonymous()
100 def repo_group_list(self):
98 def repo_group_list(self):
101 c = self.load_default_context()
99 c = self.load_default_context()
102 return self._get_template_context(c)
100 return self._get_template_context(c)
103
101
104 # permission check inside
102 # permission check inside
105 @LoginRequired()
103 @LoginRequired()
106 @NotAnonymous()
104 @NotAnonymous()
107 def repo_group_list_data(self):
105 def repo_group_list_data(self):
108 self.load_default_context()
106 self.load_default_context()
109 column_map = {
107 column_map = {
110 'name': 'group_name_hash',
108 'name': 'group_name_hash',
111 'desc': 'group_description',
109 'desc': 'group_description',
112 'last_change': 'updated_on',
110 'last_change': 'updated_on',
113 'top_level_repos': 'repos_total',
111 'top_level_repos': 'repos_total',
114 'owner': 'user_username',
112 'owner': 'user_username',
115 }
113 }
116 draw, start, limit = self._extract_chunk(self.request)
114 draw, start, limit = self._extract_chunk(self.request)
117 search_q, order_by, order_dir = self._extract_ordering(
115 search_q, order_by, order_dir = self._extract_ordering(
118 self.request, column_map=column_map)
116 self.request, column_map=column_map)
119
117
120 _render = self.request.get_partial_renderer(
118 _render = self.request.get_partial_renderer(
121 'rhodecode:templates/data_table/_dt_elements.mako')
119 'rhodecode:templates/data_table/_dt_elements.mako')
122 c = _render.get_call_context()
120 c = _render.get_call_context()
123
121
124 def quick_menu(repo_group_name):
122 def quick_menu(repo_group_name):
125 return _render('quick_repo_group_menu', repo_group_name)
123 return _render('quick_repo_group_menu', repo_group_name)
126
124
127 def repo_group_lnk(repo_group_name):
125 def repo_group_lnk(repo_group_name):
128 return _render('repo_group_name', repo_group_name)
126 return _render('repo_group_name', repo_group_name)
129
127
130 def last_change(last_change):
128 def last_change(last_change):
131 if isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
129 if isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
132 ts = time.time()
130 ts = time.time()
133 utc_offset = (datetime.datetime.fromtimestamp(ts)
131 utc_offset = (datetime.datetime.fromtimestamp(ts)
134 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
132 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
135 last_change = last_change + datetime.timedelta(seconds=utc_offset)
133 last_change = last_change + datetime.timedelta(seconds=utc_offset)
136 return _render("last_change", last_change)
134 return _render("last_change", last_change)
137
135
138 def desc(desc, personal):
136 def desc(desc, personal):
139 return _render(
137 return _render(
140 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
138 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
141
139
142 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
140 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
143 return _render(
141 return _render(
144 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
142 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
145
143
146 def user_profile(username):
144 def user_profile(username):
147 return _render('user_profile', username)
145 return _render('user_profile', username)
148
146
149 _perms = ['group.admin']
147 _perms = ['group.admin']
150 allowed_ids = [-1] + self._rhodecode_user.repo_group_acl_ids_from_stack(_perms)
148 allowed_ids = [-1] + self._rhodecode_user.repo_group_acl_ids_from_stack(_perms)
151
149
152 repo_groups_data_total_count = RepoGroup.query()\
150 repo_groups_data_total_count = RepoGroup.query()\
153 .filter(or_(
151 .filter(or_(
154 # generate multiple IN to fix limitation problems
152 # generate multiple IN to fix limitation problems
155 *in_filter_generator(RepoGroup.group_id, allowed_ids)
153 *in_filter_generator(RepoGroup.group_id, allowed_ids)
156 )) \
154 )) \
157 .count()
155 .count()
158
156
159 repo_groups_data_total_inactive_count = RepoGroup.query()\
157 repo_groups_data_total_inactive_count = RepoGroup.query()\
160 .filter(RepoGroup.group_id.in_(allowed_ids))\
158 .filter(RepoGroup.group_id.in_(allowed_ids))\
161 .count()
159 .count()
162
160
163 repo_count = count(Repository.repo_id)
161 repo_count = count(Repository.repo_id)
164 OwnerUser = aliased(User)
162 OwnerUser = aliased(User)
165 base_q = Session.query(
163 base_q = Session.query(
166 RepoGroup.group_name,
164 RepoGroup.group_name,
167 RepoGroup.group_name_hash,
165 RepoGroup.group_name_hash,
168 RepoGroup.group_description,
166 RepoGroup.group_description,
169 RepoGroup.group_id,
167 RepoGroup.group_id,
170 RepoGroup.personal,
168 RepoGroup.personal,
171 RepoGroup.updated_on,
169 RepoGroup.updated_on,
172 OwnerUser.username.label('owner_username'),
170 OwnerUser.username.label('owner_username'),
173 repo_count.label('repos_count')
171 repo_count.label('repos_count')
174 ) \
172 ) \
175 .filter(or_(
173 .filter(or_(
176 # generate multiple IN to fix limitation problems
174 # generate multiple IN to fix limitation problems
177 *in_filter_generator(RepoGroup.group_id, allowed_ids)
175 *in_filter_generator(RepoGroup.group_id, allowed_ids)
178 )) \
176 )) \
179 .outerjoin(Repository, RepoGroup.group_id == Repository.group_id) \
177 .outerjoin(Repository, RepoGroup.group_id == Repository.group_id) \
180 .join(OwnerUser, RepoGroup.user_id == OwnerUser.user_id)
178 .join(OwnerUser, RepoGroup.user_id == OwnerUser.user_id)
181
179
182 base_q = base_q.group_by(RepoGroup, OwnerUser)
180 base_q = base_q.group_by(RepoGroup, OwnerUser)
183
181
184 if search_q:
182 if search_q:
185 like_expression = u'%{}%'.format(safe_str(search_q))
183 like_expression = f'%{safe_str(search_q)}%'
186 base_q = base_q.filter(or_(
184 base_q = base_q.filter(or_(
187 RepoGroup.group_name.ilike(like_expression),
185 RepoGroup.group_name.ilike(like_expression),
188 ))
186 ))
189
187
190 repo_groups_data_total_filtered_count = base_q.count()
188 repo_groups_data_total_filtered_count = base_q.count()
191 # the inactive isn't really used, but we still make it same as other data grids
189 # the inactive isn't really used, but we still make it same as other data grids
192 # which use inactive (users,user groups)
190 # which use inactive (users,user groups)
193 repo_groups_data_total_filtered_inactive_count = repo_groups_data_total_filtered_count
191 repo_groups_data_total_filtered_inactive_count = repo_groups_data_total_filtered_count
194
192
195 sort_defined = False
193 sort_defined = False
196 if order_by == 'group_name':
194 if order_by == 'group_name':
197 sort_col = func.lower(RepoGroup.group_name)
195 sort_col = func.lower(RepoGroup.group_name)
198 sort_defined = True
196 sort_defined = True
199 elif order_by == 'repos_total':
197 elif order_by == 'repos_total':
200 sort_col = repo_count
198 sort_col = repo_count
201 sort_defined = True
199 sort_defined = True
202 elif order_by == 'user_username':
200 elif order_by == 'user_username':
203 sort_col = OwnerUser.username
201 sort_col = OwnerUser.username
204 else:
202 else:
205 sort_col = getattr(RepoGroup, order_by, None)
203 sort_col = getattr(RepoGroup, order_by, None)
206
204
207 if sort_defined or sort_col:
205 if sort_defined or sort_col:
208 if order_dir == 'asc':
206 if order_dir == 'asc':
209 sort_col = sort_col.asc()
207 sort_col = sort_col.asc()
210 else:
208 else:
211 sort_col = sort_col.desc()
209 sort_col = sort_col.desc()
212
210
213 base_q = base_q.order_by(sort_col)
211 base_q = base_q.order_by(sort_col)
214 base_q = base_q.offset(start).limit(limit)
212 base_q = base_q.offset(start).limit(limit)
215
213
216 # authenticated access to user groups
214 # authenticated access to user groups
217 auth_repo_group_list = base_q.all()
215 auth_repo_group_list = base_q.all()
218
216
219 repo_groups_data = []
217 repo_groups_data = []
220 for repo_gr in auth_repo_group_list:
218 for repo_gr in auth_repo_group_list:
221 row = {
219 row = {
222 "menu": quick_menu(repo_gr.group_name),
220 "menu": quick_menu(repo_gr.group_name),
223 "name": repo_group_lnk(repo_gr.group_name),
221 "name": repo_group_lnk(repo_gr.group_name),
224
222
225 "last_change": last_change(repo_gr.updated_on),
223 "last_change": last_change(repo_gr.updated_on),
226
224
227 "last_changeset": "",
225 "last_changeset": "",
228 "last_changeset_raw": "",
226 "last_changeset_raw": "",
229
227
230 "desc": desc(repo_gr.group_description, repo_gr.personal),
228 "desc": desc(repo_gr.group_description, repo_gr.personal),
231 "owner": user_profile(repo_gr.owner_username),
229 "owner": user_profile(repo_gr.owner_username),
232 "top_level_repos": repo_gr.repos_count,
230 "top_level_repos": repo_gr.repos_count,
233 "action": repo_group_actions(
231 "action": repo_group_actions(
234 repo_gr.group_id, repo_gr.group_name, repo_gr.repos_count),
232 repo_gr.group_id, repo_gr.group_name, repo_gr.repos_count),
235 }
233 }
236
234
237 repo_groups_data.append(row)
235 repo_groups_data.append(row)
238
236
239 data = ({
237 data = ({
240 'draw': draw,
238 'draw': draw,
241 'data': repo_groups_data,
239 'data': repo_groups_data,
242 'recordsTotal': repo_groups_data_total_count,
240 'recordsTotal': repo_groups_data_total_count,
243 'recordsTotalInactive': repo_groups_data_total_inactive_count,
241 'recordsTotalInactive': repo_groups_data_total_inactive_count,
244 'recordsFiltered': repo_groups_data_total_filtered_count,
242 'recordsFiltered': repo_groups_data_total_filtered_count,
245 'recordsFilteredInactive': repo_groups_data_total_filtered_inactive_count,
243 'recordsFilteredInactive': repo_groups_data_total_filtered_inactive_count,
246 })
244 })
247
245
248 return data
246 return data
249
247
250 @LoginRequired()
248 @LoginRequired()
251 @NotAnonymous()
249 @NotAnonymous()
252 # perm checks inside
250 # perm checks inside
253 def repo_group_new(self):
251 def repo_group_new(self):
254 c = self.load_default_context()
252 c = self.load_default_context()
255
253
256 # perm check for admin, create_group perm or admin of parent_group
254 # perm check for admin, create_group perm or admin of parent_group
257 parent_group_id = safe_int(self.request.GET.get('parent_group'))
255 parent_group_id = safe_int(self.request.GET.get('parent_group'))
258 _gr = RepoGroup.get(parent_group_id)
256 _gr = RepoGroup.get(parent_group_id)
259 if not self._can_create_repo_group(parent_group_id):
257 if not self._can_create_repo_group(parent_group_id):
260 raise HTTPForbidden()
258 raise HTTPForbidden()
261
259
262 self._load_form_data(c)
260 self._load_form_data(c)
263
261
264 defaults = {} # Future proof for default of repo group
262 defaults = {} # Future proof for default of repo group
265
263
266 parent_group_choice = '-1'
264 parent_group_choice = '-1'
267 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
265 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
268 parent_group_choice = self._rhodecode_user.personal_repo_group
266 parent_group_choice = self._rhodecode_user.personal_repo_group
269
267
270 if parent_group_id and _gr:
268 if parent_group_id and _gr:
271 if parent_group_id in [x[0] for x in c.repo_groups]:
269 if parent_group_id in [x[0] for x in c.repo_groups]:
272 parent_group_choice = safe_str(parent_group_id)
270 parent_group_choice = safe_str(parent_group_id)
273
271
274 defaults.update({'group_parent_id': parent_group_choice})
272 defaults.update({'group_parent_id': parent_group_choice})
275
273
276 data = render(
274 data = render(
277 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
275 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
278 self._get_template_context(c), self.request)
276 self._get_template_context(c), self.request)
279
277
280 html = formencode.htmlfill.render(
278 html = formencode.htmlfill.render(
281 data,
279 data,
282 defaults=defaults,
280 defaults=defaults,
283 encoding="UTF-8",
281 encoding="UTF-8",
284 force_defaults=False
282 force_defaults=False
285 )
283 )
286 return Response(html)
284 return Response(html)
287
285
288 @LoginRequired()
286 @LoginRequired()
289 @NotAnonymous()
287 @NotAnonymous()
290 @CSRFRequired()
288 @CSRFRequired()
291 # perm checks inside
289 # perm checks inside
292 def repo_group_create(self):
290 def repo_group_create(self):
293 c = self.load_default_context()
291 c = self.load_default_context()
294 _ = self.request.translate
292 _ = self.request.translate
295
293
296 parent_group_id = safe_int(self.request.POST.get('group_parent_id'))
294 parent_group_id = safe_int(self.request.POST.get('group_parent_id'))
297 can_create = self._can_create_repo_group(parent_group_id)
295 can_create = self._can_create_repo_group(parent_group_id)
298
296
299 self._load_form_data(c)
297 self._load_form_data(c)
300 # permissions for can create group based on parent_id are checked
298 # permissions for can create group based on parent_id are checked
301 # here in the Form
299 # here in the Form
302 available_groups = list(map(lambda k: safe_str(k[0]), c.repo_groups))
300 available_groups = list(map(lambda k: safe_str(k[0]), c.repo_groups))
303 repo_group_form = RepoGroupForm(
301 repo_group_form = RepoGroupForm(
304 self.request.translate, available_groups=available_groups,
302 self.request.translate, available_groups=available_groups,
305 can_create_in_root=can_create)()
303 can_create_in_root=can_create)()
306
304
307 repo_group_name = self.request.POST.get('group_name')
305 repo_group_name = self.request.POST.get('group_name')
308 try:
306 try:
309 owner = self._rhodecode_user
307 owner = self._rhodecode_user
310 form_result = repo_group_form.to_python(dict(self.request.POST))
308 form_result = repo_group_form.to_python(dict(self.request.POST))
311 copy_permissions = form_result.get('group_copy_permissions')
309 copy_permissions = form_result.get('group_copy_permissions')
312 repo_group = RepoGroupModel().create(
310 repo_group = RepoGroupModel().create(
313 group_name=form_result['group_name_full'],
311 group_name=form_result['group_name_full'],
314 group_description=form_result['group_description'],
312 group_description=form_result['group_description'],
315 owner=owner.user_id,
313 owner=owner.user_id,
316 copy_permissions=form_result['group_copy_permissions']
314 copy_permissions=form_result['group_copy_permissions']
317 )
315 )
318 Session().flush()
316 Session().flush()
319
317
320 repo_group_data = repo_group.get_api_data()
318 repo_group_data = repo_group.get_api_data()
321 audit_logger.store_web(
319 audit_logger.store_web(
322 'repo_group.create', action_data={'data': repo_group_data},
320 'repo_group.create', action_data={'data': repo_group_data},
323 user=self._rhodecode_user)
321 user=self._rhodecode_user)
324
322
325 Session().commit()
323 Session().commit()
326
324
327 _new_group_name = form_result['group_name_full']
325 _new_group_name = form_result['group_name_full']
328
326
329 repo_group_url = h.link_to(
327 repo_group_url = h.link_to(
330 _new_group_name,
328 _new_group_name,
331 h.route_path('repo_group_home', repo_group_name=_new_group_name))
329 h.route_path('repo_group_home', repo_group_name=_new_group_name))
332 h.flash(h.literal(_('Created repository group %s')
330 h.flash(h.literal(_('Created repository group %s')
333 % repo_group_url), category='success')
331 % repo_group_url), category='success')
334
332
335 except formencode.Invalid as errors:
333 except formencode.Invalid as errors:
336 data = render(
334 data = render(
337 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
335 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
338 self._get_template_context(c), self.request)
336 self._get_template_context(c), self.request)
339 html = formencode.htmlfill.render(
337 html = formencode.htmlfill.render(
340 data,
338 data,
341 defaults=errors.value,
339 defaults=errors.value,
342 errors=errors.unpack_errors() or {},
340 errors=errors.unpack_errors() or {},
343 prefix_error=False,
341 prefix_error=False,
344 encoding="UTF-8",
342 encoding="UTF-8",
345 force_defaults=False
343 force_defaults=False
346 )
344 )
347 return Response(html)
345 return Response(html)
348 except Exception:
346 except Exception:
349 log.exception("Exception during creation of repository group")
347 log.exception("Exception during creation of repository group")
350 h.flash(_('Error occurred during creation of repository group %s')
348 h.flash(_('Error occurred during creation of repository group %s')
351 % repo_group_name, category='error')
349 % repo_group_name, category='error')
352 raise HTTPFound(h.route_path('home'))
350 raise HTTPFound(h.route_path('home'))
353
351
354 PermissionModel().trigger_permission_flush()
352 PermissionModel().trigger_permission_flush()
355
353
356 raise HTTPFound(
354 raise HTTPFound(
357 h.route_path('repo_group_home',
355 h.route_path('repo_group_home',
358 repo_group_name=form_result['group_name_full']))
356 repo_group_name=form_result['group_name_full']))
@@ -1,256 +1,254 b''
1
2
3 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 import logging
19 import logging
22 import formencode
20 import formencode
23 import formencode.htmlfill
21 import formencode.htmlfill
24
22
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
23 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
26
24
27 from pyramid.renderers import render
25 from pyramid.renderers import render
28 from pyramid.response import Response
26 from pyramid.response import Response
29 from sqlalchemy.orm import aliased
27 from sqlalchemy.orm import aliased
30
28
31 from rhodecode import events
29 from rhodecode import events
32 from rhodecode.apps._base import BaseAppView, DataGridAppView
30 from rhodecode.apps._base import BaseAppView, DataGridAppView
33 from rhodecode.lib.celerylib.utils import get_task_id
31 from rhodecode.lib.celerylib.utils import get_task_id
34
32
35 from rhodecode.lib.auth import (
33 from rhodecode.lib.auth import (
36 LoginRequired, CSRFRequired, NotAnonymous,
34 LoginRequired, CSRFRequired, NotAnonymous,
37 HasPermissionAny, HasRepoGroupPermissionAny)
35 HasPermissionAny, HasRepoGroupPermissionAny)
38 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h
39 from rhodecode.lib.utils import repo_name_slug
37 from rhodecode.lib.utils import repo_name_slug
40 from rhodecode.lib.utils2 import safe_int, safe_str
38 from rhodecode.lib.utils2 import safe_int, safe_str
41 from rhodecode.model.forms import RepoForm
39 from rhodecode.model.forms import RepoForm
42 from rhodecode.model.permission import PermissionModel
40 from rhodecode.model.permission import PermissionModel
43 from rhodecode.model.repo import RepoModel
41 from rhodecode.model.repo import RepoModel
44 from rhodecode.model.scm import RepoList, RepoGroupList, ScmModel
42 from rhodecode.model.scm import RepoList, RepoGroupList, ScmModel
45 from rhodecode.model.settings import SettingsModel
43 from rhodecode.model.settings import SettingsModel
46 from rhodecode.model.db import (
44 from rhodecode.model.db import (
47 in_filter_generator, or_, func, Session, Repository, RepoGroup, User)
45 in_filter_generator, or_, func, Session, Repository, RepoGroup, User)
48
46
49 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
50
48
51
49
52 class AdminReposView(BaseAppView, DataGridAppView):
50 class AdminReposView(BaseAppView, DataGridAppView):
53
51
54 def load_default_context(self):
52 def load_default_context(self):
55 c = self._get_local_tmpl_context()
53 c = self._get_local_tmpl_context()
56 return c
54 return c
57
55
58 def _load_form_data(self, c):
56 def _load_form_data(self, c):
59 acl_groups = RepoGroupList(RepoGroup.query().all(),
57 acl_groups = RepoGroupList(RepoGroup.query().all(),
60 perm_set=['group.write', 'group.admin'])
58 perm_set=['group.write', 'group.admin'])
61 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
59 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
62 c.repo_groups_choices = list(map(lambda k: safe_str(k[0]), c.repo_groups))
60 c.repo_groups_choices = list(map(lambda k: safe_str(k[0]), c.repo_groups))
63 c.personal_repo_group = self._rhodecode_user.personal_repo_group
61 c.personal_repo_group = self._rhodecode_user.personal_repo_group
64
62
65 @LoginRequired()
63 @LoginRequired()
66 @NotAnonymous()
64 @NotAnonymous()
67 # perms check inside
65 # perms check inside
68 def repository_list(self):
66 def repository_list(self):
69 c = self.load_default_context()
67 c = self.load_default_context()
70 return self._get_template_context(c)
68 return self._get_template_context(c)
71
69
72 @LoginRequired()
70 @LoginRequired()
73 @NotAnonymous()
71 @NotAnonymous()
74 # perms check inside
72 # perms check inside
75 def repository_list_data(self):
73 def repository_list_data(self):
76 self.load_default_context()
74 self.load_default_context()
77 column_map = {
75 column_map = {
78 'name': 'repo_name',
76 'name': 'repo_name',
79 'desc': 'description',
77 'desc': 'description',
80 'last_change': 'updated_on',
78 'last_change': 'updated_on',
81 'owner': 'user_username',
79 'owner': 'user_username',
82 }
80 }
83 draw, start, limit = self._extract_chunk(self.request)
81 draw, start, limit = self._extract_chunk(self.request)
84 search_q, order_by, order_dir = self._extract_ordering(
82 search_q, order_by, order_dir = self._extract_ordering(
85 self.request, column_map=column_map)
83 self.request, column_map=column_map)
86
84
87 _perms = ['repository.admin']
85 _perms = ['repository.admin']
88 allowed_ids = [-1] + self._rhodecode_user.repo_acl_ids_from_stack(_perms)
86 allowed_ids = [-1] + self._rhodecode_user.repo_acl_ids_from_stack(_perms)
89
87
90 repos_data_total_count = Repository.query() \
88 repos_data_total_count = Repository.query() \
91 .filter(or_(
89 .filter(or_(
92 # generate multiple IN to fix limitation problems
90 # generate multiple IN to fix limitation problems
93 *in_filter_generator(Repository.repo_id, allowed_ids))
91 *in_filter_generator(Repository.repo_id, allowed_ids))
94 ) \
92 ) \
95 .count()
93 .count()
96
94
97 RepoFork = aliased(Repository)
95 RepoFork = aliased(Repository)
98 OwnerUser = aliased(User)
96 OwnerUser = aliased(User)
99 base_q = Session.query(
97 base_q = Session.query(
100 Repository.repo_id,
98 Repository.repo_id,
101 Repository.repo_name,
99 Repository.repo_name,
102 Repository.description,
100 Repository.description,
103 Repository.repo_type,
101 Repository.repo_type,
104 Repository.repo_state,
102 Repository.repo_state,
105 Repository.private,
103 Repository.private,
106 Repository.archived,
104 Repository.archived,
107 Repository.updated_on,
105 Repository.updated_on,
108 Repository._changeset_cache,
106 Repository._changeset_cache,
109 RepoFork.repo_name.label('fork_repo_name'),
107 RepoFork.repo_name.label('fork_repo_name'),
110 OwnerUser.username.label('owner_username'),
108 OwnerUser.username.label('owner_username'),
111 ) \
109 ) \
112 .filter(or_(
110 .filter(or_(
113 # generate multiple IN to fix limitation problems
111 # generate multiple IN to fix limitation problems
114 *in_filter_generator(Repository.repo_id, allowed_ids))
112 *in_filter_generator(Repository.repo_id, allowed_ids))
115 ) \
113 ) \
116 .outerjoin(RepoFork, Repository.fork_id == RepoFork.repo_id) \
114 .outerjoin(RepoFork, Repository.fork_id == RepoFork.repo_id) \
117 .join(OwnerUser, Repository.user_id == OwnerUser.user_id)
115 .join(OwnerUser, Repository.user_id == OwnerUser.user_id)
118
116
119 if search_q:
117 if search_q:
120 like_expression = u'%{}%'.format(safe_str(search_q))
118 like_expression = f'%{safe_str(search_q)}%'
121 base_q = base_q.filter(or_(
119 base_q = base_q.filter(or_(
122 Repository.repo_name.ilike(like_expression),
120 Repository.repo_name.ilike(like_expression),
123 ))
121 ))
124
122
125 #TODO: check if we need group_by here ?
123 #TODO: check if we need group_by here ?
126 #base_q = base_q.group_by(Repository, User)
124 #base_q = base_q.group_by(Repository, User)
127
125
128 repos_data_total_filtered_count = base_q.count()
126 repos_data_total_filtered_count = base_q.count()
129
127
130 sort_defined = False
128 sort_defined = False
131 if order_by == 'repo_name':
129 if order_by == 'repo_name':
132 sort_col = func.lower(Repository.repo_name)
130 sort_col = func.lower(Repository.repo_name)
133 sort_defined = True
131 sort_defined = True
134 elif order_by == 'user_username':
132 elif order_by == 'user_username':
135 sort_col = OwnerUser.username
133 sort_col = OwnerUser.username
136 else:
134 else:
137 sort_col = getattr(Repository, order_by, None)
135 sort_col = getattr(Repository, order_by, None)
138
136
139 if sort_defined or sort_col:
137 if sort_defined or sort_col:
140 if order_dir == 'asc':
138 if order_dir == 'asc':
141 sort_col = sort_col.asc()
139 sort_col = sort_col.asc()
142 else:
140 else:
143 sort_col = sort_col.desc()
141 sort_col = sort_col.desc()
144
142
145 base_q = base_q.order_by(sort_col)
143 base_q = base_q.order_by(sort_col)
146 base_q = base_q.offset(start).limit(limit)
144 base_q = base_q.offset(start).limit(limit)
147
145
148 repos_list = base_q.all()
146 repos_list = base_q.all()
149
147
150 repos_data = RepoModel().get_repos_as_dict(
148 repos_data = RepoModel().get_repos_as_dict(
151 repo_list=repos_list, admin=True, super_user_actions=True)
149 repo_list=repos_list, admin=True, super_user_actions=True)
152
150
153 data = ({
151 data = ({
154 'draw': draw,
152 'draw': draw,
155 'data': repos_data,
153 'data': repos_data,
156 'recordsTotal': repos_data_total_count,
154 'recordsTotal': repos_data_total_count,
157 'recordsFiltered': repos_data_total_filtered_count,
155 'recordsFiltered': repos_data_total_filtered_count,
158 })
156 })
159 return data
157 return data
160
158
161 @LoginRequired()
159 @LoginRequired()
162 @NotAnonymous()
160 @NotAnonymous()
163 # perms check inside
161 # perms check inside
164 def repository_new(self):
162 def repository_new(self):
165 c = self.load_default_context()
163 c = self.load_default_context()
166
164
167 new_repo = self.request.GET.get('repo', '')
165 new_repo = self.request.GET.get('repo', '')
168 parent_group_id = safe_int(self.request.GET.get('parent_group'))
166 parent_group_id = safe_int(self.request.GET.get('parent_group'))
169 _gr = RepoGroup.get(parent_group_id)
167 _gr = RepoGroup.get(parent_group_id)
170
168
171 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
169 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
172 # you're not super admin nor have global create permissions,
170 # you're not super admin nor have global create permissions,
173 # but maybe you have at least write permission to a parent group ?
171 # but maybe you have at least write permission to a parent group ?
174
172
175 gr_name = _gr.group_name if _gr else None
173 gr_name = _gr.group_name if _gr else None
176 # create repositories with write permission on group is set to true
174 # create repositories with write permission on group is set to true
177 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
175 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
178 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
176 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
179 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
177 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
180 if not (group_admin or (group_write and create_on_write)):
178 if not (group_admin or (group_write and create_on_write)):
181 raise HTTPForbidden()
179 raise HTTPForbidden()
182
180
183 self._load_form_data(c)
181 self._load_form_data(c)
184 c.new_repo = repo_name_slug(new_repo)
182 c.new_repo = repo_name_slug(new_repo)
185
183
186 # apply the defaults from defaults page
184 # apply the defaults from defaults page
187 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
185 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
188 # set checkbox to autochecked
186 # set checkbox to autochecked
189 defaults['repo_copy_permissions'] = True
187 defaults['repo_copy_permissions'] = True
190
188
191 parent_group_choice = '-1'
189 parent_group_choice = '-1'
192 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
190 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
193 parent_group_choice = self._rhodecode_user.personal_repo_group
191 parent_group_choice = self._rhodecode_user.personal_repo_group
194
192
195 if parent_group_id and _gr:
193 if parent_group_id and _gr:
196 if parent_group_id in [x[0] for x in c.repo_groups]:
194 if parent_group_id in [x[0] for x in c.repo_groups]:
197 parent_group_choice = safe_str(parent_group_id)
195 parent_group_choice = safe_str(parent_group_id)
198
196
199 defaults.update({'repo_group': parent_group_choice})
197 defaults.update({'repo_group': parent_group_choice})
200
198
201 data = render('rhodecode:templates/admin/repos/repo_add.mako',
199 data = render('rhodecode:templates/admin/repos/repo_add.mako',
202 self._get_template_context(c), self.request)
200 self._get_template_context(c), self.request)
203 html = formencode.htmlfill.render(
201 html = formencode.htmlfill.render(
204 data,
202 data,
205 defaults=defaults,
203 defaults=defaults,
206 encoding="UTF-8",
204 encoding="UTF-8",
207 force_defaults=False
205 force_defaults=False
208 )
206 )
209 return Response(html)
207 return Response(html)
210
208
211 @LoginRequired()
209 @LoginRequired()
212 @NotAnonymous()
210 @NotAnonymous()
213 @CSRFRequired()
211 @CSRFRequired()
214 # perms check inside
212 # perms check inside
215 def repository_create(self):
213 def repository_create(self):
216 c = self.load_default_context()
214 c = self.load_default_context()
217
215
218 form_result = {}
216 form_result = {}
219 self._load_form_data(c)
217 self._load_form_data(c)
220
218
221 try:
219 try:
222 # CanWriteToGroup validators checks permissions of this POST
220 # CanWriteToGroup validators checks permissions of this POST
223 form = RepoForm(
221 form = RepoForm(
224 self.request.translate, repo_groups=c.repo_groups_choices)()
222 self.request.translate, repo_groups=c.repo_groups_choices)()
225 form_result = form.to_python(dict(self.request.POST))
223 form_result = form.to_python(dict(self.request.POST))
226 copy_permissions = form_result.get('repo_copy_permissions')
224 copy_permissions = form_result.get('repo_copy_permissions')
227 # create is done sometimes async on celery, db transaction
225 # create is done sometimes async on celery, db transaction
228 # management is handled there.
226 # management is handled there.
229 task = RepoModel().create(form_result, self._rhodecode_user.user_id)
227 task = RepoModel().create(form_result, self._rhodecode_user.user_id)
230 task_id = get_task_id(task)
228 task_id = get_task_id(task)
231 except formencode.Invalid as errors:
229 except formencode.Invalid as errors:
232 data = render('rhodecode:templates/admin/repos/repo_add.mako',
230 data = render('rhodecode:templates/admin/repos/repo_add.mako',
233 self._get_template_context(c), self.request)
231 self._get_template_context(c), self.request)
234 html = formencode.htmlfill.render(
232 html = formencode.htmlfill.render(
235 data,
233 data,
236 defaults=errors.value,
234 defaults=errors.value,
237 errors=errors.unpack_errors() or {},
235 errors=errors.unpack_errors() or {},
238 prefix_error=False,
236 prefix_error=False,
239 encoding="UTF-8",
237 encoding="UTF-8",
240 force_defaults=False
238 force_defaults=False
241 )
239 )
242 return Response(html)
240 return Response(html)
243
241
244 except Exception as e:
242 except Exception as e:
245 msg = self._log_creation_exception(e, form_result.get('repo_name'))
243 msg = self._log_creation_exception(e, form_result.get('repo_name'))
246 h.flash(msg, category='error')
244 h.flash(msg, category='error')
247 raise HTTPFound(h.route_path('home'))
245 raise HTTPFound(h.route_path('home'))
248
246
249 repo_name = form_result.get('repo_name_full')
247 repo_name = form_result.get('repo_name_full')
250
248
251 affected_user_ids = [self._rhodecode_user.user_id]
249 affected_user_ids = [self._rhodecode_user.user_id]
252 PermissionModel().trigger_permission_flush(affected_user_ids)
250 PermissionModel().trigger_permission_flush(affected_user_ids)
253
251
254 raise HTTPFound(
252 raise HTTPFound(
255 h.route_path('repo_creating', repo_name=repo_name,
253 h.route_path('repo_creating', repo_name=repo_name,
256 _query=dict(task_id=task_id)))
254 _query=dict(task_id=task_id)))
@@ -1,95 +1,93 b''
1
2
3 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 import logging
19 import logging
22
20
23
21
24 from pyramid.httpexceptions import HTTPFound
22 from pyramid.httpexceptions import HTTPFound
25
23
26 from rhodecode.apps._base import BaseAppView
24 from rhodecode.apps._base import BaseAppView
27 from rhodecode.apps._base.navigation import navigation_list
25 from rhodecode.apps._base.navigation import navigation_list
28 from rhodecode.lib.auth import (
26 from rhodecode.lib.auth import (
29 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
27 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
30 from rhodecode.lib.utils2 import safe_int
28 from rhodecode.lib.utils2 import safe_int
31 from rhodecode.lib import system_info
29 from rhodecode.lib import system_info
32 from rhodecode.lib import user_sessions
30 from rhodecode.lib import user_sessions
33 from rhodecode.lib import helpers as h
31 from rhodecode.lib import helpers as h
34
32
35
33
36 log = logging.getLogger(__name__)
34 log = logging.getLogger(__name__)
37
35
38
36
39 class AdminSessionSettingsView(BaseAppView):
37 class AdminSessionSettingsView(BaseAppView):
40
38
41 def load_default_context(self):
39 def load_default_context(self):
42 c = self._get_local_tmpl_context()
40 c = self._get_local_tmpl_context()
43 return c
41 return c
44
42
45 @LoginRequired()
43 @LoginRequired()
46 @HasPermissionAllDecorator('hg.admin')
44 @HasPermissionAllDecorator('hg.admin')
47 def settings_sessions(self):
45 def settings_sessions(self):
48 c = self.load_default_context()
46 c = self.load_default_context()
49
47
50 c.active = 'sessions'
48 c.active = 'sessions'
51 c.navlist = navigation_list(self.request)
49 c.navlist = navigation_list(self.request)
52
50
53 c.cleanup_older_days = 60
51 c.cleanup_older_days = 60
54 older_than_seconds = 60 * 60 * 24 * c.cleanup_older_days
52 older_than_seconds = 60 * 60 * 24 * c.cleanup_older_days
55
53
56 config = system_info.rhodecode_config().get_value()['value']['config']
54 config = system_info.rhodecode_config().get_value()['value']['config']
57 c.session_model = user_sessions.get_session_handler(
55 c.session_model = user_sessions.get_session_handler(
58 config.get('beaker.session.type', 'memory'))(config)
56 config.get('beaker.session.type', 'memory'))(config)
59
57
60 c.session_conf = c.session_model.config
58 c.session_conf = c.session_model.config
61 c.session_count = c.session_model.get_count()
59 c.session_count = c.session_model.get_count()
62 c.session_expired_count = c.session_model.get_expired_count(
60 c.session_expired_count = c.session_model.get_expired_count(
63 older_than_seconds)
61 older_than_seconds)
64
62
65 return self._get_template_context(c)
63 return self._get_template_context(c)
66
64
67 @LoginRequired()
65 @LoginRequired()
68 @HasPermissionAllDecorator('hg.admin')
66 @HasPermissionAllDecorator('hg.admin')
69 @CSRFRequired()
67 @CSRFRequired()
70 def settings_sessions_cleanup(self):
68 def settings_sessions_cleanup(self):
71 _ = self.request.translate
69 _ = self.request.translate
72 expire_days = safe_int(self.request.params.get('expire_days'))
70 expire_days = safe_int(self.request.params.get('expire_days'))
73
71
74 if expire_days is None:
72 if expire_days is None:
75 expire_days = 60
73 expire_days = 60
76
74
77 older_than_seconds = 60 * 60 * 24 * expire_days
75 older_than_seconds = 60 * 60 * 24 * expire_days
78
76
79 config = system_info.rhodecode_config().get_value()['value']['config']
77 config = system_info.rhodecode_config().get_value()['value']['config']
80 session_model = user_sessions.get_session_handler(
78 session_model = user_sessions.get_session_handler(
81 config.get('beaker.session.type', 'memory'))(config)
79 config.get('beaker.session.type', 'memory'))(config)
82
80
83 try:
81 try:
84 session_model.clean_sessions(
82 session_model.clean_sessions(
85 older_than_seconds=older_than_seconds)
83 older_than_seconds=older_than_seconds)
86 h.flash(_('Cleaned up old sessions'), category='success')
84 h.flash(_('Cleaned up old sessions'), category='success')
87 except user_sessions.CleanupCommand as msg:
85 except user_sessions.CleanupCommand as msg:
88 h.flash(msg.message, category='warning')
86 h.flash(msg.message, category='warning')
89 except Exception as e:
87 except Exception as e:
90 log.exception('Failed session cleanup')
88 log.exception('Failed session cleanup')
91 h.flash(_('Failed to cleanup up old sessions'), category='error')
89 h.flash(_('Failed to cleanup up old sessions'), category='error')
92
90
93 redirect_to = self.request.resource_path(
91 redirect_to = self.request.resource_path(
94 self.context, route_name='admin_settings_sessions')
92 self.context, route_name='admin_settings_sessions')
95 return HTTPFound(redirect_to)
93 return HTTPFound(redirect_to)
@@ -1,722 +1,721 b''
1
2 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
2 #
4 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
7 #
6 #
8 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
10 # GNU General Public License for more details.
12 #
11 #
13 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
14 #
16 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
18
20
19
21 import logging
20 import logging
22 import collections
21 import collections
23
22
24 import datetime
23 import datetime
25 import formencode
24 import formencode
26 import formencode.htmlfill
25 import formencode.htmlfill
27
26
28 import rhodecode
27 import rhodecode
29
28
30 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
29 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
31 from pyramid.renderers import render
30 from pyramid.renderers import render
32 from pyramid.response import Response
31 from pyramid.response import Response
33
32
34 from rhodecode.apps._base import BaseAppView
33 from rhodecode.apps._base import BaseAppView
35 from rhodecode.apps._base.navigation import navigation_list
34 from rhodecode.apps._base.navigation import navigation_list
36 from rhodecode.apps.svn_support.config_keys import generate_config
35 from rhodecode.apps.svn_support.config_keys import generate_config
37 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h
38 from rhodecode.lib.auth import (
37 from rhodecode.lib.auth import (
39 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
38 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
40 from rhodecode.lib.celerylib import tasks, run_task
39 from rhodecode.lib.celerylib import tasks, run_task
41 from rhodecode.lib.str_utils import safe_str
40 from rhodecode.lib.str_utils import safe_str
42 from rhodecode.lib.utils import repo2db_mapper
41 from rhodecode.lib.utils import repo2db_mapper
43 from rhodecode.lib.utils2 import str2bool, AttributeDict
42 from rhodecode.lib.utils2 import str2bool, AttributeDict
44 from rhodecode.lib.index import searcher_from_config
43 from rhodecode.lib.index import searcher_from_config
45
44
46 from rhodecode.model.db import RhodeCodeUi, Repository
45 from rhodecode.model.db import RhodeCodeUi, Repository
47 from rhodecode.model.forms import (ApplicationSettingsForm,
46 from rhodecode.model.forms import (ApplicationSettingsForm,
48 ApplicationUiSettingsForm, ApplicationVisualisationForm,
47 ApplicationUiSettingsForm, ApplicationVisualisationForm,
49 LabsSettingsForm, IssueTrackerPatternsForm)
48 LabsSettingsForm, IssueTrackerPatternsForm)
50 from rhodecode.model.permission import PermissionModel
49 from rhodecode.model.permission import PermissionModel
51 from rhodecode.model.repo_group import RepoGroupModel
50 from rhodecode.model.repo_group import RepoGroupModel
52
51
53 from rhodecode.model.scm import ScmModel
52 from rhodecode.model.scm import ScmModel
54 from rhodecode.model.notification import EmailNotificationModel
53 from rhodecode.model.notification import EmailNotificationModel
55 from rhodecode.model.meta import Session
54 from rhodecode.model.meta import Session
56 from rhodecode.model.settings import (
55 from rhodecode.model.settings import (
57 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
56 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
58 SettingsModel)
57 SettingsModel)
59
58
60
59
61 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
62
61
63
62
64 class AdminSettingsView(BaseAppView):
63 class AdminSettingsView(BaseAppView):
65
64
66 def load_default_context(self):
65 def load_default_context(self):
67 c = self._get_local_tmpl_context()
66 c = self._get_local_tmpl_context()
68 c.labs_active = str2bool(
67 c.labs_active = str2bool(
69 rhodecode.CONFIG.get('labs_settings_active', 'true'))
68 rhodecode.CONFIG.get('labs_settings_active', 'true'))
70 c.navlist = navigation_list(self.request)
69 c.navlist = navigation_list(self.request)
71 return c
70 return c
72
71
73 @classmethod
72 @classmethod
74 def _get_ui_settings(cls):
73 def _get_ui_settings(cls):
75 ret = RhodeCodeUi.query().all()
74 ret = RhodeCodeUi.query().all()
76
75
77 if not ret:
76 if not ret:
78 raise Exception('Could not get application ui settings !')
77 raise Exception('Could not get application ui settings !')
79 settings = {}
78 settings = {}
80 for each in ret:
79 for each in ret:
81 k = each.ui_key
80 k = each.ui_key
82 v = each.ui_value
81 v = each.ui_value
83 if k == '/':
82 if k == '/':
84 k = 'root_path'
83 k = 'root_path'
85
84
86 if k in ['push_ssl', 'publish', 'enabled']:
85 if k in ['push_ssl', 'publish', 'enabled']:
87 v = str2bool(v)
86 v = str2bool(v)
88
87
89 if k.find('.') != -1:
88 if k.find('.') != -1:
90 k = k.replace('.', '_')
89 k = k.replace('.', '_')
91
90
92 if each.ui_section in ['hooks', 'extensions']:
91 if each.ui_section in ['hooks', 'extensions']:
93 v = each.ui_active
92 v = each.ui_active
94
93
95 settings[each.ui_section + '_' + k] = v
94 settings[each.ui_section + '_' + k] = v
96 return settings
95 return settings
97
96
98 @classmethod
97 @classmethod
99 def _form_defaults(cls):
98 def _form_defaults(cls):
100 defaults = SettingsModel().get_all_settings()
99 defaults = SettingsModel().get_all_settings()
101 defaults.update(cls._get_ui_settings())
100 defaults.update(cls._get_ui_settings())
102
101
103 defaults.update({
102 defaults.update({
104 'new_svn_branch': '',
103 'new_svn_branch': '',
105 'new_svn_tag': '',
104 'new_svn_tag': '',
106 })
105 })
107 return defaults
106 return defaults
108
107
109 @LoginRequired()
108 @LoginRequired()
110 @HasPermissionAllDecorator('hg.admin')
109 @HasPermissionAllDecorator('hg.admin')
111 def settings_vcs(self):
110 def settings_vcs(self):
112 c = self.load_default_context()
111 c = self.load_default_context()
113 c.active = 'vcs'
112 c.active = 'vcs'
114 model = VcsSettingsModel()
113 model = VcsSettingsModel()
115 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
114 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
116 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
115 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
117
116
118 settings = self.request.registry.settings
117 settings = self.request.registry.settings
119 c.svn_proxy_generate_config = settings[generate_config]
118 c.svn_proxy_generate_config = settings[generate_config]
120
119
121 defaults = self._form_defaults()
120 defaults = self._form_defaults()
122
121
123 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
122 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
124
123
125 data = render('rhodecode:templates/admin/settings/settings.mako',
124 data = render('rhodecode:templates/admin/settings/settings.mako',
126 self._get_template_context(c), self.request)
125 self._get_template_context(c), self.request)
127 html = formencode.htmlfill.render(
126 html = formencode.htmlfill.render(
128 data,
127 data,
129 defaults=defaults,
128 defaults=defaults,
130 encoding="UTF-8",
129 encoding="UTF-8",
131 force_defaults=False
130 force_defaults=False
132 )
131 )
133 return Response(html)
132 return Response(html)
134
133
135 @LoginRequired()
134 @LoginRequired()
136 @HasPermissionAllDecorator('hg.admin')
135 @HasPermissionAllDecorator('hg.admin')
137 @CSRFRequired()
136 @CSRFRequired()
138 def settings_vcs_update(self):
137 def settings_vcs_update(self):
139 _ = self.request.translate
138 _ = self.request.translate
140 c = self.load_default_context()
139 c = self.load_default_context()
141 c.active = 'vcs'
140 c.active = 'vcs'
142
141
143 model = VcsSettingsModel()
142 model = VcsSettingsModel()
144 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
143 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
145 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
144 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
146
145
147 settings = self.request.registry.settings
146 settings = self.request.registry.settings
148 c.svn_proxy_generate_config = settings[generate_config]
147 c.svn_proxy_generate_config = settings[generate_config]
149
148
150 application_form = ApplicationUiSettingsForm(self.request.translate)()
149 application_form = ApplicationUiSettingsForm(self.request.translate)()
151
150
152 try:
151 try:
153 form_result = application_form.to_python(dict(self.request.POST))
152 form_result = application_form.to_python(dict(self.request.POST))
154 except formencode.Invalid as errors:
153 except formencode.Invalid as errors:
155 h.flash(
154 h.flash(
156 _("Some form inputs contain invalid data."),
155 _("Some form inputs contain invalid data."),
157 category='error')
156 category='error')
158 data = render('rhodecode:templates/admin/settings/settings.mako',
157 data = render('rhodecode:templates/admin/settings/settings.mako',
159 self._get_template_context(c), self.request)
158 self._get_template_context(c), self.request)
160 html = formencode.htmlfill.render(
159 html = formencode.htmlfill.render(
161 data,
160 data,
162 defaults=errors.value,
161 defaults=errors.value,
163 errors=errors.unpack_errors() or {},
162 errors=errors.unpack_errors() or {},
164 prefix_error=False,
163 prefix_error=False,
165 encoding="UTF-8",
164 encoding="UTF-8",
166 force_defaults=False
165 force_defaults=False
167 )
166 )
168 return Response(html)
167 return Response(html)
169
168
170 try:
169 try:
171 if c.visual.allow_repo_location_change:
170 if c.visual.allow_repo_location_change:
172 model.update_global_path_setting(form_result['paths_root_path'])
171 model.update_global_path_setting(form_result['paths_root_path'])
173
172
174 model.update_global_ssl_setting(form_result['web_push_ssl'])
173 model.update_global_ssl_setting(form_result['web_push_ssl'])
175 model.update_global_hook_settings(form_result)
174 model.update_global_hook_settings(form_result)
176
175
177 model.create_or_update_global_svn_settings(form_result)
176 model.create_or_update_global_svn_settings(form_result)
178 model.create_or_update_global_hg_settings(form_result)
177 model.create_or_update_global_hg_settings(form_result)
179 model.create_or_update_global_git_settings(form_result)
178 model.create_or_update_global_git_settings(form_result)
180 model.create_or_update_global_pr_settings(form_result)
179 model.create_or_update_global_pr_settings(form_result)
181 except Exception:
180 except Exception:
182 log.exception("Exception while updating settings")
181 log.exception("Exception while updating settings")
183 h.flash(_('Error occurred during updating '
182 h.flash(_('Error occurred during updating '
184 'application settings'), category='error')
183 'application settings'), category='error')
185 else:
184 else:
186 Session().commit()
185 Session().commit()
187 h.flash(_('Updated VCS settings'), category='success')
186 h.flash(_('Updated VCS settings'), category='success')
188 raise HTTPFound(h.route_path('admin_settings_vcs'))
187 raise HTTPFound(h.route_path('admin_settings_vcs'))
189
188
190 data = render('rhodecode:templates/admin/settings/settings.mako',
189 data = render('rhodecode:templates/admin/settings/settings.mako',
191 self._get_template_context(c), self.request)
190 self._get_template_context(c), self.request)
192 html = formencode.htmlfill.render(
191 html = formencode.htmlfill.render(
193 data,
192 data,
194 defaults=self._form_defaults(),
193 defaults=self._form_defaults(),
195 encoding="UTF-8",
194 encoding="UTF-8",
196 force_defaults=False
195 force_defaults=False
197 )
196 )
198 return Response(html)
197 return Response(html)
199
198
200 @LoginRequired()
199 @LoginRequired()
201 @HasPermissionAllDecorator('hg.admin')
200 @HasPermissionAllDecorator('hg.admin')
202 @CSRFRequired()
201 @CSRFRequired()
203 def settings_vcs_delete_svn_pattern(self):
202 def settings_vcs_delete_svn_pattern(self):
204 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
203 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
205 model = VcsSettingsModel()
204 model = VcsSettingsModel()
206 try:
205 try:
207 model.delete_global_svn_pattern(delete_pattern_id)
206 model.delete_global_svn_pattern(delete_pattern_id)
208 except SettingNotFound:
207 except SettingNotFound:
209 log.exception(
208 log.exception(
210 'Failed to delete svn_pattern with id %s', delete_pattern_id)
209 'Failed to delete svn_pattern with id %s', delete_pattern_id)
211 raise HTTPNotFound()
210 raise HTTPNotFound()
212
211
213 Session().commit()
212 Session().commit()
214 return True
213 return True
215
214
216 @LoginRequired()
215 @LoginRequired()
217 @HasPermissionAllDecorator('hg.admin')
216 @HasPermissionAllDecorator('hg.admin')
218 def settings_mapping(self):
217 def settings_mapping(self):
219 c = self.load_default_context()
218 c = self.load_default_context()
220 c.active = 'mapping'
219 c.active = 'mapping'
221
220
222 data = render('rhodecode:templates/admin/settings/settings.mako',
221 data = render('rhodecode:templates/admin/settings/settings.mako',
223 self._get_template_context(c), self.request)
222 self._get_template_context(c), self.request)
224 html = formencode.htmlfill.render(
223 html = formencode.htmlfill.render(
225 data,
224 data,
226 defaults=self._form_defaults(),
225 defaults=self._form_defaults(),
227 encoding="UTF-8",
226 encoding="UTF-8",
228 force_defaults=False
227 force_defaults=False
229 )
228 )
230 return Response(html)
229 return Response(html)
231
230
232 @LoginRequired()
231 @LoginRequired()
233 @HasPermissionAllDecorator('hg.admin')
232 @HasPermissionAllDecorator('hg.admin')
234 @CSRFRequired()
233 @CSRFRequired()
235 def settings_mapping_update(self):
234 def settings_mapping_update(self):
236 _ = self.request.translate
235 _ = self.request.translate
237 c = self.load_default_context()
236 c = self.load_default_context()
238 c.active = 'mapping'
237 c.active = 'mapping'
239 rm_obsolete = self.request.POST.get('destroy', False)
238 rm_obsolete = self.request.POST.get('destroy', False)
240 invalidate_cache = self.request.POST.get('invalidate', False)
239 invalidate_cache = self.request.POST.get('invalidate', False)
241 log.debug('rescanning repo location with destroy obsolete=%s', rm_obsolete)
240 log.debug('rescanning repo location with destroy obsolete=%s', rm_obsolete)
242
241
243 if invalidate_cache:
242 if invalidate_cache:
244 log.debug('invalidating all repositories cache')
243 log.debug('invalidating all repositories cache')
245 for repo in Repository.get_all():
244 for repo in Repository.get_all():
246 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
245 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
247
246
248 filesystem_repos = ScmModel().repo_scan()
247 filesystem_repos = ScmModel().repo_scan()
249 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
248 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
250 PermissionModel().trigger_permission_flush()
249 PermissionModel().trigger_permission_flush()
251
250
252 def _repr(l):
251 def _repr(l):
253 return ', '.join(map(safe_str, l)) or '-'
252 return ', '.join(map(safe_str, l)) or '-'
254 h.flash(_('Repositories successfully '
253 h.flash(_('Repositories successfully '
255 'rescanned added: %s ; removed: %s') %
254 'rescanned added: %s ; removed: %s') %
256 (_repr(added), _repr(removed)),
255 (_repr(added), _repr(removed)),
257 category='success')
256 category='success')
258 raise HTTPFound(h.route_path('admin_settings_mapping'))
257 raise HTTPFound(h.route_path('admin_settings_mapping'))
259
258
260 @LoginRequired()
259 @LoginRequired()
261 @HasPermissionAllDecorator('hg.admin')
260 @HasPermissionAllDecorator('hg.admin')
262 def settings_global(self):
261 def settings_global(self):
263 c = self.load_default_context()
262 c = self.load_default_context()
264 c.active = 'global'
263 c.active = 'global'
265 c.personal_repo_group_default_pattern = RepoGroupModel()\
264 c.personal_repo_group_default_pattern = RepoGroupModel()\
266 .get_personal_group_name_pattern()
265 .get_personal_group_name_pattern()
267
266
268 data = render('rhodecode:templates/admin/settings/settings.mako',
267 data = render('rhodecode:templates/admin/settings/settings.mako',
269 self._get_template_context(c), self.request)
268 self._get_template_context(c), self.request)
270 html = formencode.htmlfill.render(
269 html = formencode.htmlfill.render(
271 data,
270 data,
272 defaults=self._form_defaults(),
271 defaults=self._form_defaults(),
273 encoding="UTF-8",
272 encoding="UTF-8",
274 force_defaults=False
273 force_defaults=False
275 )
274 )
276 return Response(html)
275 return Response(html)
277
276
278 @LoginRequired()
277 @LoginRequired()
279 @HasPermissionAllDecorator('hg.admin')
278 @HasPermissionAllDecorator('hg.admin')
280 @CSRFRequired()
279 @CSRFRequired()
281 def settings_global_update(self):
280 def settings_global_update(self):
282 _ = self.request.translate
281 _ = self.request.translate
283 c = self.load_default_context()
282 c = self.load_default_context()
284 c.active = 'global'
283 c.active = 'global'
285 c.personal_repo_group_default_pattern = RepoGroupModel()\
284 c.personal_repo_group_default_pattern = RepoGroupModel()\
286 .get_personal_group_name_pattern()
285 .get_personal_group_name_pattern()
287 application_form = ApplicationSettingsForm(self.request.translate)()
286 application_form = ApplicationSettingsForm(self.request.translate)()
288 try:
287 try:
289 form_result = application_form.to_python(dict(self.request.POST))
288 form_result = application_form.to_python(dict(self.request.POST))
290 except formencode.Invalid as errors:
289 except formencode.Invalid as errors:
291 h.flash(
290 h.flash(
292 _("Some form inputs contain invalid data."),
291 _("Some form inputs contain invalid data."),
293 category='error')
292 category='error')
294 data = render('rhodecode:templates/admin/settings/settings.mako',
293 data = render('rhodecode:templates/admin/settings/settings.mako',
295 self._get_template_context(c), self.request)
294 self._get_template_context(c), self.request)
296 html = formencode.htmlfill.render(
295 html = formencode.htmlfill.render(
297 data,
296 data,
298 defaults=errors.value,
297 defaults=errors.value,
299 errors=errors.unpack_errors() or {},
298 errors=errors.unpack_errors() or {},
300 prefix_error=False,
299 prefix_error=False,
301 encoding="UTF-8",
300 encoding="UTF-8",
302 force_defaults=False
301 force_defaults=False
303 )
302 )
304 return Response(html)
303 return Response(html)
305
304
306 settings = [
305 settings = [
307 ('title', 'rhodecode_title', 'unicode'),
306 ('title', 'rhodecode_title', 'unicode'),
308 ('realm', 'rhodecode_realm', 'unicode'),
307 ('realm', 'rhodecode_realm', 'unicode'),
309 ('pre_code', 'rhodecode_pre_code', 'unicode'),
308 ('pre_code', 'rhodecode_pre_code', 'unicode'),
310 ('post_code', 'rhodecode_post_code', 'unicode'),
309 ('post_code', 'rhodecode_post_code', 'unicode'),
311 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
310 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
312 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
311 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
313 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
312 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
314 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
313 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
315 ]
314 ]
316
315
317 try:
316 try:
318 for setting, form_key, type_ in settings:
317 for setting, form_key, type_ in settings:
319 sett = SettingsModel().create_or_update_setting(
318 sett = SettingsModel().create_or_update_setting(
320 setting, form_result[form_key], type_)
319 setting, form_result[form_key], type_)
321 Session().add(sett)
320 Session().add(sett)
322
321
323 Session().commit()
322 Session().commit()
324 SettingsModel().invalidate_settings_cache()
323 SettingsModel().invalidate_settings_cache()
325 h.flash(_('Updated application settings'), category='success')
324 h.flash(_('Updated application settings'), category='success')
326 except Exception:
325 except Exception:
327 log.exception("Exception while updating application settings")
326 log.exception("Exception while updating application settings")
328 h.flash(
327 h.flash(
329 _('Error occurred during updating application settings'),
328 _('Error occurred during updating application settings'),
330 category='error')
329 category='error')
331
330
332 raise HTTPFound(h.route_path('admin_settings_global'))
331 raise HTTPFound(h.route_path('admin_settings_global'))
333
332
334 @LoginRequired()
333 @LoginRequired()
335 @HasPermissionAllDecorator('hg.admin')
334 @HasPermissionAllDecorator('hg.admin')
336 def settings_visual(self):
335 def settings_visual(self):
337 c = self.load_default_context()
336 c = self.load_default_context()
338 c.active = 'visual'
337 c.active = 'visual'
339
338
340 data = render('rhodecode:templates/admin/settings/settings.mako',
339 data = render('rhodecode:templates/admin/settings/settings.mako',
341 self._get_template_context(c), self.request)
340 self._get_template_context(c), self.request)
342 html = formencode.htmlfill.render(
341 html = formencode.htmlfill.render(
343 data,
342 data,
344 defaults=self._form_defaults(),
343 defaults=self._form_defaults(),
345 encoding="UTF-8",
344 encoding="UTF-8",
346 force_defaults=False
345 force_defaults=False
347 )
346 )
348 return Response(html)
347 return Response(html)
349
348
350 @LoginRequired()
349 @LoginRequired()
351 @HasPermissionAllDecorator('hg.admin')
350 @HasPermissionAllDecorator('hg.admin')
352 @CSRFRequired()
351 @CSRFRequired()
353 def settings_visual_update(self):
352 def settings_visual_update(self):
354 _ = self.request.translate
353 _ = self.request.translate
355 c = self.load_default_context()
354 c = self.load_default_context()
356 c.active = 'visual'
355 c.active = 'visual'
357 application_form = ApplicationVisualisationForm(self.request.translate)()
356 application_form = ApplicationVisualisationForm(self.request.translate)()
358 try:
357 try:
359 form_result = application_form.to_python(dict(self.request.POST))
358 form_result = application_form.to_python(dict(self.request.POST))
360 except formencode.Invalid as errors:
359 except formencode.Invalid as errors:
361 h.flash(
360 h.flash(
362 _("Some form inputs contain invalid data."),
361 _("Some form inputs contain invalid data."),
363 category='error')
362 category='error')
364 data = render('rhodecode:templates/admin/settings/settings.mako',
363 data = render('rhodecode:templates/admin/settings/settings.mako',
365 self._get_template_context(c), self.request)
364 self._get_template_context(c), self.request)
366 html = formencode.htmlfill.render(
365 html = formencode.htmlfill.render(
367 data,
366 data,
368 defaults=errors.value,
367 defaults=errors.value,
369 errors=errors.unpack_errors() or {},
368 errors=errors.unpack_errors() or {},
370 prefix_error=False,
369 prefix_error=False,
371 encoding="UTF-8",
370 encoding="UTF-8",
372 force_defaults=False
371 force_defaults=False
373 )
372 )
374 return Response(html)
373 return Response(html)
375
374
376 try:
375 try:
377 settings = [
376 settings = [
378 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
377 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
379 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
378 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
380 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
379 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
381 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
380 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
382 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
381 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
383 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
382 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
384 ('show_version', 'rhodecode_show_version', 'bool'),
383 ('show_version', 'rhodecode_show_version', 'bool'),
385 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
384 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
386 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
385 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
387 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
386 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
388 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
387 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
389 ('clone_uri_id_tmpl', 'rhodecode_clone_uri_id_tmpl', 'unicode'),
388 ('clone_uri_id_tmpl', 'rhodecode_clone_uri_id_tmpl', 'unicode'),
390 ('clone_uri_ssh_tmpl', 'rhodecode_clone_uri_ssh_tmpl', 'unicode'),
389 ('clone_uri_ssh_tmpl', 'rhodecode_clone_uri_ssh_tmpl', 'unicode'),
391 ('support_url', 'rhodecode_support_url', 'unicode'),
390 ('support_url', 'rhodecode_support_url', 'unicode'),
392 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
391 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
393 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
392 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
394 ]
393 ]
395 for setting, form_key, type_ in settings:
394 for setting, form_key, type_ in settings:
396 sett = SettingsModel().create_or_update_setting(
395 sett = SettingsModel().create_or_update_setting(
397 setting, form_result[form_key], type_)
396 setting, form_result[form_key], type_)
398 Session().add(sett)
397 Session().add(sett)
399
398
400 Session().commit()
399 Session().commit()
401 SettingsModel().invalidate_settings_cache()
400 SettingsModel().invalidate_settings_cache()
402 h.flash(_('Updated visualisation settings'), category='success')
401 h.flash(_('Updated visualisation settings'), category='success')
403 except Exception:
402 except Exception:
404 log.exception("Exception updating visualization settings")
403 log.exception("Exception updating visualization settings")
405 h.flash(_('Error occurred during updating '
404 h.flash(_('Error occurred during updating '
406 'visualisation settings'),
405 'visualisation settings'),
407 category='error')
406 category='error')
408
407
409 raise HTTPFound(h.route_path('admin_settings_visual'))
408 raise HTTPFound(h.route_path('admin_settings_visual'))
410
409
411 @LoginRequired()
410 @LoginRequired()
412 @HasPermissionAllDecorator('hg.admin')
411 @HasPermissionAllDecorator('hg.admin')
413 def settings_issuetracker(self):
412 def settings_issuetracker(self):
414 c = self.load_default_context()
413 c = self.load_default_context()
415 c.active = 'issuetracker'
414 c.active = 'issuetracker'
416 defaults = c.rc_config
415 defaults = c.rc_config
417
416
418 entry_key = 'rhodecode_issuetracker_pat_'
417 entry_key = 'rhodecode_issuetracker_pat_'
419
418
420 c.issuetracker_entries = {}
419 c.issuetracker_entries = {}
421 for k, v in defaults.items():
420 for k, v in defaults.items():
422 if k.startswith(entry_key):
421 if k.startswith(entry_key):
423 uid = k[len(entry_key):]
422 uid = k[len(entry_key):]
424 c.issuetracker_entries[uid] = None
423 c.issuetracker_entries[uid] = None
425
424
426 for uid in c.issuetracker_entries:
425 for uid in c.issuetracker_entries:
427 c.issuetracker_entries[uid] = AttributeDict({
426 c.issuetracker_entries[uid] = AttributeDict({
428 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
427 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
429 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
428 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
430 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
429 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
431 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
430 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
432 })
431 })
433
432
434 return self._get_template_context(c)
433 return self._get_template_context(c)
435
434
436 @LoginRequired()
435 @LoginRequired()
437 @HasPermissionAllDecorator('hg.admin')
436 @HasPermissionAllDecorator('hg.admin')
438 @CSRFRequired()
437 @CSRFRequired()
439 def settings_issuetracker_test(self):
438 def settings_issuetracker_test(self):
440 error_container = []
439 error_container = []
441
440
442 urlified_commit = h.urlify_commit_message(
441 urlified_commit = h.urlify_commit_message(
443 self.request.POST.get('test_text', ''),
442 self.request.POST.get('test_text', ''),
444 'repo_group/test_repo1', error_container=error_container)
443 'repo_group/test_repo1', error_container=error_container)
445 if error_container:
444 if error_container:
446 def converter(inp):
445 def converter(inp):
447 return h.html_escape(inp)
446 return h.html_escape(inp)
448
447
449 return 'ERRORS: ' + '\n'.join(map(converter, error_container))
448 return 'ERRORS: ' + '\n'.join(map(converter, error_container))
450
449
451 return urlified_commit
450 return urlified_commit
452
451
453 @LoginRequired()
452 @LoginRequired()
454 @HasPermissionAllDecorator('hg.admin')
453 @HasPermissionAllDecorator('hg.admin')
455 @CSRFRequired()
454 @CSRFRequired()
456 def settings_issuetracker_update(self):
455 def settings_issuetracker_update(self):
457 _ = self.request.translate
456 _ = self.request.translate
458 self.load_default_context()
457 self.load_default_context()
459 settings_model = IssueTrackerSettingsModel()
458 settings_model = IssueTrackerSettingsModel()
460
459
461 try:
460 try:
462 form = IssueTrackerPatternsForm(self.request.translate)()
461 form = IssueTrackerPatternsForm(self.request.translate)()
463 data = form.to_python(self.request.POST)
462 data = form.to_python(self.request.POST)
464 except formencode.Invalid as errors:
463 except formencode.Invalid as errors:
465 log.exception('Failed to add new pattern')
464 log.exception('Failed to add new pattern')
466 error = errors
465 error = errors
467 h.flash(_('Invalid issue tracker pattern: {}'.format(error)),
466 h.flash(_(f'Invalid issue tracker pattern: {error}'),
468 category='error')
467 category='error')
469 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
468 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
470
469
471 if data:
470 if data:
472 for uid in data.get('delete_patterns', []):
471 for uid in data.get('delete_patterns', []):
473 settings_model.delete_entries(uid)
472 settings_model.delete_entries(uid)
474
473
475 for pattern in data.get('patterns', []):
474 for pattern in data.get('patterns', []):
476 for setting, value, type_ in pattern:
475 for setting, value, type_ in pattern:
477 sett = settings_model.create_or_update_setting(
476 sett = settings_model.create_or_update_setting(
478 setting, value, type_)
477 setting, value, type_)
479 Session().add(sett)
478 Session().add(sett)
480
479
481 Session().commit()
480 Session().commit()
482
481
483 SettingsModel().invalidate_settings_cache()
482 SettingsModel().invalidate_settings_cache()
484 h.flash(_('Updated issue tracker entries'), category='success')
483 h.flash(_('Updated issue tracker entries'), category='success')
485 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
484 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
486
485
487 @LoginRequired()
486 @LoginRequired()
488 @HasPermissionAllDecorator('hg.admin')
487 @HasPermissionAllDecorator('hg.admin')
489 @CSRFRequired()
488 @CSRFRequired()
490 def settings_issuetracker_delete(self):
489 def settings_issuetracker_delete(self):
491 _ = self.request.translate
490 _ = self.request.translate
492 self.load_default_context()
491 self.load_default_context()
493 uid = self.request.POST.get('uid')
492 uid = self.request.POST.get('uid')
494 try:
493 try:
495 IssueTrackerSettingsModel().delete_entries(uid)
494 IssueTrackerSettingsModel().delete_entries(uid)
496 except Exception:
495 except Exception:
497 log.exception('Failed to delete issue tracker setting %s', uid)
496 log.exception('Failed to delete issue tracker setting %s', uid)
498 raise HTTPNotFound()
497 raise HTTPNotFound()
499
498
500 SettingsModel().invalidate_settings_cache()
499 SettingsModel().invalidate_settings_cache()
501 h.flash(_('Removed issue tracker entry.'), category='success')
500 h.flash(_('Removed issue tracker entry.'), category='success')
502
501
503 return {'deleted': uid}
502 return {'deleted': uid}
504
503
505 @LoginRequired()
504 @LoginRequired()
506 @HasPermissionAllDecorator('hg.admin')
505 @HasPermissionAllDecorator('hg.admin')
507 def settings_email(self):
506 def settings_email(self):
508 c = self.load_default_context()
507 c = self.load_default_context()
509 c.active = 'email'
508 c.active = 'email'
510 c.rhodecode_ini = rhodecode.CONFIG
509 c.rhodecode_ini = rhodecode.CONFIG
511
510
512 data = render('rhodecode:templates/admin/settings/settings.mako',
511 data = render('rhodecode:templates/admin/settings/settings.mako',
513 self._get_template_context(c), self.request)
512 self._get_template_context(c), self.request)
514 html = formencode.htmlfill.render(
513 html = formencode.htmlfill.render(
515 data,
514 data,
516 defaults=self._form_defaults(),
515 defaults=self._form_defaults(),
517 encoding="UTF-8",
516 encoding="UTF-8",
518 force_defaults=False
517 force_defaults=False
519 )
518 )
520 return Response(html)
519 return Response(html)
521
520
522 @LoginRequired()
521 @LoginRequired()
523 @HasPermissionAllDecorator('hg.admin')
522 @HasPermissionAllDecorator('hg.admin')
524 @CSRFRequired()
523 @CSRFRequired()
525 def settings_email_update(self):
524 def settings_email_update(self):
526 _ = self.request.translate
525 _ = self.request.translate
527 c = self.load_default_context()
526 c = self.load_default_context()
528 c.active = 'email'
527 c.active = 'email'
529
528
530 test_email = self.request.POST.get('test_email')
529 test_email = self.request.POST.get('test_email')
531
530
532 if not test_email:
531 if not test_email:
533 h.flash(_('Please enter email address'), category='error')
532 h.flash(_('Please enter email address'), category='error')
534 raise HTTPFound(h.route_path('admin_settings_email'))
533 raise HTTPFound(h.route_path('admin_settings_email'))
535
534
536 email_kwargs = {
535 email_kwargs = {
537 'date': datetime.datetime.now(),
536 'date': datetime.datetime.now(),
538 'user': self._rhodecode_db_user
537 'user': self._rhodecode_db_user
539 }
538 }
540
539
541 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
540 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
542 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
541 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
543
542
544 recipients = [test_email] if test_email else None
543 recipients = [test_email] if test_email else None
545
544
546 run_task(tasks.send_email, recipients, subject,
545 run_task(tasks.send_email, recipients, subject,
547 email_body_plaintext, email_body)
546 email_body_plaintext, email_body)
548
547
549 h.flash(_('Send email task created'), category='success')
548 h.flash(_('Send email task created'), category='success')
550 raise HTTPFound(h.route_path('admin_settings_email'))
549 raise HTTPFound(h.route_path('admin_settings_email'))
551
550
552 @LoginRequired()
551 @LoginRequired()
553 @HasPermissionAllDecorator('hg.admin')
552 @HasPermissionAllDecorator('hg.admin')
554 def settings_hooks(self):
553 def settings_hooks(self):
555 c = self.load_default_context()
554 c = self.load_default_context()
556 c.active = 'hooks'
555 c.active = 'hooks'
557
556
558 model = SettingsModel()
557 model = SettingsModel()
559 c.hooks = model.get_builtin_hooks()
558 c.hooks = model.get_builtin_hooks()
560 c.custom_hooks = model.get_custom_hooks()
559 c.custom_hooks = model.get_custom_hooks()
561
560
562 data = render('rhodecode:templates/admin/settings/settings.mako',
561 data = render('rhodecode:templates/admin/settings/settings.mako',
563 self._get_template_context(c), self.request)
562 self._get_template_context(c), self.request)
564 html = formencode.htmlfill.render(
563 html = formencode.htmlfill.render(
565 data,
564 data,
566 defaults=self._form_defaults(),
565 defaults=self._form_defaults(),
567 encoding="UTF-8",
566 encoding="UTF-8",
568 force_defaults=False
567 force_defaults=False
569 )
568 )
570 return Response(html)
569 return Response(html)
571
570
572 @LoginRequired()
571 @LoginRequired()
573 @HasPermissionAllDecorator('hg.admin')
572 @HasPermissionAllDecorator('hg.admin')
574 @CSRFRequired()
573 @CSRFRequired()
575 def settings_hooks_update(self):
574 def settings_hooks_update(self):
576 _ = self.request.translate
575 _ = self.request.translate
577 c = self.load_default_context()
576 c = self.load_default_context()
578 c.active = 'hooks'
577 c.active = 'hooks'
579 if c.visual.allow_custom_hooks_settings:
578 if c.visual.allow_custom_hooks_settings:
580 ui_key = self.request.POST.get('new_hook_ui_key')
579 ui_key = self.request.POST.get('new_hook_ui_key')
581 ui_value = self.request.POST.get('new_hook_ui_value')
580 ui_value = self.request.POST.get('new_hook_ui_value')
582
581
583 hook_id = self.request.POST.get('hook_id')
582 hook_id = self.request.POST.get('hook_id')
584 new_hook = False
583 new_hook = False
585
584
586 model = SettingsModel()
585 model = SettingsModel()
587 try:
586 try:
588 if ui_value and ui_key:
587 if ui_value and ui_key:
589 model.create_or_update_hook(ui_key, ui_value)
588 model.create_or_update_hook(ui_key, ui_value)
590 h.flash(_('Added new hook'), category='success')
589 h.flash(_('Added new hook'), category='success')
591 new_hook = True
590 new_hook = True
592 elif hook_id:
591 elif hook_id:
593 RhodeCodeUi.delete(hook_id)
592 RhodeCodeUi.delete(hook_id)
594 Session().commit()
593 Session().commit()
595
594
596 # check for edits
595 # check for edits
597 update = False
596 update = False
598 _d = self.request.POST.dict_of_lists()
597 _d = self.request.POST.dict_of_lists()
599 for k, v in zip(_d.get('hook_ui_key', []),
598 for k, v in zip(_d.get('hook_ui_key', []),
600 _d.get('hook_ui_value_new', [])):
599 _d.get('hook_ui_value_new', [])):
601 model.create_or_update_hook(k, v)
600 model.create_or_update_hook(k, v)
602 update = True
601 update = True
603
602
604 if update and not new_hook:
603 if update and not new_hook:
605 h.flash(_('Updated hooks'), category='success')
604 h.flash(_('Updated hooks'), category='success')
606 Session().commit()
605 Session().commit()
607 except Exception:
606 except Exception:
608 log.exception("Exception during hook creation")
607 log.exception("Exception during hook creation")
609 h.flash(_('Error occurred during hook creation'),
608 h.flash(_('Error occurred during hook creation'),
610 category='error')
609 category='error')
611
610
612 raise HTTPFound(h.route_path('admin_settings_hooks'))
611 raise HTTPFound(h.route_path('admin_settings_hooks'))
613
612
614 @LoginRequired()
613 @LoginRequired()
615 @HasPermissionAllDecorator('hg.admin')
614 @HasPermissionAllDecorator('hg.admin')
616 def settings_search(self):
615 def settings_search(self):
617 c = self.load_default_context()
616 c = self.load_default_context()
618 c.active = 'search'
617 c.active = 'search'
619
618
620 c.searcher = searcher_from_config(self.request.registry.settings)
619 c.searcher = searcher_from_config(self.request.registry.settings)
621 c.statistics = c.searcher.statistics(self.request.translate)
620 c.statistics = c.searcher.statistics(self.request.translate)
622
621
623 return self._get_template_context(c)
622 return self._get_template_context(c)
624
623
625 @LoginRequired()
624 @LoginRequired()
626 @HasPermissionAllDecorator('hg.admin')
625 @HasPermissionAllDecorator('hg.admin')
627 def settings_automation(self):
626 def settings_automation(self):
628 c = self.load_default_context()
627 c = self.load_default_context()
629 c.active = 'automation'
628 c.active = 'automation'
630
629
631 return self._get_template_context(c)
630 return self._get_template_context(c)
632
631
633 @LoginRequired()
632 @LoginRequired()
634 @HasPermissionAllDecorator('hg.admin')
633 @HasPermissionAllDecorator('hg.admin')
635 def settings_labs(self):
634 def settings_labs(self):
636 c = self.load_default_context()
635 c = self.load_default_context()
637 if not c.labs_active:
636 if not c.labs_active:
638 raise HTTPFound(h.route_path('admin_settings'))
637 raise HTTPFound(h.route_path('admin_settings'))
639
638
640 c.active = 'labs'
639 c.active = 'labs'
641 c.lab_settings = _LAB_SETTINGS
640 c.lab_settings = _LAB_SETTINGS
642
641
643 data = render('rhodecode:templates/admin/settings/settings.mako',
642 data = render('rhodecode:templates/admin/settings/settings.mako',
644 self._get_template_context(c), self.request)
643 self._get_template_context(c), self.request)
645 html = formencode.htmlfill.render(
644 html = formencode.htmlfill.render(
646 data,
645 data,
647 defaults=self._form_defaults(),
646 defaults=self._form_defaults(),
648 encoding="UTF-8",
647 encoding="UTF-8",
649 force_defaults=False
648 force_defaults=False
650 )
649 )
651 return Response(html)
650 return Response(html)
652
651
653 @LoginRequired()
652 @LoginRequired()
654 @HasPermissionAllDecorator('hg.admin')
653 @HasPermissionAllDecorator('hg.admin')
655 @CSRFRequired()
654 @CSRFRequired()
656 def settings_labs_update(self):
655 def settings_labs_update(self):
657 _ = self.request.translate
656 _ = self.request.translate
658 c = self.load_default_context()
657 c = self.load_default_context()
659 c.active = 'labs'
658 c.active = 'labs'
660
659
661 application_form = LabsSettingsForm(self.request.translate)()
660 application_form = LabsSettingsForm(self.request.translate)()
662 try:
661 try:
663 form_result = application_form.to_python(dict(self.request.POST))
662 form_result = application_form.to_python(dict(self.request.POST))
664 except formencode.Invalid as errors:
663 except formencode.Invalid as errors:
665 h.flash(
664 h.flash(
666 _("Some form inputs contain invalid data."),
665 _("Some form inputs contain invalid data."),
667 category='error')
666 category='error')
668 data = render('rhodecode:templates/admin/settings/settings.mako',
667 data = render('rhodecode:templates/admin/settings/settings.mako',
669 self._get_template_context(c), self.request)
668 self._get_template_context(c), self.request)
670 html = formencode.htmlfill.render(
669 html = formencode.htmlfill.render(
671 data,
670 data,
672 defaults=errors.value,
671 defaults=errors.value,
673 errors=errors.unpack_errors() or {},
672 errors=errors.unpack_errors() or {},
674 prefix_error=False,
673 prefix_error=False,
675 encoding="UTF-8",
674 encoding="UTF-8",
676 force_defaults=False
675 force_defaults=False
677 )
676 )
678 return Response(html)
677 return Response(html)
679
678
680 try:
679 try:
681 session = Session()
680 session = Session()
682 for setting in _LAB_SETTINGS:
681 for setting in _LAB_SETTINGS:
683 setting_name = setting.key[len('rhodecode_'):]
682 setting_name = setting.key[len('rhodecode_'):]
684 sett = SettingsModel().create_or_update_setting(
683 sett = SettingsModel().create_or_update_setting(
685 setting_name, form_result[setting.key], setting.type)
684 setting_name, form_result[setting.key], setting.type)
686 session.add(sett)
685 session.add(sett)
687
686
688 except Exception:
687 except Exception:
689 log.exception('Exception while updating lab settings')
688 log.exception('Exception while updating lab settings')
690 h.flash(_('Error occurred during updating labs settings'),
689 h.flash(_('Error occurred during updating labs settings'),
691 category='error')
690 category='error')
692 else:
691 else:
693 Session().commit()
692 Session().commit()
694 SettingsModel().invalidate_settings_cache()
693 SettingsModel().invalidate_settings_cache()
695 h.flash(_('Updated Labs settings'), category='success')
694 h.flash(_('Updated Labs settings'), category='success')
696 raise HTTPFound(h.route_path('admin_settings_labs'))
695 raise HTTPFound(h.route_path('admin_settings_labs'))
697
696
698 data = render('rhodecode:templates/admin/settings/settings.mako',
697 data = render('rhodecode:templates/admin/settings/settings.mako',
699 self._get_template_context(c), self.request)
698 self._get_template_context(c), self.request)
700 html = formencode.htmlfill.render(
699 html = formencode.htmlfill.render(
701 data,
700 data,
702 defaults=self._form_defaults(),
701 defaults=self._form_defaults(),
703 encoding="UTF-8",
702 encoding="UTF-8",
704 force_defaults=False
703 force_defaults=False
705 )
704 )
706 return Response(html)
705 return Response(html)
707
706
708
707
709 # :param key: name of the setting including the 'rhodecode_' prefix
708 # :param key: name of the setting including the 'rhodecode_' prefix
710 # :param type: the RhodeCodeSetting type to use.
709 # :param type: the RhodeCodeSetting type to use.
711 # :param group: the i18ned group in which we should dispaly this setting
710 # :param group: the i18ned group in which we should dispaly this setting
712 # :param label: the i18ned label we should display for this setting
711 # :param label: the i18ned label we should display for this setting
713 # :param help: the i18ned help we should dispaly for this setting
712 # :param help: the i18ned help we should dispaly for this setting
714 LabSetting = collections.namedtuple(
713 LabSetting = collections.namedtuple(
715 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
714 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
716
715
717
716
718 # This list has to be kept in sync with the form
717 # This list has to be kept in sync with the form
719 # rhodecode.model.forms.LabsSettingsForm.
718 # rhodecode.model.forms.LabsSettingsForm.
720 _LAB_SETTINGS = [
719 _LAB_SETTINGS = [
721
720
722 ]
721 ]
@@ -1,56 +1,54 b''
1
2
3 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 import logging
19 import logging
22
20
23
21
24
22
25 from rhodecode.apps._base import BaseAppView
23 from rhodecode.apps._base import BaseAppView
26 from rhodecode.apps.svn_support.utils import generate_mod_dav_svn_config
24 from rhodecode.apps.svn_support.utils import generate_mod_dav_svn_config
27 from rhodecode.lib.auth import (
25 from rhodecode.lib.auth import (
28 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
26 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
29
27
30 log = logging.getLogger(__name__)
28 log = logging.getLogger(__name__)
31
29
32
30
33 class AdminSvnConfigView(BaseAppView):
31 class AdminSvnConfigView(BaseAppView):
34
32
35 @LoginRequired()
33 @LoginRequired()
36 @HasPermissionAllDecorator('hg.admin')
34 @HasPermissionAllDecorator('hg.admin')
37 @CSRFRequired()
35 @CSRFRequired()
38 def vcs_svn_generate_config(self):
36 def vcs_svn_generate_config(self):
39 _ = self.request.translate
37 _ = self.request.translate
40 try:
38 try:
41 file_path = generate_mod_dav_svn_config(self.request.registry)
39 file_path = generate_mod_dav_svn_config(self.request.registry)
42 msg = {
40 msg = {
43 'message': _('Apache configuration for Subversion generated at `{}`.').format(file_path),
41 'message': _('Apache configuration for Subversion generated at `{}`.').format(file_path),
44 'level': 'success',
42 'level': 'success',
45 }
43 }
46 except Exception:
44 except Exception:
47 log.exception(
45 log.exception(
48 'Exception while generating the Apache '
46 'Exception while generating the Apache '
49 'configuration for Subversion.')
47 'configuration for Subversion.')
50 msg = {
48 msg = {
51 'message': _('Failed to generate the Apache configuration for Subversion.'),
49 'message': _('Failed to generate the Apache configuration for Subversion.'),
52 'level': 'error',
50 'level': 'error',
53 }
51 }
54
52
55 data = {'message': msg}
53 data = {'message': msg}
56 return data
54 return data
@@ -1,19 +1,17 b''
1
2
3 # Copyright (C) 2011-2023 RhodeCode GmbH
1 # Copyright (C) 2011-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -1,784 +1,782 b''
1
2
3 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 import logging
19 import logging
22 import datetime
20 import datetime
23 import string
21 import string
24
22
25 import formencode
23 import formencode
26 import formencode.htmlfill
24 import formencode.htmlfill
27 import peppercorn
25 import peppercorn
28 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
26 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
29
27
30 from rhodecode.apps._base import BaseAppView, DataGridAppView
28 from rhodecode.apps._base import BaseAppView, DataGridAppView
31 from rhodecode import forms
29 from rhodecode import forms
32 from rhodecode.lib import helpers as h
30 from rhodecode.lib import helpers as h
33 from rhodecode.lib import audit_logger
31 from rhodecode.lib import audit_logger
34 from rhodecode.lib import ext_json
32 from rhodecode.lib import ext_json
35 from rhodecode.lib.auth import (
33 from rhodecode.lib.auth import (
36 LoginRequired, NotAnonymous, CSRFRequired,
34 LoginRequired, NotAnonymous, CSRFRequired,
37 HasRepoPermissionAny, HasRepoGroupPermissionAny, AuthUser)
35 HasRepoPermissionAny, HasRepoGroupPermissionAny, AuthUser)
38 from rhodecode.lib.channelstream import (
36 from rhodecode.lib.channelstream import (
39 channelstream_request, ChannelstreamException)
37 channelstream_request, ChannelstreamException)
40 from rhodecode.lib.hash_utils import md5_safe
38 from rhodecode.lib.hash_utils import md5_safe
41 from rhodecode.lib.utils2 import safe_int, md5, str2bool
39 from rhodecode.lib.utils2 import safe_int, md5, str2bool
42 from rhodecode.model.auth_token import AuthTokenModel
40 from rhodecode.model.auth_token import AuthTokenModel
43 from rhodecode.model.comment import CommentsModel
41 from rhodecode.model.comment import CommentsModel
44 from rhodecode.model.db import (
42 from rhodecode.model.db import (
45 IntegrityError, or_, in_filter_generator,
43 IntegrityError, or_, in_filter_generator,
46 Repository, UserEmailMap, UserApiKeys, UserFollowing,
44 Repository, UserEmailMap, UserApiKeys, UserFollowing,
47 PullRequest, UserBookmark, RepoGroup, ChangesetStatus)
45 PullRequest, UserBookmark, RepoGroup, ChangesetStatus)
48 from rhodecode.model.meta import Session
46 from rhodecode.model.meta import Session
49 from rhodecode.model.pull_request import PullRequestModel
47 from rhodecode.model.pull_request import PullRequestModel
50 from rhodecode.model.user import UserModel
48 from rhodecode.model.user import UserModel
51 from rhodecode.model.user_group import UserGroupModel
49 from rhodecode.model.user_group import UserGroupModel
52 from rhodecode.model.validation_schema.schemas import user_schema
50 from rhodecode.model.validation_schema.schemas import user_schema
53
51
54 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
55
53
56
54
57 class MyAccountView(BaseAppView, DataGridAppView):
55 class MyAccountView(BaseAppView, DataGridAppView):
58 ALLOW_SCOPED_TOKENS = False
56 ALLOW_SCOPED_TOKENS = False
59 """
57 """
60 This view has alternative version inside EE, if modified please take a look
58 This view has alternative version inside EE, if modified please take a look
61 in there as well.
59 in there as well.
62 """
60 """
63
61
64 def load_default_context(self):
62 def load_default_context(self):
65 c = self._get_local_tmpl_context()
63 c = self._get_local_tmpl_context()
66 c.user = c.auth_user.get_instance()
64 c.user = c.auth_user.get_instance()
67 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
65 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
68 return c
66 return c
69
67
70 @LoginRequired()
68 @LoginRequired()
71 @NotAnonymous()
69 @NotAnonymous()
72 def my_account_profile(self):
70 def my_account_profile(self):
73 c = self.load_default_context()
71 c = self.load_default_context()
74 c.active = 'profile'
72 c.active = 'profile'
75 c.extern_type = c.user.extern_type
73 c.extern_type = c.user.extern_type
76 return self._get_template_context(c)
74 return self._get_template_context(c)
77
75
78 @LoginRequired()
76 @LoginRequired()
79 @NotAnonymous()
77 @NotAnonymous()
80 def my_account_edit(self):
78 def my_account_edit(self):
81 c = self.load_default_context()
79 c = self.load_default_context()
82 c.active = 'profile_edit'
80 c.active = 'profile_edit'
83 c.extern_type = c.user.extern_type
81 c.extern_type = c.user.extern_type
84 c.extern_name = c.user.extern_name
82 c.extern_name = c.user.extern_name
85
83
86 schema = user_schema.UserProfileSchema().bind(
84 schema = user_schema.UserProfileSchema().bind(
87 username=c.user.username, user_emails=c.user.emails)
85 username=c.user.username, user_emails=c.user.emails)
88 appstruct = {
86 appstruct = {
89 'username': c.user.username,
87 'username': c.user.username,
90 'email': c.user.email,
88 'email': c.user.email,
91 'firstname': c.user.firstname,
89 'firstname': c.user.firstname,
92 'lastname': c.user.lastname,
90 'lastname': c.user.lastname,
93 'description': c.user.description,
91 'description': c.user.description,
94 }
92 }
95 c.form = forms.RcForm(
93 c.form = forms.RcForm(
96 schema, appstruct=appstruct,
94 schema, appstruct=appstruct,
97 action=h.route_path('my_account_update'),
95 action=h.route_path('my_account_update'),
98 buttons=(forms.buttons.save, forms.buttons.reset))
96 buttons=(forms.buttons.save, forms.buttons.reset))
99
97
100 return self._get_template_context(c)
98 return self._get_template_context(c)
101
99
102 @LoginRequired()
100 @LoginRequired()
103 @NotAnonymous()
101 @NotAnonymous()
104 @CSRFRequired()
102 @CSRFRequired()
105 def my_account_update(self):
103 def my_account_update(self):
106 _ = self.request.translate
104 _ = self.request.translate
107 c = self.load_default_context()
105 c = self.load_default_context()
108 c.active = 'profile_edit'
106 c.active = 'profile_edit'
109 c.perm_user = c.auth_user
107 c.perm_user = c.auth_user
110 c.extern_type = c.user.extern_type
108 c.extern_type = c.user.extern_type
111 c.extern_name = c.user.extern_name
109 c.extern_name = c.user.extern_name
112
110
113 schema = user_schema.UserProfileSchema().bind(
111 schema = user_schema.UserProfileSchema().bind(
114 username=c.user.username, user_emails=c.user.emails)
112 username=c.user.username, user_emails=c.user.emails)
115 form = forms.RcForm(
113 form = forms.RcForm(
116 schema, buttons=(forms.buttons.save, forms.buttons.reset))
114 schema, buttons=(forms.buttons.save, forms.buttons.reset))
117
115
118 controls = list(self.request.POST.items())
116 controls = list(self.request.POST.items())
119 try:
117 try:
120 valid_data = form.validate(controls)
118 valid_data = form.validate(controls)
121 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
119 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
122 'new_password', 'password_confirmation']
120 'new_password', 'password_confirmation']
123 if c.extern_type != "rhodecode":
121 if c.extern_type != "rhodecode":
124 # forbid updating username for external accounts
122 # forbid updating username for external accounts
125 skip_attrs.append('username')
123 skip_attrs.append('username')
126 old_email = c.user.email
124 old_email = c.user.email
127 UserModel().update_user(
125 UserModel().update_user(
128 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
126 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
129 **valid_data)
127 **valid_data)
130 if old_email != valid_data['email']:
128 if old_email != valid_data['email']:
131 old = UserEmailMap.query() \
129 old = UserEmailMap.query() \
132 .filter(UserEmailMap.user == c.user)\
130 .filter(UserEmailMap.user == c.user)\
133 .filter(UserEmailMap.email == valid_data['email'])\
131 .filter(UserEmailMap.email == valid_data['email'])\
134 .first()
132 .first()
135 old.email = old_email
133 old.email = old_email
136 h.flash(_('Your account was updated successfully'), category='success')
134 h.flash(_('Your account was updated successfully'), category='success')
137 Session().commit()
135 Session().commit()
138 except forms.ValidationFailure as e:
136 except forms.ValidationFailure as e:
139 c.form = e
137 c.form = e
140 return self._get_template_context(c)
138 return self._get_template_context(c)
141 except Exception:
139 except Exception:
142 log.exception("Exception updating user")
140 log.exception("Exception updating user")
143 h.flash(_('Error occurred during update of user'),
141 h.flash(_('Error occurred during update of user'),
144 category='error')
142 category='error')
145 raise HTTPFound(h.route_path('my_account_profile'))
143 raise HTTPFound(h.route_path('my_account_profile'))
146
144
147 @LoginRequired()
145 @LoginRequired()
148 @NotAnonymous()
146 @NotAnonymous()
149 def my_account_password(self):
147 def my_account_password(self):
150 c = self.load_default_context()
148 c = self.load_default_context()
151 c.active = 'password'
149 c.active = 'password'
152 c.extern_type = c.user.extern_type
150 c.extern_type = c.user.extern_type
153
151
154 schema = user_schema.ChangePasswordSchema().bind(
152 schema = user_schema.ChangePasswordSchema().bind(
155 username=c.user.username)
153 username=c.user.username)
156
154
157 form = forms.Form(
155 form = forms.Form(
158 schema,
156 schema,
159 action=h.route_path('my_account_password_update'),
157 action=h.route_path('my_account_password_update'),
160 buttons=(forms.buttons.save, forms.buttons.reset))
158 buttons=(forms.buttons.save, forms.buttons.reset))
161
159
162 c.form = form
160 c.form = form
163 return self._get_template_context(c)
161 return self._get_template_context(c)
164
162
165 @LoginRequired()
163 @LoginRequired()
166 @NotAnonymous()
164 @NotAnonymous()
167 @CSRFRequired()
165 @CSRFRequired()
168 def my_account_password_update(self):
166 def my_account_password_update(self):
169 _ = self.request.translate
167 _ = self.request.translate
170 c = self.load_default_context()
168 c = self.load_default_context()
171 c.active = 'password'
169 c.active = 'password'
172 c.extern_type = c.user.extern_type
170 c.extern_type = c.user.extern_type
173
171
174 schema = user_schema.ChangePasswordSchema().bind(
172 schema = user_schema.ChangePasswordSchema().bind(
175 username=c.user.username)
173 username=c.user.username)
176
174
177 form = forms.Form(
175 form = forms.Form(
178 schema, buttons=(forms.buttons.save, forms.buttons.reset))
176 schema, buttons=(forms.buttons.save, forms.buttons.reset))
179
177
180 if c.extern_type != 'rhodecode':
178 if c.extern_type != 'rhodecode':
181 raise HTTPFound(self.request.route_path('my_account_password'))
179 raise HTTPFound(self.request.route_path('my_account_password'))
182
180
183 controls = list(self.request.POST.items())
181 controls = list(self.request.POST.items())
184 try:
182 try:
185 valid_data = form.validate(controls)
183 valid_data = form.validate(controls)
186 UserModel().update_user(c.user.user_id, **valid_data)
184 UserModel().update_user(c.user.user_id, **valid_data)
187 c.user.update_userdata(force_password_change=False)
185 c.user.update_userdata(force_password_change=False)
188 Session().commit()
186 Session().commit()
189 except forms.ValidationFailure as e:
187 except forms.ValidationFailure as e:
190 c.form = e
188 c.form = e
191 return self._get_template_context(c)
189 return self._get_template_context(c)
192
190
193 except Exception:
191 except Exception:
194 log.exception("Exception updating password")
192 log.exception("Exception updating password")
195 h.flash(_('Error occurred during update of user password'),
193 h.flash(_('Error occurred during update of user password'),
196 category='error')
194 category='error')
197 else:
195 else:
198 instance = c.auth_user.get_instance()
196 instance = c.auth_user.get_instance()
199 self.session.setdefault('rhodecode_user', {}).update(
197 self.session.setdefault('rhodecode_user', {}).update(
200 {'password': md5_safe(instance.password)})
198 {'password': md5_safe(instance.password)})
201 self.session.save()
199 self.session.save()
202 h.flash(_("Successfully updated password"), category='success')
200 h.flash(_("Successfully updated password"), category='success')
203
201
204 raise HTTPFound(self.request.route_path('my_account_password'))
202 raise HTTPFound(self.request.route_path('my_account_password'))
205
203
206 @LoginRequired()
204 @LoginRequired()
207 @NotAnonymous()
205 @NotAnonymous()
208 def my_account_auth_tokens(self):
206 def my_account_auth_tokens(self):
209 _ = self.request.translate
207 _ = self.request.translate
210
208
211 c = self.load_default_context()
209 c = self.load_default_context()
212 c.active = 'auth_tokens'
210 c.active = 'auth_tokens'
213 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
211 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
214 c.role_values = [
212 c.role_values = [
215 (x, AuthTokenModel.cls._get_role_name(x))
213 (x, AuthTokenModel.cls._get_role_name(x))
216 for x in AuthTokenModel.cls.ROLES]
214 for x in AuthTokenModel.cls.ROLES]
217 c.role_options = [(c.role_values, _("Role"))]
215 c.role_options = [(c.role_values, _("Role"))]
218 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
216 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
219 c.user.user_id, show_expired=True)
217 c.user.user_id, show_expired=True)
220 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
218 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
221 return self._get_template_context(c)
219 return self._get_template_context(c)
222
220
223 @LoginRequired()
221 @LoginRequired()
224 @NotAnonymous()
222 @NotAnonymous()
225 @CSRFRequired()
223 @CSRFRequired()
226 def my_account_auth_tokens_view(self):
224 def my_account_auth_tokens_view(self):
227 _ = self.request.translate
225 _ = self.request.translate
228 c = self.load_default_context()
226 c = self.load_default_context()
229
227
230 auth_token_id = self.request.POST.get('auth_token_id')
228 auth_token_id = self.request.POST.get('auth_token_id')
231
229
232 if auth_token_id:
230 if auth_token_id:
233 token = UserApiKeys.get_or_404(auth_token_id)
231 token = UserApiKeys.get_or_404(auth_token_id)
234 if token.user.user_id != c.user.user_id:
232 if token.user.user_id != c.user.user_id:
235 raise HTTPNotFound()
233 raise HTTPNotFound()
236
234
237 return {
235 return {
238 'auth_token': token.api_key
236 'auth_token': token.api_key
239 }
237 }
240
238
241 def maybe_attach_token_scope(self, token):
239 def maybe_attach_token_scope(self, token):
242 # implemented in EE edition
240 # implemented in EE edition
243 pass
241 pass
244
242
245 @LoginRequired()
243 @LoginRequired()
246 @NotAnonymous()
244 @NotAnonymous()
247 @CSRFRequired()
245 @CSRFRequired()
248 def my_account_auth_tokens_add(self):
246 def my_account_auth_tokens_add(self):
249 _ = self.request.translate
247 _ = self.request.translate
250 c = self.load_default_context()
248 c = self.load_default_context()
251
249
252 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
250 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
253 description = self.request.POST.get('description')
251 description = self.request.POST.get('description')
254 role = self.request.POST.get('role')
252 role = self.request.POST.get('role')
255
253
256 token = UserModel().add_auth_token(
254 token = UserModel().add_auth_token(
257 user=c.user.user_id,
255 user=c.user.user_id,
258 lifetime_minutes=lifetime, role=role, description=description,
256 lifetime_minutes=lifetime, role=role, description=description,
259 scope_callback=self.maybe_attach_token_scope)
257 scope_callback=self.maybe_attach_token_scope)
260 token_data = token.get_api_data()
258 token_data = token.get_api_data()
261
259
262 audit_logger.store_web(
260 audit_logger.store_web(
263 'user.edit.token.add', action_data={
261 'user.edit.token.add', action_data={
264 'data': {'token': token_data, 'user': 'self'}},
262 'data': {'token': token_data, 'user': 'self'}},
265 user=self._rhodecode_user, )
263 user=self._rhodecode_user, )
266 Session().commit()
264 Session().commit()
267
265
268 h.flash(_("Auth token successfully created"), category='success')
266 h.flash(_("Auth token successfully created"), category='success')
269 return HTTPFound(h.route_path('my_account_auth_tokens'))
267 return HTTPFound(h.route_path('my_account_auth_tokens'))
270
268
271 @LoginRequired()
269 @LoginRequired()
272 @NotAnonymous()
270 @NotAnonymous()
273 @CSRFRequired()
271 @CSRFRequired()
274 def my_account_auth_tokens_delete(self):
272 def my_account_auth_tokens_delete(self):
275 _ = self.request.translate
273 _ = self.request.translate
276 c = self.load_default_context()
274 c = self.load_default_context()
277
275
278 del_auth_token = self.request.POST.get('del_auth_token')
276 del_auth_token = self.request.POST.get('del_auth_token')
279
277
280 if del_auth_token:
278 if del_auth_token:
281 token = UserApiKeys.get_or_404(del_auth_token)
279 token = UserApiKeys.get_or_404(del_auth_token)
282 token_data = token.get_api_data()
280 token_data = token.get_api_data()
283
281
284 AuthTokenModel().delete(del_auth_token, c.user.user_id)
282 AuthTokenModel().delete(del_auth_token, c.user.user_id)
285 audit_logger.store_web(
283 audit_logger.store_web(
286 'user.edit.token.delete', action_data={
284 'user.edit.token.delete', action_data={
287 'data': {'token': token_data, 'user': 'self'}},
285 'data': {'token': token_data, 'user': 'self'}},
288 user=self._rhodecode_user,)
286 user=self._rhodecode_user,)
289 Session().commit()
287 Session().commit()
290 h.flash(_("Auth token successfully deleted"), category='success')
288 h.flash(_("Auth token successfully deleted"), category='success')
291
289
292 return HTTPFound(h.route_path('my_account_auth_tokens'))
290 return HTTPFound(h.route_path('my_account_auth_tokens'))
293
291
294 @LoginRequired()
292 @LoginRequired()
295 @NotAnonymous()
293 @NotAnonymous()
296 def my_account_emails(self):
294 def my_account_emails(self):
297 _ = self.request.translate
295 _ = self.request.translate
298
296
299 c = self.load_default_context()
297 c = self.load_default_context()
300 c.active = 'emails'
298 c.active = 'emails'
301
299
302 c.user_email_map = UserEmailMap.query()\
300 c.user_email_map = UserEmailMap.query()\
303 .filter(UserEmailMap.user == c.user).all()
301 .filter(UserEmailMap.user == c.user).all()
304
302
305 schema = user_schema.AddEmailSchema().bind(
303 schema = user_schema.AddEmailSchema().bind(
306 username=c.user.username, user_emails=c.user.emails)
304 username=c.user.username, user_emails=c.user.emails)
307
305
308 form = forms.RcForm(schema,
306 form = forms.RcForm(schema,
309 action=h.route_path('my_account_emails_add'),
307 action=h.route_path('my_account_emails_add'),
310 buttons=(forms.buttons.save, forms.buttons.reset))
308 buttons=(forms.buttons.save, forms.buttons.reset))
311
309
312 c.form = form
310 c.form = form
313 return self._get_template_context(c)
311 return self._get_template_context(c)
314
312
315 @LoginRequired()
313 @LoginRequired()
316 @NotAnonymous()
314 @NotAnonymous()
317 @CSRFRequired()
315 @CSRFRequired()
318 def my_account_emails_add(self):
316 def my_account_emails_add(self):
319 _ = self.request.translate
317 _ = self.request.translate
320 c = self.load_default_context()
318 c = self.load_default_context()
321 c.active = 'emails'
319 c.active = 'emails'
322
320
323 schema = user_schema.AddEmailSchema().bind(
321 schema = user_schema.AddEmailSchema().bind(
324 username=c.user.username, user_emails=c.user.emails)
322 username=c.user.username, user_emails=c.user.emails)
325
323
326 form = forms.RcForm(
324 form = forms.RcForm(
327 schema, action=h.route_path('my_account_emails_add'),
325 schema, action=h.route_path('my_account_emails_add'),
328 buttons=(forms.buttons.save, forms.buttons.reset))
326 buttons=(forms.buttons.save, forms.buttons.reset))
329
327
330 controls = list(self.request.POST.items())
328 controls = list(self.request.POST.items())
331 try:
329 try:
332 valid_data = form.validate(controls)
330 valid_data = form.validate(controls)
333 UserModel().add_extra_email(c.user.user_id, valid_data['email'])
331 UserModel().add_extra_email(c.user.user_id, valid_data['email'])
334 audit_logger.store_web(
332 audit_logger.store_web(
335 'user.edit.email.add', action_data={
333 'user.edit.email.add', action_data={
336 'data': {'email': valid_data['email'], 'user': 'self'}},
334 'data': {'email': valid_data['email'], 'user': 'self'}},
337 user=self._rhodecode_user,)
335 user=self._rhodecode_user,)
338 Session().commit()
336 Session().commit()
339 except formencode.Invalid as error:
337 except formencode.Invalid as error:
340 h.flash(h.escape(error.error_dict['email']), category='error')
338 h.flash(h.escape(error.error_dict['email']), category='error')
341 except forms.ValidationFailure as e:
339 except forms.ValidationFailure as e:
342 c.user_email_map = UserEmailMap.query() \
340 c.user_email_map = UserEmailMap.query() \
343 .filter(UserEmailMap.user == c.user).all()
341 .filter(UserEmailMap.user == c.user).all()
344 c.form = e
342 c.form = e
345 return self._get_template_context(c)
343 return self._get_template_context(c)
346 except Exception:
344 except Exception:
347 log.exception("Exception adding email")
345 log.exception("Exception adding email")
348 h.flash(_('Error occurred during adding email'),
346 h.flash(_('Error occurred during adding email'),
349 category='error')
347 category='error')
350 else:
348 else:
351 h.flash(_("Successfully added email"), category='success')
349 h.flash(_("Successfully added email"), category='success')
352
350
353 raise HTTPFound(self.request.route_path('my_account_emails'))
351 raise HTTPFound(self.request.route_path('my_account_emails'))
354
352
355 @LoginRequired()
353 @LoginRequired()
356 @NotAnonymous()
354 @NotAnonymous()
357 @CSRFRequired()
355 @CSRFRequired()
358 def my_account_emails_delete(self):
356 def my_account_emails_delete(self):
359 _ = self.request.translate
357 _ = self.request.translate
360 c = self.load_default_context()
358 c = self.load_default_context()
361
359
362 del_email_id = self.request.POST.get('del_email_id')
360 del_email_id = self.request.POST.get('del_email_id')
363 if del_email_id:
361 if del_email_id:
364 email = UserEmailMap.get_or_404(del_email_id).email
362 email = UserEmailMap.get_or_404(del_email_id).email
365 UserModel().delete_extra_email(c.user.user_id, del_email_id)
363 UserModel().delete_extra_email(c.user.user_id, del_email_id)
366 audit_logger.store_web(
364 audit_logger.store_web(
367 'user.edit.email.delete', action_data={
365 'user.edit.email.delete', action_data={
368 'data': {'email': email, 'user': 'self'}},
366 'data': {'email': email, 'user': 'self'}},
369 user=self._rhodecode_user,)
367 user=self._rhodecode_user,)
370 Session().commit()
368 Session().commit()
371 h.flash(_("Email successfully deleted"),
369 h.flash(_("Email successfully deleted"),
372 category='success')
370 category='success')
373 return HTTPFound(h.route_path('my_account_emails'))
371 return HTTPFound(h.route_path('my_account_emails'))
374
372
375 @LoginRequired()
373 @LoginRequired()
376 @NotAnonymous()
374 @NotAnonymous()
377 @CSRFRequired()
375 @CSRFRequired()
378 def my_account_notifications_test_channelstream(self):
376 def my_account_notifications_test_channelstream(self):
379 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
377 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
380 self._rhodecode_user.username, datetime.datetime.now())
378 self._rhodecode_user.username, datetime.datetime.now())
381 payload = {
379 payload = {
382 # 'channel': 'broadcast',
380 # 'channel': 'broadcast',
383 'type': 'message',
381 'type': 'message',
384 'timestamp': datetime.datetime.utcnow(),
382 'timestamp': datetime.datetime.utcnow(),
385 'user': 'system',
383 'user': 'system',
386 'pm_users': [self._rhodecode_user.username],
384 'pm_users': [self._rhodecode_user.username],
387 'message': {
385 'message': {
388 'message': message,
386 'message': message,
389 'level': 'info',
387 'level': 'info',
390 'topic': '/notifications'
388 'topic': '/notifications'
391 }
389 }
392 }
390 }
393
391
394 registry = self.request.registry
392 registry = self.request.registry
395 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
393 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
396 channelstream_config = rhodecode_plugins.get('channelstream', {})
394 channelstream_config = rhodecode_plugins.get('channelstream', {})
397
395
398 try:
396 try:
399 channelstream_request(channelstream_config, [payload], '/message')
397 channelstream_request(channelstream_config, [payload], '/message')
400 except ChannelstreamException as e:
398 except ChannelstreamException as e:
401 log.exception('Failed to send channelstream data')
399 log.exception('Failed to send channelstream data')
402 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
400 return {"response": f'ERROR: {e.__class__.__name__}'}
403 return {"response": 'Channelstream data sent. '
401 return {"response": 'Channelstream data sent. '
404 'You should see a new live message now.'}
402 'You should see a new live message now.'}
405
403
406 def _load_my_repos_data(self, watched=False):
404 def _load_my_repos_data(self, watched=False):
407
405
408 allowed_ids = [-1] + self._rhodecode_user.repo_acl_ids_from_stack(AuthUser.repo_read_perms)
406 allowed_ids = [-1] + self._rhodecode_user.repo_acl_ids_from_stack(AuthUser.repo_read_perms)
409
407
410 if watched:
408 if watched:
411 # repos user watch
409 # repos user watch
412 repo_list = Session().query(
410 repo_list = Session().query(
413 Repository
411 Repository
414 ) \
412 ) \
415 .join(
413 .join(
416 (UserFollowing, UserFollowing.follows_repo_id == Repository.repo_id)
414 (UserFollowing, UserFollowing.follows_repo_id == Repository.repo_id)
417 ) \
415 ) \
418 .filter(
416 .filter(
419 UserFollowing.user_id == self._rhodecode_user.user_id
417 UserFollowing.user_id == self._rhodecode_user.user_id
420 ) \
418 ) \
421 .filter(or_(
419 .filter(or_(
422 # generate multiple IN to fix limitation problems
420 # generate multiple IN to fix limitation problems
423 *in_filter_generator(Repository.repo_id, allowed_ids))
421 *in_filter_generator(Repository.repo_id, allowed_ids))
424 ) \
422 ) \
425 .order_by(Repository.repo_name) \
423 .order_by(Repository.repo_name) \
426 .all()
424 .all()
427
425
428 else:
426 else:
429 # repos user is owner of
427 # repos user is owner of
430 repo_list = Session().query(
428 repo_list = Session().query(
431 Repository
429 Repository
432 ) \
430 ) \
433 .filter(
431 .filter(
434 Repository.user_id == self._rhodecode_user.user_id
432 Repository.user_id == self._rhodecode_user.user_id
435 ) \
433 ) \
436 .filter(or_(
434 .filter(or_(
437 # generate multiple IN to fix limitation problems
435 # generate multiple IN to fix limitation problems
438 *in_filter_generator(Repository.repo_id, allowed_ids))
436 *in_filter_generator(Repository.repo_id, allowed_ids))
439 ) \
437 ) \
440 .order_by(Repository.repo_name) \
438 .order_by(Repository.repo_name) \
441 .all()
439 .all()
442
440
443 _render = self.request.get_partial_renderer(
441 _render = self.request.get_partial_renderer(
444 'rhodecode:templates/data_table/_dt_elements.mako')
442 'rhodecode:templates/data_table/_dt_elements.mako')
445
443
446 def repo_lnk(name, rtype, rstate, private, archived, fork_of):
444 def repo_lnk(name, rtype, rstate, private, archived, fork_of):
447 return _render('repo_name', name, rtype, rstate, private, archived, fork_of,
445 return _render('repo_name', name, rtype, rstate, private, archived, fork_of,
448 short_name=False, admin=False)
446 short_name=False, admin=False)
449
447
450 repos_data = []
448 repos_data = []
451 for repo in repo_list:
449 for repo in repo_list:
452 row = {
450 row = {
453 "name": repo_lnk(repo.repo_name, repo.repo_type, repo.repo_state,
451 "name": repo_lnk(repo.repo_name, repo.repo_type, repo.repo_state,
454 repo.private, repo.archived, repo.fork),
452 repo.private, repo.archived, repo.fork),
455 "name_raw": repo.repo_name.lower(),
453 "name_raw": repo.repo_name.lower(),
456 }
454 }
457
455
458 repos_data.append(row)
456 repos_data.append(row)
459
457
460 # json used to render the grid
458 # json used to render the grid
461 return ext_json.str_json(repos_data)
459 return ext_json.str_json(repos_data)
462
460
463 @LoginRequired()
461 @LoginRequired()
464 @NotAnonymous()
462 @NotAnonymous()
465 def my_account_repos(self):
463 def my_account_repos(self):
466 c = self.load_default_context()
464 c = self.load_default_context()
467 c.active = 'repos'
465 c.active = 'repos'
468
466
469 # json used to render the grid
467 # json used to render the grid
470 c.data = self._load_my_repos_data()
468 c.data = self._load_my_repos_data()
471 return self._get_template_context(c)
469 return self._get_template_context(c)
472
470
473 @LoginRequired()
471 @LoginRequired()
474 @NotAnonymous()
472 @NotAnonymous()
475 def my_account_watched(self):
473 def my_account_watched(self):
476 c = self.load_default_context()
474 c = self.load_default_context()
477 c.active = 'watched'
475 c.active = 'watched'
478
476
479 # json used to render the grid
477 # json used to render the grid
480 c.data = self._load_my_repos_data(watched=True)
478 c.data = self._load_my_repos_data(watched=True)
481 return self._get_template_context(c)
479 return self._get_template_context(c)
482
480
483 @LoginRequired()
481 @LoginRequired()
484 @NotAnonymous()
482 @NotAnonymous()
485 def my_account_bookmarks(self):
483 def my_account_bookmarks(self):
486 c = self.load_default_context()
484 c = self.load_default_context()
487 c.active = 'bookmarks'
485 c.active = 'bookmarks'
488 c.bookmark_items = UserBookmark.get_bookmarks_for_user(
486 c.bookmark_items = UserBookmark.get_bookmarks_for_user(
489 self._rhodecode_db_user.user_id, cache=False)
487 self._rhodecode_db_user.user_id, cache=False)
490 return self._get_template_context(c)
488 return self._get_template_context(c)
491
489
492 def _process_bookmark_entry(self, entry, user_id):
490 def _process_bookmark_entry(self, entry, user_id):
493 position = safe_int(entry.get('position'))
491 position = safe_int(entry.get('position'))
494 cur_position = safe_int(entry.get('cur_position'))
492 cur_position = safe_int(entry.get('cur_position'))
495 if position is None:
493 if position is None:
496 return
494 return
497
495
498 # check if this is an existing entry
496 # check if this is an existing entry
499 is_new = False
497 is_new = False
500 db_entry = UserBookmark().get_by_position_for_user(cur_position, user_id)
498 db_entry = UserBookmark().get_by_position_for_user(cur_position, user_id)
501
499
502 if db_entry and str2bool(entry.get('remove')):
500 if db_entry and str2bool(entry.get('remove')):
503 log.debug('Marked bookmark %s for deletion', db_entry)
501 log.debug('Marked bookmark %s for deletion', db_entry)
504 Session().delete(db_entry)
502 Session().delete(db_entry)
505 return
503 return
506
504
507 if not db_entry:
505 if not db_entry:
508 # new
506 # new
509 db_entry = UserBookmark()
507 db_entry = UserBookmark()
510 is_new = True
508 is_new = True
511
509
512 should_save = False
510 should_save = False
513 default_redirect_url = ''
511 default_redirect_url = ''
514
512
515 # save repo
513 # save repo
516 if entry.get('bookmark_repo') and safe_int(entry.get('bookmark_repo')):
514 if entry.get('bookmark_repo') and safe_int(entry.get('bookmark_repo')):
517 repo = Repository.get(entry['bookmark_repo'])
515 repo = Repository.get(entry['bookmark_repo'])
518 perm_check = HasRepoPermissionAny(
516 perm_check = HasRepoPermissionAny(
519 'repository.read', 'repository.write', 'repository.admin')
517 'repository.read', 'repository.write', 'repository.admin')
520 if repo and perm_check(repo_name=repo.repo_name):
518 if repo and perm_check(repo_name=repo.repo_name):
521 db_entry.repository = repo
519 db_entry.repository = repo
522 should_save = True
520 should_save = True
523 default_redirect_url = '${repo_url}'
521 default_redirect_url = '${repo_url}'
524 # save repo group
522 # save repo group
525 elif entry.get('bookmark_repo_group') and safe_int(entry.get('bookmark_repo_group')):
523 elif entry.get('bookmark_repo_group') and safe_int(entry.get('bookmark_repo_group')):
526 repo_group = RepoGroup.get(entry['bookmark_repo_group'])
524 repo_group = RepoGroup.get(entry['bookmark_repo_group'])
527 perm_check = HasRepoGroupPermissionAny(
525 perm_check = HasRepoGroupPermissionAny(
528 'group.read', 'group.write', 'group.admin')
526 'group.read', 'group.write', 'group.admin')
529
527
530 if repo_group and perm_check(group_name=repo_group.group_name):
528 if repo_group and perm_check(group_name=repo_group.group_name):
531 db_entry.repository_group = repo_group
529 db_entry.repository_group = repo_group
532 should_save = True
530 should_save = True
533 default_redirect_url = '${repo_group_url}'
531 default_redirect_url = '${repo_group_url}'
534 # save generic info
532 # save generic info
535 elif entry.get('title') and entry.get('redirect_url'):
533 elif entry.get('title') and entry.get('redirect_url'):
536 should_save = True
534 should_save = True
537
535
538 if should_save:
536 if should_save:
539 # mark user and position
537 # mark user and position
540 db_entry.user_id = user_id
538 db_entry.user_id = user_id
541 db_entry.position = position
539 db_entry.position = position
542 db_entry.title = entry.get('title')
540 db_entry.title = entry.get('title')
543 db_entry.redirect_url = entry.get('redirect_url') or default_redirect_url
541 db_entry.redirect_url = entry.get('redirect_url') or default_redirect_url
544 log.debug('Saving bookmark %s, new:%s', db_entry, is_new)
542 log.debug('Saving bookmark %s, new:%s', db_entry, is_new)
545
543
546 Session().add(db_entry)
544 Session().add(db_entry)
547
545
548 @LoginRequired()
546 @LoginRequired()
549 @NotAnonymous()
547 @NotAnonymous()
550 @CSRFRequired()
548 @CSRFRequired()
551 def my_account_bookmarks_update(self):
549 def my_account_bookmarks_update(self):
552 _ = self.request.translate
550 _ = self.request.translate
553 c = self.load_default_context()
551 c = self.load_default_context()
554 c.active = 'bookmarks'
552 c.active = 'bookmarks'
555
553
556 controls = peppercorn.parse(self.request.POST.items())
554 controls = peppercorn.parse(self.request.POST.items())
557 user_id = c.user.user_id
555 user_id = c.user.user_id
558
556
559 # validate positions
557 # validate positions
560 positions = {}
558 positions = {}
561 for entry in controls.get('bookmarks', []):
559 for entry in controls.get('bookmarks', []):
562 position = safe_int(entry['position'])
560 position = safe_int(entry['position'])
563 if position is None:
561 if position is None:
564 continue
562 continue
565
563
566 if position in positions:
564 if position in positions:
567 h.flash(_("Position {} is defined twice. "
565 h.flash(_("Position {} is defined twice. "
568 "Please correct this error.").format(position), category='error')
566 "Please correct this error.").format(position), category='error')
569 return HTTPFound(h.route_path('my_account_bookmarks'))
567 return HTTPFound(h.route_path('my_account_bookmarks'))
570
568
571 entry['position'] = position
569 entry['position'] = position
572 entry['cur_position'] = safe_int(entry.get('cur_position'))
570 entry['cur_position'] = safe_int(entry.get('cur_position'))
573 positions[position] = entry
571 positions[position] = entry
574
572
575 try:
573 try:
576 for entry in positions.values():
574 for entry in positions.values():
577 self._process_bookmark_entry(entry, user_id)
575 self._process_bookmark_entry(entry, user_id)
578
576
579 Session().commit()
577 Session().commit()
580 h.flash(_("Update Bookmarks"), category='success')
578 h.flash(_("Update Bookmarks"), category='success')
581 except IntegrityError:
579 except IntegrityError:
582 h.flash(_("Failed to update bookmarks. "
580 h.flash(_("Failed to update bookmarks. "
583 "Make sure an unique position is used."), category='error')
581 "Make sure an unique position is used."), category='error')
584
582
585 return HTTPFound(h.route_path('my_account_bookmarks'))
583 return HTTPFound(h.route_path('my_account_bookmarks'))
586
584
587 @LoginRequired()
585 @LoginRequired()
588 @NotAnonymous()
586 @NotAnonymous()
589 def my_account_goto_bookmark(self):
587 def my_account_goto_bookmark(self):
590
588
591 bookmark_id = self.request.matchdict['bookmark_id']
589 bookmark_id = self.request.matchdict['bookmark_id']
592 user_bookmark = UserBookmark().query()\
590 user_bookmark = UserBookmark().query()\
593 .filter(UserBookmark.user_id == self.request.user.user_id) \
591 .filter(UserBookmark.user_id == self.request.user.user_id) \
594 .filter(UserBookmark.position == bookmark_id).scalar()
592 .filter(UserBookmark.position == bookmark_id).scalar()
595
593
596 redirect_url = h.route_path('my_account_bookmarks')
594 redirect_url = h.route_path('my_account_bookmarks')
597 if not user_bookmark:
595 if not user_bookmark:
598 raise HTTPFound(redirect_url)
596 raise HTTPFound(redirect_url)
599
597
600 # repository set
598 # repository set
601 if user_bookmark.repository:
599 if user_bookmark.repository:
602 repo_name = user_bookmark.repository.repo_name
600 repo_name = user_bookmark.repository.repo_name
603 base_redirect_url = h.route_path(
601 base_redirect_url = h.route_path(
604 'repo_summary', repo_name=repo_name)
602 'repo_summary', repo_name=repo_name)
605 if user_bookmark.redirect_url and \
603 if user_bookmark.redirect_url and \
606 '${repo_url}' in user_bookmark.redirect_url:
604 '${repo_url}' in user_bookmark.redirect_url:
607 redirect_url = string.Template(user_bookmark.redirect_url)\
605 redirect_url = string.Template(user_bookmark.redirect_url)\
608 .safe_substitute({'repo_url': base_redirect_url})
606 .safe_substitute({'repo_url': base_redirect_url})
609 else:
607 else:
610 redirect_url = base_redirect_url
608 redirect_url = base_redirect_url
611 # repository group set
609 # repository group set
612 elif user_bookmark.repository_group:
610 elif user_bookmark.repository_group:
613 repo_group_name = user_bookmark.repository_group.group_name
611 repo_group_name = user_bookmark.repository_group.group_name
614 base_redirect_url = h.route_path(
612 base_redirect_url = h.route_path(
615 'repo_group_home', repo_group_name=repo_group_name)
613 'repo_group_home', repo_group_name=repo_group_name)
616 if user_bookmark.redirect_url and \
614 if user_bookmark.redirect_url and \
617 '${repo_group_url}' in user_bookmark.redirect_url:
615 '${repo_group_url}' in user_bookmark.redirect_url:
618 redirect_url = string.Template(user_bookmark.redirect_url)\
616 redirect_url = string.Template(user_bookmark.redirect_url)\
619 .safe_substitute({'repo_group_url': base_redirect_url})
617 .safe_substitute({'repo_group_url': base_redirect_url})
620 else:
618 else:
621 redirect_url = base_redirect_url
619 redirect_url = base_redirect_url
622 # custom URL set
620 # custom URL set
623 elif user_bookmark.redirect_url:
621 elif user_bookmark.redirect_url:
624 server_url = h.route_url('home').rstrip('/')
622 server_url = h.route_url('home').rstrip('/')
625 redirect_url = string.Template(user_bookmark.redirect_url) \
623 redirect_url = string.Template(user_bookmark.redirect_url) \
626 .safe_substitute({'server_url': server_url})
624 .safe_substitute({'server_url': server_url})
627
625
628 log.debug('Redirecting bookmark %s to %s', user_bookmark, redirect_url)
626 log.debug('Redirecting bookmark %s to %s', user_bookmark, redirect_url)
629 raise HTTPFound(redirect_url)
627 raise HTTPFound(redirect_url)
630
628
631 @LoginRequired()
629 @LoginRequired()
632 @NotAnonymous()
630 @NotAnonymous()
633 def my_account_perms(self):
631 def my_account_perms(self):
634 c = self.load_default_context()
632 c = self.load_default_context()
635 c.active = 'perms'
633 c.active = 'perms'
636
634
637 c.perm_user = c.auth_user
635 c.perm_user = c.auth_user
638 return self._get_template_context(c)
636 return self._get_template_context(c)
639
637
640 @LoginRequired()
638 @LoginRequired()
641 @NotAnonymous()
639 @NotAnonymous()
642 def my_notifications(self):
640 def my_notifications(self):
643 c = self.load_default_context()
641 c = self.load_default_context()
644 c.active = 'notifications'
642 c.active = 'notifications'
645
643
646 return self._get_template_context(c)
644 return self._get_template_context(c)
647
645
648 @LoginRequired()
646 @LoginRequired()
649 @NotAnonymous()
647 @NotAnonymous()
650 @CSRFRequired()
648 @CSRFRequired()
651 def my_notifications_toggle_visibility(self):
649 def my_notifications_toggle_visibility(self):
652 user = self._rhodecode_db_user
650 user = self._rhodecode_db_user
653 new_status = not user.user_data.get('notification_status', True)
651 new_status = not user.user_data.get('notification_status', True)
654 user.update_userdata(notification_status=new_status)
652 user.update_userdata(notification_status=new_status)
655 Session().commit()
653 Session().commit()
656 return user.user_data['notification_status']
654 return user.user_data['notification_status']
657
655
658 def _get_pull_requests_list(self, statuses, filter_type=None):
656 def _get_pull_requests_list(self, statuses, filter_type=None):
659 draw, start, limit = self._extract_chunk(self.request)
657 draw, start, limit = self._extract_chunk(self.request)
660 search_q, order_by, order_dir = self._extract_ordering(self.request)
658 search_q, order_by, order_dir = self._extract_ordering(self.request)
661
659
662 _render = self.request.get_partial_renderer(
660 _render = self.request.get_partial_renderer(
663 'rhodecode:templates/data_table/_dt_elements.mako')
661 'rhodecode:templates/data_table/_dt_elements.mako')
664
662
665 if filter_type == 'awaiting_my_review':
663 if filter_type == 'awaiting_my_review':
666 pull_requests = PullRequestModel().get_im_participating_in_for_review(
664 pull_requests = PullRequestModel().get_im_participating_in_for_review(
667 user_id=self._rhodecode_user.user_id,
665 user_id=self._rhodecode_user.user_id,
668 statuses=statuses, query=search_q,
666 statuses=statuses, query=search_q,
669 offset=start, length=limit, order_by=order_by,
667 offset=start, length=limit, order_by=order_by,
670 order_dir=order_dir)
668 order_dir=order_dir)
671
669
672 pull_requests_total_count = PullRequestModel().count_im_participating_in_for_review(
670 pull_requests_total_count = PullRequestModel().count_im_participating_in_for_review(
673 user_id=self._rhodecode_user.user_id, statuses=statuses, query=search_q)
671 user_id=self._rhodecode_user.user_id, statuses=statuses, query=search_q)
674 else:
672 else:
675 pull_requests = PullRequestModel().get_im_participating_in(
673 pull_requests = PullRequestModel().get_im_participating_in(
676 user_id=self._rhodecode_user.user_id,
674 user_id=self._rhodecode_user.user_id,
677 statuses=statuses, query=search_q,
675 statuses=statuses, query=search_q,
678 offset=start, length=limit, order_by=order_by,
676 offset=start, length=limit, order_by=order_by,
679 order_dir=order_dir)
677 order_dir=order_dir)
680
678
681 pull_requests_total_count = PullRequestModel().count_im_participating_in(
679 pull_requests_total_count = PullRequestModel().count_im_participating_in(
682 user_id=self._rhodecode_user.user_id, statuses=statuses, query=search_q)
680 user_id=self._rhodecode_user.user_id, statuses=statuses, query=search_q)
683
681
684 data = []
682 data = []
685 comments_model = CommentsModel()
683 comments_model = CommentsModel()
686 for pr in pull_requests:
684 for pr in pull_requests:
687 repo_id = pr.target_repo_id
685 repo_id = pr.target_repo_id
688 comments_count = comments_model.get_all_comments(
686 comments_count = comments_model.get_all_comments(
689 repo_id, pull_request=pr, include_drafts=False, count_only=True)
687 repo_id, pull_request=pr, include_drafts=False, count_only=True)
690 owned = pr.user_id == self._rhodecode_user.user_id
688 owned = pr.user_id == self._rhodecode_user.user_id
691
689
692 review_statuses = pr.reviewers_statuses(user=self._rhodecode_db_user)
690 review_statuses = pr.reviewers_statuses(user=self._rhodecode_db_user)
693 my_review_status = ChangesetStatus.STATUS_NOT_REVIEWED
691 my_review_status = ChangesetStatus.STATUS_NOT_REVIEWED
694 if review_statuses and review_statuses[4]:
692 if review_statuses and review_statuses[4]:
695 _review_obj, _user, _reasons, _mandatory, statuses = review_statuses
693 _review_obj, _user, _reasons, _mandatory, statuses = review_statuses
696 my_review_status = statuses[0][1].status
694 my_review_status = statuses[0][1].status
697
695
698 data.append({
696 data.append({
699 'target_repo': _render('pullrequest_target_repo',
697 'target_repo': _render('pullrequest_target_repo',
700 pr.target_repo.repo_name),
698 pr.target_repo.repo_name),
701 'name': _render('pullrequest_name',
699 'name': _render('pullrequest_name',
702 pr.pull_request_id, pr.pull_request_state,
700 pr.pull_request_id, pr.pull_request_state,
703 pr.work_in_progress, pr.target_repo.repo_name,
701 pr.work_in_progress, pr.target_repo.repo_name,
704 short=True),
702 short=True),
705 'name_raw': pr.pull_request_id,
703 'name_raw': pr.pull_request_id,
706 'status': _render('pullrequest_status',
704 'status': _render('pullrequest_status',
707 pr.calculated_review_status()),
705 pr.calculated_review_status()),
708 'my_status': _render('pullrequest_status',
706 'my_status': _render('pullrequest_status',
709 my_review_status),
707 my_review_status),
710 'title': _render('pullrequest_title', pr.title, pr.description),
708 'title': _render('pullrequest_title', pr.title, pr.description),
711 'description': h.escape(pr.description),
709 'description': h.escape(pr.description),
712 'updated_on': _render('pullrequest_updated_on',
710 'updated_on': _render('pullrequest_updated_on',
713 h.datetime_to_time(pr.updated_on),
711 h.datetime_to_time(pr.updated_on),
714 pr.versions_count),
712 pr.versions_count),
715 'updated_on_raw': h.datetime_to_time(pr.updated_on),
713 'updated_on_raw': h.datetime_to_time(pr.updated_on),
716 'created_on': _render('pullrequest_updated_on',
714 'created_on': _render('pullrequest_updated_on',
717 h.datetime_to_time(pr.created_on)),
715 h.datetime_to_time(pr.created_on)),
718 'created_on_raw': h.datetime_to_time(pr.created_on),
716 'created_on_raw': h.datetime_to_time(pr.created_on),
719 'state': pr.pull_request_state,
717 'state': pr.pull_request_state,
720 'author': _render('pullrequest_author',
718 'author': _render('pullrequest_author',
721 pr.author.full_contact, ),
719 pr.author.full_contact, ),
722 'author_raw': pr.author.full_name,
720 'author_raw': pr.author.full_name,
723 'comments': _render('pullrequest_comments', comments_count),
721 'comments': _render('pullrequest_comments', comments_count),
724 'comments_raw': comments_count,
722 'comments_raw': comments_count,
725 'closed': pr.is_closed(),
723 'closed': pr.is_closed(),
726 'owned': owned
724 'owned': owned
727 })
725 })
728
726
729 # json used to render the grid
727 # json used to render the grid
730 data = ({
728 data = ({
731 'draw': draw,
729 'draw': draw,
732 'data': data,
730 'data': data,
733 'recordsTotal': pull_requests_total_count,
731 'recordsTotal': pull_requests_total_count,
734 'recordsFiltered': pull_requests_total_count,
732 'recordsFiltered': pull_requests_total_count,
735 })
733 })
736 return data
734 return data
737
735
738 @LoginRequired()
736 @LoginRequired()
739 @NotAnonymous()
737 @NotAnonymous()
740 def my_account_pullrequests(self):
738 def my_account_pullrequests(self):
741 c = self.load_default_context()
739 c = self.load_default_context()
742 c.active = 'pullrequests'
740 c.active = 'pullrequests'
743 req_get = self.request.GET
741 req_get = self.request.GET
744
742
745 c.closed = str2bool(req_get.get('closed'))
743 c.closed = str2bool(req_get.get('closed'))
746 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
744 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
747
745
748 c.selected_filter = 'all'
746 c.selected_filter = 'all'
749 if c.closed:
747 if c.closed:
750 c.selected_filter = 'all_closed'
748 c.selected_filter = 'all_closed'
751 if c.awaiting_my_review:
749 if c.awaiting_my_review:
752 c.selected_filter = 'awaiting_my_review'
750 c.selected_filter = 'awaiting_my_review'
753
751
754 return self._get_template_context(c)
752 return self._get_template_context(c)
755
753
756 @LoginRequired()
754 @LoginRequired()
757 @NotAnonymous()
755 @NotAnonymous()
758 def my_account_pullrequests_data(self):
756 def my_account_pullrequests_data(self):
759 self.load_default_context()
757 self.load_default_context()
760 req_get = self.request.GET
758 req_get = self.request.GET
761
759
762 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
760 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
763 closed = str2bool(req_get.get('closed'))
761 closed = str2bool(req_get.get('closed'))
764
762
765 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
763 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
766 if closed:
764 if closed:
767 statuses += [PullRequest.STATUS_CLOSED]
765 statuses += [PullRequest.STATUS_CLOSED]
768
766
769 filter_type = \
767 filter_type = \
770 'awaiting_my_review' if awaiting_my_review \
768 'awaiting_my_review' if awaiting_my_review \
771 else None
769 else None
772
770
773 data = self._get_pull_requests_list(statuses=statuses, filter_type=filter_type)
771 data = self._get_pull_requests_list(statuses=statuses, filter_type=filter_type)
774 return data
772 return data
775
773
776 @LoginRequired()
774 @LoginRequired()
777 @NotAnonymous()
775 @NotAnonymous()
778 def my_account_user_group_membership(self):
776 def my_account_user_group_membership(self):
779 c = self.load_default_context()
777 c = self.load_default_context()
780 c.active = 'user_group_membership'
778 c.active = 'user_group_membership'
781 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
779 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
782 for group in self._rhodecode_db_user.group_member]
780 for group in self._rhodecode_db_user.group_member]
783 c.user_groups = ext_json.str_json(groups)
781 c.user_groups = ext_json.str_json(groups)
784 return self._get_template_context(c)
782 return self._get_template_context(c)
@@ -1,184 +1,183 b''
1
2 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
2 #
4 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
7 #
6 #
8 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
10 # GNU General Public License for more details.
12 #
11 #
13 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
14 #
16 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
18
20 import logging
19 import logging
21
20
22 from pyramid.httpexceptions import (
21 from pyramid.httpexceptions import (
23 HTTPFound, HTTPNotFound, HTTPInternalServerError)
22 HTTPFound, HTTPNotFound, HTTPInternalServerError)
24
23
25 from rhodecode.apps._base import BaseAppView
24 from rhodecode.apps._base import BaseAppView
26 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
25 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
27
26
28 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
29 from rhodecode.lib.helpers import SqlPage
28 from rhodecode.lib.helpers import SqlPage
30 from rhodecode.lib.utils2 import safe_int
29 from rhodecode.lib.utils2 import safe_int
31 from rhodecode.model.db import Notification
30 from rhodecode.model.db import Notification
32 from rhodecode.model.notification import NotificationModel
31 from rhodecode.model.notification import NotificationModel
33 from rhodecode.model.meta import Session
32 from rhodecode.model.meta import Session
34
33
35
34
36 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
37
36
38
37
39 class MyAccountNotificationsView(BaseAppView):
38 class MyAccountNotificationsView(BaseAppView):
40
39
41 def load_default_context(self):
40 def load_default_context(self):
42 c = self._get_local_tmpl_context()
41 c = self._get_local_tmpl_context()
43 c.user = c.auth_user.get_instance()
42 c.user = c.auth_user.get_instance()
44
43
45 return c
44 return c
46
45
47 def _has_permissions(self, notification):
46 def _has_permissions(self, notification):
48 def is_owner():
47 def is_owner():
49 user_id = self._rhodecode_db_user.user_id
48 user_id = self._rhodecode_db_user.user_id
50 for user_notification in notification.notifications_to_users:
49 for user_notification in notification.notifications_to_users:
51 if user_notification.user.user_id == user_id:
50 if user_notification.user.user_id == user_id:
52 return True
51 return True
53 return False
52 return False
54 return h.HasPermissionAny('hg.admin')() or is_owner()
53 return h.HasPermissionAny('hg.admin')() or is_owner()
55
54
56 @LoginRequired()
55 @LoginRequired()
57 @NotAnonymous()
56 @NotAnonymous()
58 def notifications_show_all(self):
57 def notifications_show_all(self):
59 c = self.load_default_context()
58 c = self.load_default_context()
60
59
61 c.unread_count = NotificationModel().get_unread_cnt_for_user(
60 c.unread_count = NotificationModel().get_unread_cnt_for_user(
62 self._rhodecode_db_user.user_id)
61 self._rhodecode_db_user.user_id)
63
62
64 _current_filter = self.request.GET.getall('type') or ['unread']
63 _current_filter = self.request.GET.getall('type') or ['unread']
65
64
66 notifications = NotificationModel().get_for_user(
65 notifications = NotificationModel().get_for_user(
67 self._rhodecode_db_user.user_id,
66 self._rhodecode_db_user.user_id,
68 filter_=_current_filter)
67 filter_=_current_filter)
69
68
70 p = safe_int(self.request.GET.get('page', 1), 1)
69 p = safe_int(self.request.GET.get('page', 1), 1)
71
70
72 def url_generator(page_num):
71 def url_generator(page_num):
73 query_params = {
72 query_params = {
74 'page': page_num
73 'page': page_num
75 }
74 }
76 _query = self.request.GET.mixed()
75 _query = self.request.GET.mixed()
77 query_params.update(_query)
76 query_params.update(_query)
78 return self.request.current_route_path(_query=query_params)
77 return self.request.current_route_path(_query=query_params)
79
78
80 c.notifications = SqlPage(notifications, page=p, items_per_page=10,
79 c.notifications = SqlPage(notifications, page=p, items_per_page=10,
81 url_maker=url_generator)
80 url_maker=url_generator)
82
81
83 c.unread_type = 'unread'
82 c.unread_type = 'unread'
84 c.all_type = 'all'
83 c.all_type = 'all'
85 c.pull_request_type = Notification.TYPE_PULL_REQUEST
84 c.pull_request_type = Notification.TYPE_PULL_REQUEST
86 c.comment_type = [Notification.TYPE_CHANGESET_COMMENT,
85 c.comment_type = [Notification.TYPE_CHANGESET_COMMENT,
87 Notification.TYPE_PULL_REQUEST_COMMENT]
86 Notification.TYPE_PULL_REQUEST_COMMENT]
88
87
89 c.current_filter = 'unread' # default filter
88 c.current_filter = 'unread' # default filter
90
89
91 if _current_filter == [c.pull_request_type]:
90 if _current_filter == [c.pull_request_type]:
92 c.current_filter = 'pull_request'
91 c.current_filter = 'pull_request'
93 elif _current_filter == c.comment_type:
92 elif _current_filter == c.comment_type:
94 c.current_filter = 'comment'
93 c.current_filter = 'comment'
95 elif _current_filter == [c.unread_type]:
94 elif _current_filter == [c.unread_type]:
96 c.current_filter = 'unread'
95 c.current_filter = 'unread'
97 elif _current_filter == [c.all_type]:
96 elif _current_filter == [c.all_type]:
98 c.current_filter = 'all'
97 c.current_filter = 'all'
99 return self._get_template_context(c)
98 return self._get_template_context(c)
100
99
101 @LoginRequired()
100 @LoginRequired()
102 @NotAnonymous()
101 @NotAnonymous()
103 def notifications_show(self):
102 def notifications_show(self):
104 c = self.load_default_context()
103 c = self.load_default_context()
105 notification_id = self.request.matchdict['notification_id']
104 notification_id = self.request.matchdict['notification_id']
106 notification = Notification.get_or_404(notification_id)
105 notification = Notification.get_or_404(notification_id)
107
106
108 if not self._has_permissions(notification):
107 if not self._has_permissions(notification):
109 log.debug('User %s does not have permission to access notification',
108 log.debug('User %s does not have permission to access notification',
110 self._rhodecode_user)
109 self._rhodecode_user)
111 raise HTTPNotFound()
110 raise HTTPNotFound()
112
111
113 u_notification = NotificationModel().get_user_notification(
112 u_notification = NotificationModel().get_user_notification(
114 self._rhodecode_db_user.user_id, notification)
113 self._rhodecode_db_user.user_id, notification)
115 if not u_notification:
114 if not u_notification:
116 log.debug('User %s notification does not exist',
115 log.debug('User %s notification does not exist',
117 self._rhodecode_user)
116 self._rhodecode_user)
118 raise HTTPNotFound()
117 raise HTTPNotFound()
119
118
120 # when opening this notification, mark it as read for this use
119 # when opening this notification, mark it as read for this use
121 if not u_notification.read:
120 if not u_notification.read:
122 u_notification.mark_as_read()
121 u_notification.mark_as_read()
123 Session().commit()
122 Session().commit()
124
123
125 c.notification = notification
124 c.notification = notification
126
125
127 return self._get_template_context(c)
126 return self._get_template_context(c)
128
127
129 @LoginRequired()
128 @LoginRequired()
130 @NotAnonymous()
129 @NotAnonymous()
131 @CSRFRequired()
130 @CSRFRequired()
132 def notifications_mark_all_read(self):
131 def notifications_mark_all_read(self):
133 NotificationModel().mark_all_read_for_user(
132 NotificationModel().mark_all_read_for_user(
134 self._rhodecode_db_user.user_id,
133 self._rhodecode_db_user.user_id,
135 filter_=self.request.GET.getall('type'))
134 filter_=self.request.GET.getall('type'))
136 Session().commit()
135 Session().commit()
137 raise HTTPFound(h.route_path('notifications_show_all'))
136 raise HTTPFound(h.route_path('notifications_show_all'))
138
137
139 @LoginRequired()
138 @LoginRequired()
140 @NotAnonymous()
139 @NotAnonymous()
141 @CSRFRequired()
140 @CSRFRequired()
142 def notification_update(self):
141 def notification_update(self):
143 notification_id = self.request.matchdict['notification_id']
142 notification_id = self.request.matchdict['notification_id']
144 notification = Notification.get_or_404(notification_id)
143 notification = Notification.get_or_404(notification_id)
145
144
146 if not self._has_permissions(notification):
145 if not self._has_permissions(notification):
147 log.debug('User %s does not have permission to access notification',
146 log.debug('User %s does not have permission to access notification',
148 self._rhodecode_user)
147 self._rhodecode_user)
149 raise HTTPNotFound()
148 raise HTTPNotFound()
150
149
151 try:
150 try:
152 # updates notification read flag
151 # updates notification read flag
153 NotificationModel().mark_read(
152 NotificationModel().mark_read(
154 self._rhodecode_user.user_id, notification)
153 self._rhodecode_user.user_id, notification)
155 Session().commit()
154 Session().commit()
156 return 'ok'
155 return 'ok'
157 except Exception:
156 except Exception:
158 Session().rollback()
157 Session().rollback()
159 log.exception("Exception updating a notification item")
158 log.exception("Exception updating a notification item")
160
159
161 raise HTTPInternalServerError()
160 raise HTTPInternalServerError()
162
161
163 @LoginRequired()
162 @LoginRequired()
164 @NotAnonymous()
163 @NotAnonymous()
165 @CSRFRequired()
164 @CSRFRequired()
166 def notification_delete(self):
165 def notification_delete(self):
167 notification_id = self.request.matchdict['notification_id']
166 notification_id = self.request.matchdict['notification_id']
168 notification = Notification.get_or_404(notification_id)
167 notification = Notification.get_or_404(notification_id)
169 if not self._has_permissions(notification):
168 if not self._has_permissions(notification):
170 log.debug('User %s does not have permission to access notification',
169 log.debug('User %s does not have permission to access notification',
171 self._rhodecode_user)
170 self._rhodecode_user)
172 raise HTTPNotFound()
171 raise HTTPNotFound()
173
172
174 try:
173 try:
175 # deletes only notification2user
174 # deletes only notification2user
176 NotificationModel().delete(
175 NotificationModel().delete(
177 self._rhodecode_user.user_id, notification)
176 self._rhodecode_user.user_id, notification)
178 Session().commit()
177 Session().commit()
179 return 'ok'
178 return 'ok'
180 except Exception:
179 except Exception:
181 Session().rollback()
180 Session().rollback()
182 log.exception("Exception deleting a notification item")
181 log.exception("Exception deleting a notification item")
183
182
184 raise HTTPInternalServerError()
183 raise HTTPInternalServerError()
@@ -1,146 +1,144 b''
1
2
3 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 import logging
19 import logging
22
20
23 from pyramid.httpexceptions import HTTPFound
21 from pyramid.httpexceptions import HTTPFound
24
22
25 from rhodecode.apps._base import BaseAppView, DataGridAppView
23 from rhodecode.apps._base import BaseAppView, DataGridAppView
26 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
24 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
27 from rhodecode.events import trigger
25 from rhodecode.events import trigger
28 from rhodecode.lib import helpers as h
26 from rhodecode.lib import helpers as h
29 from rhodecode.lib import audit_logger
27 from rhodecode.lib import audit_logger
30 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
28 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
31 from rhodecode.model.db import IntegrityError, UserSshKeys
29 from rhodecode.model.db import IntegrityError, UserSshKeys
32 from rhodecode.model.meta import Session
30 from rhodecode.model.meta import Session
33 from rhodecode.model.ssh_key import SshKeyModel
31 from rhodecode.model.ssh_key import SshKeyModel
34
32
35 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
36
34
37
35
38 class MyAccountSshKeysView(BaseAppView, DataGridAppView):
36 class MyAccountSshKeysView(BaseAppView, DataGridAppView):
39
37
40 def load_default_context(self):
38 def load_default_context(self):
41 c = self._get_local_tmpl_context()
39 c = self._get_local_tmpl_context()
42 c.user = c.auth_user.get_instance()
40 c.user = c.auth_user.get_instance()
43 c.ssh_enabled = self.request.registry.settings.get(
41 c.ssh_enabled = self.request.registry.settings.get(
44 'ssh.generate_authorized_keyfile')
42 'ssh.generate_authorized_keyfile')
45 return c
43 return c
46
44
47 @LoginRequired()
45 @LoginRequired()
48 @NotAnonymous()
46 @NotAnonymous()
49 def my_account_ssh_keys(self):
47 def my_account_ssh_keys(self):
50 _ = self.request.translate
48 _ = self.request.translate
51
49
52 c = self.load_default_context()
50 c = self.load_default_context()
53 c.active = 'ssh_keys'
51 c.active = 'ssh_keys'
54 c.default_key = self.request.GET.get('default_key')
52 c.default_key = self.request.GET.get('default_key')
55 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
53 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
56 return self._get_template_context(c)
54 return self._get_template_context(c)
57
55
58 @LoginRequired()
56 @LoginRequired()
59 @NotAnonymous()
57 @NotAnonymous()
60 def ssh_keys_generate_keypair(self):
58 def ssh_keys_generate_keypair(self):
61 _ = self.request.translate
59 _ = self.request.translate
62 c = self.load_default_context()
60 c = self.load_default_context()
63
61
64 c.active = 'ssh_keys_generate'
62 c.active = 'ssh_keys_generate'
65 if c.ssh_key_generator_enabled:
63 if c.ssh_key_generator_enabled:
66 private_format = self.request.GET.get('private_format') \
64 private_format = self.request.GET.get('private_format') \
67 or SshKeyModel.DEFAULT_PRIVATE_KEY_FORMAT
65 or SshKeyModel.DEFAULT_PRIVATE_KEY_FORMAT
68 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
66 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
69 c.private, c.public = SshKeyModel().generate_keypair(
67 c.private, c.public = SshKeyModel().generate_keypair(
70 comment=comment, private_format=private_format)
68 comment=comment, private_format=private_format)
71 c.target_form_url = h.route_path(
69 c.target_form_url = h.route_path(
72 'my_account_ssh_keys', _query=dict(default_key=c.public))
70 'my_account_ssh_keys', _query=dict(default_key=c.public))
73 return self._get_template_context(c)
71 return self._get_template_context(c)
74
72
75 @LoginRequired()
73 @LoginRequired()
76 @NotAnonymous()
74 @NotAnonymous()
77 @CSRFRequired()
75 @CSRFRequired()
78 def my_account_ssh_keys_add(self):
76 def my_account_ssh_keys_add(self):
79 _ = self.request.translate
77 _ = self.request.translate
80 c = self.load_default_context()
78 c = self.load_default_context()
81
79
82 user_data = c.user.get_api_data()
80 user_data = c.user.get_api_data()
83 key_data = self.request.POST.get('key_data')
81 key_data = self.request.POST.get('key_data')
84 description = self.request.POST.get('description')
82 description = self.request.POST.get('description')
85 fingerprint = 'unknown'
83 fingerprint = 'unknown'
86 try:
84 try:
87 if not key_data:
85 if not key_data:
88 raise ValueError('Please add a valid public key')
86 raise ValueError('Please add a valid public key')
89
87
90 key = SshKeyModel().parse_key(key_data.strip())
88 key = SshKeyModel().parse_key(key_data.strip())
91 fingerprint = key.hash_md5()
89 fingerprint = key.hash_md5()
92
90
93 ssh_key = SshKeyModel().create(
91 ssh_key = SshKeyModel().create(
94 c.user.user_id, fingerprint, key.keydata, description)
92 c.user.user_id, fingerprint, key.keydata, description)
95 ssh_key_data = ssh_key.get_api_data()
93 ssh_key_data = ssh_key.get_api_data()
96
94
97 audit_logger.store_web(
95 audit_logger.store_web(
98 'user.edit.ssh_key.add', action_data={
96 'user.edit.ssh_key.add', action_data={
99 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
97 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
100 user=self._rhodecode_user, )
98 user=self._rhodecode_user, )
101 Session().commit()
99 Session().commit()
102
100
103 # Trigger an event on change of keys.
101 # Trigger an event on change of keys.
104 trigger(SshKeyFileChangeEvent(), self.request.registry)
102 trigger(SshKeyFileChangeEvent(), self.request.registry)
105
103
106 h.flash(_("Ssh Key successfully created"), category='success')
104 h.flash(_("Ssh Key successfully created"), category='success')
107
105
108 except IntegrityError:
106 except IntegrityError:
109 log.exception("Exception during ssh key saving")
107 log.exception("Exception during ssh key saving")
110 err = 'Such key with fingerprint `{}` already exists, ' \
108 err = 'Such key with fingerprint `{}` already exists, ' \
111 'please use a different one'.format(fingerprint)
109 'please use a different one'.format(fingerprint)
112 h.flash(_('An error occurred during ssh key saving: {}').format(err),
110 h.flash(_('An error occurred during ssh key saving: {}').format(err),
113 category='error')
111 category='error')
114 except Exception as e:
112 except Exception as e:
115 log.exception("Exception during ssh key saving")
113 log.exception("Exception during ssh key saving")
116 h.flash(_('An error occurred during ssh key saving: {}').format(e),
114 h.flash(_('An error occurred during ssh key saving: {}').format(e),
117 category='error')
115 category='error')
118
116
119 return HTTPFound(h.route_path('my_account_ssh_keys'))
117 return HTTPFound(h.route_path('my_account_ssh_keys'))
120
118
121 @LoginRequired()
119 @LoginRequired()
122 @NotAnonymous()
120 @NotAnonymous()
123 @CSRFRequired()
121 @CSRFRequired()
124 def my_account_ssh_keys_delete(self):
122 def my_account_ssh_keys_delete(self):
125 _ = self.request.translate
123 _ = self.request.translate
126 c = self.load_default_context()
124 c = self.load_default_context()
127
125
128 user_data = c.user.get_api_data()
126 user_data = c.user.get_api_data()
129
127
130 del_ssh_key = self.request.POST.get('del_ssh_key')
128 del_ssh_key = self.request.POST.get('del_ssh_key')
131
129
132 if del_ssh_key:
130 if del_ssh_key:
133 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
131 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
134 ssh_key_data = ssh_key.get_api_data()
132 ssh_key_data = ssh_key.get_api_data()
135
133
136 SshKeyModel().delete(del_ssh_key, c.user.user_id)
134 SshKeyModel().delete(del_ssh_key, c.user.user_id)
137 audit_logger.store_web(
135 audit_logger.store_web(
138 'user.edit.ssh_key.delete', action_data={
136 'user.edit.ssh_key.delete', action_data={
139 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
137 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
140 user=self._rhodecode_user,)
138 user=self._rhodecode_user,)
141 Session().commit()
139 Session().commit()
142 # Trigger an event on change of keys.
140 # Trigger an event on change of keys.
143 trigger(SshKeyFileChangeEvent(), self.request.registry)
141 trigger(SshKeyFileChangeEvent(), self.request.registry)
144 h.flash(_("Ssh key successfully deleted"), category='success')
142 h.flash(_("Ssh key successfully deleted"), category='success')
145
143
146 return HTTPFound(h.route_path('my_account_ssh_keys'))
144 return HTTPFound(h.route_path('my_account_ssh_keys'))
@@ -1,64 +1,62 b''
1
2
3 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 from rhodecode.apps._base import ADMIN_PREFIX
19 from rhodecode.apps._base import ADMIN_PREFIX
22
20
23
21
24 def admin_routes(config):
22 def admin_routes(config):
25 from rhodecode.apps.ops.views import OpsView
23 from rhodecode.apps.ops.views import OpsView
26
24
27 config.add_route(
25 config.add_route(
28 name='ops_ping',
26 name='ops_ping',
29 pattern='/ping')
27 pattern='/ping')
30 config.add_view(
28 config.add_view(
31 OpsView,
29 OpsView,
32 attr='ops_ping',
30 attr='ops_ping',
33 route_name='ops_ping', request_method='GET',
31 route_name='ops_ping', request_method='GET',
34 renderer='json_ext')
32 renderer='json_ext')
35
33
36 config.add_route(
34 config.add_route(
37 name='ops_error_test',
35 name='ops_error_test',
38 pattern='/error')
36 pattern='/error')
39 config.add_view(
37 config.add_view(
40 OpsView,
38 OpsView,
41 attr='ops_error_test',
39 attr='ops_error_test',
42 route_name='ops_error_test', request_method='GET',
40 route_name='ops_error_test', request_method='GET',
43 renderer='json_ext')
41 renderer='json_ext')
44
42
45 config.add_route(
43 config.add_route(
46 name='ops_redirect_test',
44 name='ops_redirect_test',
47 pattern='/redirect')
45 pattern='/redirect')
48 config.add_view(
46 config.add_view(
49 OpsView,
47 OpsView,
50 attr='ops_redirect_test',
48 attr='ops_redirect_test',
51 route_name='ops_redirect_test', request_method='GET',
49 route_name='ops_redirect_test', request_method='GET',
52 renderer='json_ext')
50 renderer='json_ext')
53
51
54 config.add_route(
52 config.add_route(
55 name='ops_healthcheck',
53 name='ops_healthcheck',
56 pattern='/status')
54 pattern='/status')
57 config.add_view(
55 config.add_view(
58 OpsView,
56 OpsView,
59 attr='ops_healthcheck',
57 attr='ops_healthcheck',
60 route_name='ops_healthcheck', request_method='GET',
58 route_name='ops_healthcheck', request_method='GET',
61 renderer='json_ext')
59 renderer='json_ext')
62
60
63 def includeme(config):
61 def includeme(config):
64 config.include(admin_routes, route_prefix=ADMIN_PREFIX + '/ops')
62 config.include(admin_routes, route_prefix=ADMIN_PREFIX + '/ops')
@@ -1,96 +1,94 b''
1
2
3 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 import time
19 import time
22 import logging
20 import logging
23
21
24
22
25 from pyramid.httpexceptions import HTTPFound
23 from pyramid.httpexceptions import HTTPFound
26
24
27 from rhodecode.apps._base import BaseAppView
25 from rhodecode.apps._base import BaseAppView
28 from rhodecode.lib import helpers as h
26 from rhodecode.lib import helpers as h
29 from rhodecode.lib.auth import LoginRequired
27 from rhodecode.lib.auth import LoginRequired
30 from collections import OrderedDict
28 from collections import OrderedDict
31 from rhodecode.model.db import UserApiKeys
29 from rhodecode.model.db import UserApiKeys
32
30
33 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
34
32
35
33
36 class OpsView(BaseAppView):
34 class OpsView(BaseAppView):
37
35
38 def load_default_context(self):
36 def load_default_context(self):
39 c = self._get_local_tmpl_context()
37 c = self._get_local_tmpl_context()
40 c.user = c.auth_user.get_instance()
38 c.user = c.auth_user.get_instance()
41
39
42 return c
40 return c
43
41
44 def ops_ping(self):
42 def ops_ping(self):
45 data = OrderedDict()
43 data = OrderedDict()
46 data['instance'] = self.request.registry.settings.get('instance_id')
44 data['instance'] = self.request.registry.settings.get('instance_id')
47
45
48 if getattr(self.request, 'user'):
46 if getattr(self.request, 'user'):
49 caller_name = 'anonymous'
47 caller_name = 'anonymous'
50 if self.request.user.user_id:
48 if self.request.user.user_id:
51 caller_name = self.request.user.username
49 caller_name = self.request.user.username
52
50
53 data['caller_ip'] = self.request.user.ip_addr
51 data['caller_ip'] = self.request.user.ip_addr
54 data['caller_name'] = caller_name
52 data['caller_name'] = caller_name
55
53
56 return {'ok': data}
54 return {'ok': data}
57
55
58 def ops_error_test(self):
56 def ops_error_test(self):
59 """
57 """
60 Test exception handling and emails on errors
58 Test exception handling and emails on errors
61 """
59 """
62
60
63 class TestException(Exception):
61 class TestException(Exception):
64 pass
62 pass
65 # add timeout so we add some sort of rate limiter
63 # add timeout so we add some sort of rate limiter
66 time.sleep(2)
64 time.sleep(2)
67 msg = ('RhodeCode Enterprise test exception. '
65 msg = ('RhodeCode Enterprise test exception. '
68 'Client:{}. Generation time: {}.'.format(self.request.user, time.time()))
66 'Client:{}. Generation time: {}.'.format(self.request.user, time.time()))
69 raise TestException(msg)
67 raise TestException(msg)
70
68
71 def ops_redirect_test(self):
69 def ops_redirect_test(self):
72 """
70 """
73 Test redirect handling
71 Test redirect handling
74 """
72 """
75 redirect_to = self.request.GET.get('to') or h.route_path('home')
73 redirect_to = self.request.GET.get('to') or h.route_path('home')
76 raise HTTPFound(redirect_to)
74 raise HTTPFound(redirect_to)
77
75
78 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_HTTP])
76 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_HTTP])
79 def ops_healthcheck(self):
77 def ops_healthcheck(self):
80 from rhodecode.lib.system_info import load_system_info
78 from rhodecode.lib.system_info import load_system_info
81
79
82 vcsserver_info = load_system_info('vcs_server')
80 vcsserver_info = load_system_info('vcs_server')
83 if vcsserver_info:
81 if vcsserver_info:
84 vcsserver_info = vcsserver_info['human_value']
82 vcsserver_info = vcsserver_info['human_value']
85
83
86 db_info = load_system_info('database_info')
84 db_info = load_system_info('database_info')
87 if db_info:
85 if db_info:
88 db_info = db_info['human_value']
86 db_info = db_info['human_value']
89
87
90 health_spec = {
88 health_spec = {
91 'caller_ip': self.request.user.ip_addr,
89 'caller_ip': self.request.user.ip_addr,
92 'vcsserver': vcsserver_info,
90 'vcsserver': vcsserver_info,
93 'db': db_info,
91 'db': db_info,
94 }
92 }
95
93
96 return {'healthcheck': health_spec}
94 return {'healthcheck': health_spec}
@@ -1,102 +1,100 b''
1
2
3 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 from rhodecode.apps._base import add_route_with_slash
18 from rhodecode.apps._base import add_route_with_slash
21 from rhodecode.apps.repo_group.views.repo_group_settings import RepoGroupSettingsView
19 from rhodecode.apps.repo_group.views.repo_group_settings import RepoGroupSettingsView
22 from rhodecode.apps.repo_group.views.repo_group_advanced import RepoGroupAdvancedSettingsView
20 from rhodecode.apps.repo_group.views.repo_group_advanced import RepoGroupAdvancedSettingsView
23 from rhodecode.apps.repo_group.views.repo_group_permissions import RepoGroupPermissionsView
21 from rhodecode.apps.repo_group.views.repo_group_permissions import RepoGroupPermissionsView
24 from rhodecode.apps.home.views import HomeView
22 from rhodecode.apps.home.views import HomeView
25
23
26
24
27 def includeme(config):
25 def includeme(config):
28
26
29 # Settings
27 # Settings
30 config.add_route(
28 config.add_route(
31 name='edit_repo_group',
29 name='edit_repo_group',
32 pattern='/{repo_group_name:.*?[^/]}/_edit',
30 pattern='/{repo_group_name:.*?[^/]}/_edit',
33 repo_group_route=True)
31 repo_group_route=True)
34 config.add_view(
32 config.add_view(
35 RepoGroupSettingsView,
33 RepoGroupSettingsView,
36 attr='edit_settings',
34 attr='edit_settings',
37 route_name='edit_repo_group', request_method='GET',
35 route_name='edit_repo_group', request_method='GET',
38 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
36 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
39 config.add_view(
37 config.add_view(
40 RepoGroupSettingsView,
38 RepoGroupSettingsView,
41 attr='edit_settings_update',
39 attr='edit_settings_update',
42 route_name='edit_repo_group', request_method='POST',
40 route_name='edit_repo_group', request_method='POST',
43 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
41 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
44
42
45 # Settings advanced
43 # Settings advanced
46 config.add_route(
44 config.add_route(
47 name='edit_repo_group_advanced',
45 name='edit_repo_group_advanced',
48 pattern='/{repo_group_name:.*?[^/]}/_settings/advanced',
46 pattern='/{repo_group_name:.*?[^/]}/_settings/advanced',
49 repo_group_route=True)
47 repo_group_route=True)
50 config.add_view(
48 config.add_view(
51 RepoGroupAdvancedSettingsView,
49 RepoGroupAdvancedSettingsView,
52 attr='edit_repo_group_advanced',
50 attr='edit_repo_group_advanced',
53 route_name='edit_repo_group_advanced', request_method='GET',
51 route_name='edit_repo_group_advanced', request_method='GET',
54 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
52 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
55
53
56 config.add_route(
54 config.add_route(
57 name='edit_repo_group_advanced_delete',
55 name='edit_repo_group_advanced_delete',
58 pattern='/{repo_group_name:.*?[^/]}/_settings/advanced/delete',
56 pattern='/{repo_group_name:.*?[^/]}/_settings/advanced/delete',
59 repo_group_route=True)
57 repo_group_route=True)
60 config.add_view(
58 config.add_view(
61 RepoGroupAdvancedSettingsView,
59 RepoGroupAdvancedSettingsView,
62 attr='edit_repo_group_delete',
60 attr='edit_repo_group_delete',
63 route_name='edit_repo_group_advanced_delete', request_method='POST',
61 route_name='edit_repo_group_advanced_delete', request_method='POST',
64 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
62 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
65
63
66 # settings permissions
64 # settings permissions
67 config.add_route(
65 config.add_route(
68 name='edit_repo_group_perms',
66 name='edit_repo_group_perms',
69 pattern='/{repo_group_name:.*?[^/]}/_settings/permissions',
67 pattern='/{repo_group_name:.*?[^/]}/_settings/permissions',
70 repo_group_route=True)
68 repo_group_route=True)
71 config.add_view(
69 config.add_view(
72 RepoGroupPermissionsView,
70 RepoGroupPermissionsView,
73 attr='edit_repo_group_permissions',
71 attr='edit_repo_group_permissions',
74 route_name='edit_repo_group_perms', request_method='GET',
72 route_name='edit_repo_group_perms', request_method='GET',
75 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
73 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
76
74
77 config.add_route(
75 config.add_route(
78 name='edit_repo_group_perms_update',
76 name='edit_repo_group_perms_update',
79 pattern='/{repo_group_name:.*?[^/]}/_settings/permissions/update',
77 pattern='/{repo_group_name:.*?[^/]}/_settings/permissions/update',
80 repo_group_route=True)
78 repo_group_route=True)
81 config.add_view(
79 config.add_view(
82 RepoGroupPermissionsView,
80 RepoGroupPermissionsView,
83 attr='edit_repo_groups_permissions_update',
81 attr='edit_repo_groups_permissions_update',
84 route_name='edit_repo_group_perms_update', request_method='POST',
82 route_name='edit_repo_group_perms_update', request_method='POST',
85 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
83 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
86
84
87 # Summary, NOTE(marcink): needs to be at the end for catch-all
85 # Summary, NOTE(marcink): needs to be at the end for catch-all
88 add_route_with_slash(
86 add_route_with_slash(
89 config,
87 config,
90 name='repo_group_home',
88 name='repo_group_home',
91 pattern='/{repo_group_name:.*?[^/]}', repo_group_route=True)
89 pattern='/{repo_group_name:.*?[^/]}', repo_group_route=True)
92 config.add_view(
90 config.add_view(
93 HomeView,
91 HomeView,
94 attr='repo_group_main_page',
92 attr='repo_group_main_page',
95 route_name='repo_group_home', request_method='GET',
93 route_name='repo_group_home', request_method='GET',
96 renderer='rhodecode:templates/index_repo_group.mako')
94 renderer='rhodecode:templates/index_repo_group.mako')
97 config.add_view(
95 config.add_view(
98 HomeView,
96 HomeView,
99 attr='repo_group_main_page',
97 attr='repo_group_main_page',
100 route_name='repo_group_home_slash', request_method='GET',
98 route_name='repo_group_home_slash', request_method='GET',
101 renderer='rhodecode:templates/index_repo_group.mako')
99 renderer='rhodecode:templates/index_repo_group.mako')
102
100
@@ -1,18 +1,17 b''
1
2 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
2 #
4 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
7 #
6 #
8 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
10 # GNU General Public License for more details.
12 #
11 #
13 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
14 #
16 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -1,90 +1,89 b''
1
2 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
2 #
4 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
7 #
6 #
8 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
10 # GNU General Public License for more details.
12 #
11 #
13 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
14 #
16 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
18
20 import pytest
19 import pytest
21
20
22 from rhodecode.tests import assert_session_flash
21 from rhodecode.tests import assert_session_flash
23
22
24
23
25 def route_path(name, params=None, **kwargs):
24 def route_path(name, params=None, **kwargs):
26 import urllib.request
25 import urllib.request
27 import urllib.parse
26 import urllib.parse
28 import urllib.error
27 import urllib.error
29
28
30 base_url = {
29 base_url = {
31 'edit_repo_group_advanced':
30 'edit_repo_group_advanced':
32 '/{repo_group_name}/_settings/advanced',
31 '/{repo_group_name}/_settings/advanced',
33 'edit_repo_group_advanced_delete':
32 'edit_repo_group_advanced_delete':
34 '/{repo_group_name}/_settings/advanced/delete',
33 '/{repo_group_name}/_settings/advanced/delete',
35 }[name].format(**kwargs)
34 }[name].format(**kwargs)
36
35
37 if params:
36 if params:
38 base_url = '{}?{}'.format(base_url, urllib.parse.urlencode(params))
37 base_url = '{}?{}'.format(base_url, urllib.parse.urlencode(params))
39 return base_url
38 return base_url
40
39
41
40
42 @pytest.mark.usefixtures("app")
41 @pytest.mark.usefixtures("app")
43 class TestRepoGroupsAdvancedView(object):
42 class TestRepoGroupsAdvancedView(object):
44
43
45 @pytest.mark.parametrize('repo_group_name', [
44 @pytest.mark.parametrize('repo_group_name', [
46 'gro',
45 'gro',
47 '12345',
46 '12345',
48 ])
47 ])
49 def test_show_advanced_settings(self, autologin_user, user_util, repo_group_name):
48 def test_show_advanced_settings(self, autologin_user, user_util, repo_group_name):
50 user_util._test_name = repo_group_name
49 user_util._test_name = repo_group_name
51 gr = user_util.create_repo_group()
50 gr = user_util.create_repo_group()
52 self.app.get(
51 self.app.get(
53 route_path('edit_repo_group_advanced',
52 route_path('edit_repo_group_advanced',
54 repo_group_name=gr.group_name))
53 repo_group_name=gr.group_name))
55
54
56 def test_show_advanced_settings_delete(self, autologin_user, user_util,
55 def test_show_advanced_settings_delete(self, autologin_user, user_util,
57 csrf_token):
56 csrf_token):
58 gr = user_util.create_repo_group(auto_cleanup=False)
57 gr = user_util.create_repo_group(auto_cleanup=False)
59 repo_group_name = gr.group_name
58 repo_group_name = gr.group_name
60
59
61 params = dict(
60 params = dict(
62 csrf_token=csrf_token
61 csrf_token=csrf_token
63 )
62 )
64 response = self.app.post(
63 response = self.app.post(
65 route_path('edit_repo_group_advanced_delete',
64 route_path('edit_repo_group_advanced_delete',
66 repo_group_name=repo_group_name), params=params)
65 repo_group_name=repo_group_name), params=params)
67 assert_session_flash(
66 assert_session_flash(
68 response, 'Removed repository group `{}`'.format(repo_group_name))
67 response, 'Removed repository group `{}`'.format(repo_group_name))
69
68
70 def test_delete_not_possible_with_objects_inside(self, autologin_user,
69 def test_delete_not_possible_with_objects_inside(self, autologin_user,
71 repo_groups, csrf_token):
70 repo_groups, csrf_token):
72 zombie_group, parent_group, child_group = repo_groups
71 zombie_group, parent_group, child_group = repo_groups
73
72
74 response = self.app.get(
73 response = self.app.get(
75 route_path('edit_repo_group_advanced',
74 route_path('edit_repo_group_advanced',
76 repo_group_name=parent_group.group_name))
75 repo_group_name=parent_group.group_name))
77
76
78 response.mustcontain(
77 response.mustcontain(
79 'This repository group includes 1 children repository group')
78 'This repository group includes 1 children repository group')
80
79
81 params = dict(
80 params = dict(
82 csrf_token=csrf_token
81 csrf_token=csrf_token
83 )
82 )
84 response = self.app.post(
83 response = self.app.post(
85 route_path('edit_repo_group_advanced_delete',
84 route_path('edit_repo_group_advanced_delete',
86 repo_group_name=parent_group.group_name), params=params)
85 repo_group_name=parent_group.group_name), params=params)
87
86
88 assert_session_flash(
87 assert_session_flash(
89 response, 'This repository group contains 1 subgroup '
88 response, 'This repository group contains 1 subgroup '
90 'and cannot be deleted')
89 'and cannot be deleted')
@@ -1,87 +1,86 b''
1
2 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
2 #
4 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
7 #
6 #
8 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
10 # GNU General Public License for more details.
12 #
11 #
13 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
14 #
16 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
18
20 import pytest
19 import pytest
21
20
22 from rhodecode.tests.utils import permission_update_data_generator
21 from rhodecode.tests.utils import permission_update_data_generator
23
22
24
23
25 def route_path(name, params=None, **kwargs):
24 def route_path(name, params=None, **kwargs):
26 import urllib.request
25 import urllib.request
27 import urllib.parse
26 import urllib.parse
28 import urllib.error
27 import urllib.error
29
28
30 base_url = {
29 base_url = {
31 'edit_repo_group_perms':
30 'edit_repo_group_perms':
32 '/{repo_group_name:}/_settings/permissions',
31 '/{repo_group_name:}/_settings/permissions',
33 'edit_repo_group_perms_update':
32 'edit_repo_group_perms_update':
34 '/{repo_group_name}/_settings/permissions/update',
33 '/{repo_group_name}/_settings/permissions/update',
35 }[name].format(**kwargs)
34 }[name].format(**kwargs)
36
35
37 if params:
36 if params:
38 base_url = '{}?{}'.format(base_url, urllib.parse.urlencode(params))
37 base_url = '{}?{}'.format(base_url, urllib.parse.urlencode(params))
39 return base_url
38 return base_url
40
39
41
40
42 @pytest.mark.usefixtures("app")
41 @pytest.mark.usefixtures("app")
43 class TestRepoGroupPermissionsView(object):
42 class TestRepoGroupPermissionsView(object):
44
43
45 def test_edit_perms_view(self, user_util, autologin_user):
44 def test_edit_perms_view(self, user_util, autologin_user):
46 repo_group = user_util.create_repo_group()
45 repo_group = user_util.create_repo_group()
47
46
48 self.app.get(
47 self.app.get(
49 route_path('edit_repo_group_perms',
48 route_path('edit_repo_group_perms',
50 repo_group_name=repo_group.group_name), status=200)
49 repo_group_name=repo_group.group_name), status=200)
51
50
52 def test_update_permissions(self, csrf_token, user_util):
51 def test_update_permissions(self, csrf_token, user_util):
53 repo_group = user_util.create_repo_group()
52 repo_group = user_util.create_repo_group()
54 repo_group_name = repo_group.group_name
53 repo_group_name = repo_group.group_name
55 user = user_util.create_user()
54 user = user_util.create_user()
56 user_id = user.user_id
55 user_id = user.user_id
57 username = user.username
56 username = user.username
58
57
59 # grant new
58 # grant new
60 form_data = permission_update_data_generator(
59 form_data = permission_update_data_generator(
61 csrf_token,
60 csrf_token,
62 default='group.write',
61 default='group.write',
63 grant=[(user_id, 'group.write', username, 'user')])
62 grant=[(user_id, 'group.write', username, 'user')])
64
63
65 # recursive flag required for repo groups
64 # recursive flag required for repo groups
66 form_data.extend([('recursive', u'none')])
65 form_data.extend([('recursive', u'none')])
67
66
68 response = self.app.post(
67 response = self.app.post(
69 route_path('edit_repo_group_perms_update',
68 route_path('edit_repo_group_perms_update',
70 repo_group_name=repo_group_name), form_data).follow()
69 repo_group_name=repo_group_name), form_data).follow()
71
70
72 assert 'Repository Group permissions updated' in response
71 assert 'Repository Group permissions updated' in response
73
72
74 # revoke given
73 # revoke given
75 form_data = permission_update_data_generator(
74 form_data = permission_update_data_generator(
76 csrf_token,
75 csrf_token,
77 default='group.read',
76 default='group.read',
78 revoke=[(user_id, 'user')])
77 revoke=[(user_id, 'user')])
79
78
80 # recursive flag required for repo groups
79 # recursive flag required for repo groups
81 form_data.extend([('recursive', u'none')])
80 form_data.extend([('recursive', u'none')])
82
81
83 response = self.app.post(
82 response = self.app.post(
84 route_path('edit_repo_group_perms_update',
83 route_path('edit_repo_group_perms_update',
85 repo_group_name=repo_group_name), form_data).follow()
84 repo_group_name=repo_group_name), form_data).follow()
86
85
87 assert 'Repository Group permissions updated' in response
86 assert 'Repository Group permissions updated' in response
@@ -1,19 +1,17 b''
1
2
3 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/ No newline at end of file
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -1,105 +1,103 b''
1
2
3 # Copyright (C) 2011-2023 RhodeCode GmbH
1 # Copyright (C) 2011-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 import logging
19 import logging
22
20
23
21
24 from pyramid.httpexceptions import HTTPFound
22 from pyramid.httpexceptions import HTTPFound
25
23
26 from rhodecode.apps._base import RepoGroupAppView
24 from rhodecode.apps._base import RepoGroupAppView
27 from rhodecode.lib import helpers as h
25 from rhodecode.lib import helpers as h
28 from rhodecode.lib import audit_logger
26 from rhodecode.lib import audit_logger
29 from rhodecode.lib.auth import (
27 from rhodecode.lib.auth import (
30 LoginRequired, CSRFRequired, HasRepoGroupPermissionAnyDecorator)
28 LoginRequired, CSRFRequired, HasRepoGroupPermissionAnyDecorator)
31 from rhodecode.model.repo_group import RepoGroupModel
29 from rhodecode.model.repo_group import RepoGroupModel
32 from rhodecode.model.meta import Session
30 from rhodecode.model.meta import Session
33
31
34 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
35
33
36
34
37 class RepoGroupAdvancedSettingsView(RepoGroupAppView):
35 class RepoGroupAdvancedSettingsView(RepoGroupAppView):
38 def load_default_context(self):
36 def load_default_context(self):
39 c = self._get_local_tmpl_context()
37 c = self._get_local_tmpl_context()
40 return c
38 return c
41
39
42 @LoginRequired()
40 @LoginRequired()
43 @HasRepoGroupPermissionAnyDecorator('group.admin')
41 @HasRepoGroupPermissionAnyDecorator('group.admin')
44 def edit_repo_group_advanced(self):
42 def edit_repo_group_advanced(self):
45 _ = self.request.translate
43 _ = self.request.translate
46 c = self.load_default_context()
44 c = self.load_default_context()
47 c.active = 'advanced'
45 c.active = 'advanced'
48 c.repo_group = self.db_repo_group
46 c.repo_group = self.db_repo_group
49
47
50 # update commit cache if GET flag is present
48 # update commit cache if GET flag is present
51 if self.request.GET.get('update_commit_cache'):
49 if self.request.GET.get('update_commit_cache'):
52 self.db_repo_group.update_commit_cache()
50 self.db_repo_group.update_commit_cache()
53 h.flash(_('updated commit cache'), category='success')
51 h.flash(_('updated commit cache'), category='success')
54
52
55 return self._get_template_context(c)
53 return self._get_template_context(c)
56
54
57 @LoginRequired()
55 @LoginRequired()
58 @HasRepoGroupPermissionAnyDecorator('group.admin')
56 @HasRepoGroupPermissionAnyDecorator('group.admin')
59 @CSRFRequired()
57 @CSRFRequired()
60 def edit_repo_group_delete(self):
58 def edit_repo_group_delete(self):
61 _ = self.request.translate
59 _ = self.request.translate
62 _ungettext = self.request.plularize
60 _ungettext = self.request.plularize
63 c = self.load_default_context()
61 c = self.load_default_context()
64 c.repo_group = self.db_repo_group
62 c.repo_group = self.db_repo_group
65
63
66 repos = c.repo_group.repositories.all()
64 repos = c.repo_group.repositories.all()
67 if repos:
65 if repos:
68 msg = _ungettext(
66 msg = _ungettext(
69 'This repository group contains %(num)d repository and cannot be deleted',
67 'This repository group contains %(num)d repository and cannot be deleted',
70 'This repository group contains %(num)d repositories and cannot be'
68 'This repository group contains %(num)d repositories and cannot be'
71 ' deleted',
69 ' deleted',
72 len(repos)) % {'num': len(repos)}
70 len(repos)) % {'num': len(repos)}
73 h.flash(msg, category='warning')
71 h.flash(msg, category='warning')
74 raise HTTPFound(
72 raise HTTPFound(
75 h.route_path('edit_repo_group_advanced',
73 h.route_path('edit_repo_group_advanced',
76 repo_group_name=self.db_repo_group_name))
74 repo_group_name=self.db_repo_group_name))
77
75
78 children = c.repo_group.children.all()
76 children = c.repo_group.children.all()
79 if children:
77 if children:
80 msg = _ungettext(
78 msg = _ungettext(
81 'This repository group contains %(num)d subgroup and cannot be deleted',
79 'This repository group contains %(num)d subgroup and cannot be deleted',
82 'This repository group contains %(num)d subgroups and cannot be deleted',
80 'This repository group contains %(num)d subgroups and cannot be deleted',
83 len(children)) % {'num': len(children)}
81 len(children)) % {'num': len(children)}
84 h.flash(msg, category='warning')
82 h.flash(msg, category='warning')
85 raise HTTPFound(
83 raise HTTPFound(
86 h.route_path('edit_repo_group_advanced',
84 h.route_path('edit_repo_group_advanced',
87 repo_group_name=self.db_repo_group_name))
85 repo_group_name=self.db_repo_group_name))
88
86
89 try:
87 try:
90 old_values = c.repo_group.get_api_data()
88 old_values = c.repo_group.get_api_data()
91 RepoGroupModel().delete(self.db_repo_group_name)
89 RepoGroupModel().delete(self.db_repo_group_name)
92
90
93 audit_logger.store_web(
91 audit_logger.store_web(
94 'repo_group.delete', action_data={'old_data': old_values},
92 'repo_group.delete', action_data={'old_data': old_values},
95 user=c.rhodecode_user)
93 user=c.rhodecode_user)
96
94
97 Session().commit()
95 Session().commit()
98 h.flash(_('Removed repository group `%s`') % self.db_repo_group_name,
96 h.flash(_('Removed repository group `%s`') % self.db_repo_group_name,
99 category='success')
97 category='success')
100 except Exception:
98 except Exception:
101 log.exception("Exception during deletion of repository group")
99 log.exception("Exception during deletion of repository group")
102 h.flash(_('Error occurred during deletion of repository group %s')
100 h.flash(_('Error occurred during deletion of repository group %s')
103 % self.db_repo_group_name, category='error')
101 % self.db_repo_group_name, category='error')
104
102
105 raise HTTPFound(h.route_path('repo_groups'))
103 raise HTTPFound(h.route_path('repo_groups'))
@@ -1,104 +1,102 b''
1
2
3 # Copyright (C) 2011-2023 RhodeCode GmbH
1 # Copyright (C) 2011-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 import logging
19 import logging
22
20
23
21
24 from pyramid.httpexceptions import HTTPFound
22 from pyramid.httpexceptions import HTTPFound
25
23
26 from rhodecode.apps._base import RepoGroupAppView
24 from rhodecode.apps._base import RepoGroupAppView
27 from rhodecode.lib import helpers as h
25 from rhodecode.lib import helpers as h
28 from rhodecode.lib import audit_logger
26 from rhodecode.lib import audit_logger
29 from rhodecode.lib.auth import (
27 from rhodecode.lib.auth import (
30 LoginRequired, HasRepoGroupPermissionAnyDecorator, CSRFRequired)
28 LoginRequired, HasRepoGroupPermissionAnyDecorator, CSRFRequired)
31 from rhodecode.model.db import User
29 from rhodecode.model.db import User
32 from rhodecode.model.permission import PermissionModel
30 from rhodecode.model.permission import PermissionModel
33 from rhodecode.model.repo_group import RepoGroupModel
31 from rhodecode.model.repo_group import RepoGroupModel
34 from rhodecode.model.forms import RepoGroupPermsForm
32 from rhodecode.model.forms import RepoGroupPermsForm
35 from rhodecode.model.meta import Session
33 from rhodecode.model.meta import Session
36
34
37 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
38
36
39
37
40 class RepoGroupPermissionsView(RepoGroupAppView):
38 class RepoGroupPermissionsView(RepoGroupAppView):
41 def load_default_context(self):
39 def load_default_context(self):
42 c = self._get_local_tmpl_context()
40 c = self._get_local_tmpl_context()
43
41
44 return c
42 return c
45
43
46 @LoginRequired()
44 @LoginRequired()
47 @HasRepoGroupPermissionAnyDecorator('group.admin')
45 @HasRepoGroupPermissionAnyDecorator('group.admin')
48 def edit_repo_group_permissions(self):
46 def edit_repo_group_permissions(self):
49 c = self.load_default_context()
47 c = self.load_default_context()
50 c.active = 'permissions'
48 c.active = 'permissions'
51 c.repo_group = self.db_repo_group
49 c.repo_group = self.db_repo_group
52 return self._get_template_context(c)
50 return self._get_template_context(c)
53
51
54 @LoginRequired()
52 @LoginRequired()
55 @HasRepoGroupPermissionAnyDecorator('group.admin')
53 @HasRepoGroupPermissionAnyDecorator('group.admin')
56 @CSRFRequired()
54 @CSRFRequired()
57 def edit_repo_groups_permissions_update(self):
55 def edit_repo_groups_permissions_update(self):
58 _ = self.request.translate
56 _ = self.request.translate
59 c = self.load_default_context()
57 c = self.load_default_context()
60 c.active = 'perms'
58 c.active = 'perms'
61 c.repo_group = self.db_repo_group
59 c.repo_group = self.db_repo_group
62
60
63 valid_recursive_choices = ['none', 'repos', 'groups', 'all']
61 valid_recursive_choices = ['none', 'repos', 'groups', 'all']
64 form = RepoGroupPermsForm(self.request.translate, valid_recursive_choices)()\
62 form = RepoGroupPermsForm(self.request.translate, valid_recursive_choices)()\
65 .to_python(self.request.POST)
63 .to_python(self.request.POST)
66
64
67 if not c.rhodecode_user.is_admin:
65 if not c.rhodecode_user.is_admin:
68 if self._revoke_perms_on_yourself(form):
66 if self._revoke_perms_on_yourself(form):
69 msg = _('Cannot change permission for yourself as admin')
67 msg = _('Cannot change permission for yourself as admin')
70 h.flash(msg, category='warning')
68 h.flash(msg, category='warning')
71 raise HTTPFound(
69 raise HTTPFound(
72 h.route_path('edit_repo_group_perms',
70 h.route_path('edit_repo_group_perms',
73 repo_group_name=self.db_repo_group_name))
71 repo_group_name=self.db_repo_group_name))
74
72
75 # iterate over all members(if in recursive mode) of this groups and
73 # iterate over all members(if in recursive mode) of this groups and
76 # set the permissions !
74 # set the permissions !
77 # this can be potentially heavy operation
75 # this can be potentially heavy operation
78 changes = RepoGroupModel().update_permissions(
76 changes = RepoGroupModel().update_permissions(
79 c.repo_group,
77 c.repo_group,
80 form['perm_additions'], form['perm_updates'], form['perm_deletions'],
78 form['perm_additions'], form['perm_updates'], form['perm_deletions'],
81 form['recursive'])
79 form['recursive'])
82
80
83 action_data = {
81 action_data = {
84 'added': changes['added'],
82 'added': changes['added'],
85 'updated': changes['updated'],
83 'updated': changes['updated'],
86 'deleted': changes['deleted'],
84 'deleted': changes['deleted'],
87 }
85 }
88 audit_logger.store_web(
86 audit_logger.store_web(
89 'repo_group.edit.permissions', action_data=action_data,
87 'repo_group.edit.permissions', action_data=action_data,
90 user=c.rhodecode_user)
88 user=c.rhodecode_user)
91
89
92 Session().commit()
90 Session().commit()
93 h.flash(_('Repository Group permissions updated'), category='success')
91 h.flash(_('Repository Group permissions updated'), category='success')
94
92
95 affected_user_ids = None
93 affected_user_ids = None
96 if changes.get('default_user_changed', False):
94 if changes.get('default_user_changed', False):
97 # if we change the default user, we need to flush everyone permissions
95 # if we change the default user, we need to flush everyone permissions
98 affected_user_ids = User.get_all_user_ids()
96 affected_user_ids = User.get_all_user_ids()
99 PermissionModel().flush_user_permission_caches(
97 PermissionModel().flush_user_permission_caches(
100 changes, affected_user_ids=affected_user_ids)
98 changes, affected_user_ids=affected_user_ids)
101
99
102 raise HTTPFound(
100 raise HTTPFound(
103 h.route_path('edit_repo_group_perms',
101 h.route_path('edit_repo_group_perms',
104 repo_group_name=self.db_repo_group_name))
102 repo_group_name=self.db_repo_group_name))
@@ -1,187 +1,185 b''
1
2
3 # Copyright (C) 2011-2023 RhodeCode GmbH
1 # Copyright (C) 2011-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 import logging
19 import logging
22 import deform
20 import deform
23
21
24
22
25 from pyramid.httpexceptions import HTTPFound
23 from pyramid.httpexceptions import HTTPFound
26
24
27 from rhodecode import events
25 from rhodecode import events
28 from rhodecode.apps._base import RepoGroupAppView
26 from rhodecode.apps._base import RepoGroupAppView
29 from rhodecode.forms import RcForm
27 from rhodecode.forms import RcForm
30 from rhodecode.lib import helpers as h
28 from rhodecode.lib import helpers as h
31 from rhodecode.lib import audit_logger
29 from rhodecode.lib import audit_logger
32 from rhodecode.lib.auth import (
30 from rhodecode.lib.auth import (
33 LoginRequired, HasPermissionAll,
31 LoginRequired, HasPermissionAll,
34 HasRepoGroupPermissionAny, HasRepoGroupPermissionAnyDecorator, CSRFRequired)
32 HasRepoGroupPermissionAny, HasRepoGroupPermissionAnyDecorator, CSRFRequired)
35 from rhodecode.model.db import Session, RepoGroup, User
33 from rhodecode.model.db import Session, RepoGroup, User
36 from rhodecode.model.permission import PermissionModel
34 from rhodecode.model.permission import PermissionModel
37 from rhodecode.model.scm import RepoGroupList
35 from rhodecode.model.scm import RepoGroupList
38 from rhodecode.model.repo_group import RepoGroupModel
36 from rhodecode.model.repo_group import RepoGroupModel
39 from rhodecode.model.validation_schema.schemas import repo_group_schema
37 from rhodecode.model.validation_schema.schemas import repo_group_schema
40
38
41 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
42
40
43
41
44 class RepoGroupSettingsView(RepoGroupAppView):
42 class RepoGroupSettingsView(RepoGroupAppView):
45 def load_default_context(self):
43 def load_default_context(self):
46 c = self._get_local_tmpl_context()
44 c = self._get_local_tmpl_context()
47 c.repo_group = self.db_repo_group
45 c.repo_group = self.db_repo_group
48 no_parrent = not c.repo_group.parent_group
46 no_parrent = not c.repo_group.parent_group
49 can_create_in_root = self._can_create_repo_group()
47 can_create_in_root = self._can_create_repo_group()
50
48
51 show_root_location = False
49 show_root_location = False
52 if no_parrent or can_create_in_root:
50 if no_parrent or can_create_in_root:
53 # we're global admin, we're ok and we can create TOP level groups
51 # we're global admin, we're ok and we can create TOP level groups
54 # or in case this group is already at top-level we also allow
52 # or in case this group is already at top-level we also allow
55 # creation in root
53 # creation in root
56 show_root_location = True
54 show_root_location = True
57
55
58 acl_groups = RepoGroupList(
56 acl_groups = RepoGroupList(
59 RepoGroup.query().all(),
57 RepoGroup.query().all(),
60 perm_set=['group.admin'])
58 perm_set=['group.admin'])
61 c.repo_groups = RepoGroup.groups_choices(
59 c.repo_groups = RepoGroup.groups_choices(
62 groups=acl_groups,
60 groups=acl_groups,
63 show_empty_group=show_root_location)
61 show_empty_group=show_root_location)
64 # filter out current repo group
62 # filter out current repo group
65 exclude_group_ids = [c.repo_group.group_id]
63 exclude_group_ids = [c.repo_group.group_id]
66 c.repo_groups = [x for x in c.repo_groups if x[0] not in exclude_group_ids]
64 c.repo_groups = [x for x in c.repo_groups if x[0] not in exclude_group_ids]
67 c.repo_groups_choices = [k[0] for k in c.repo_groups]
65 c.repo_groups_choices = [k[0] for k in c.repo_groups]
68
66
69 parent_group = c.repo_group.parent_group
67 parent_group = c.repo_group.parent_group
70
68
71 add_parent_group = (parent_group and (
69 add_parent_group = (parent_group and (
72 parent_group.group_id not in c.repo_groups_choices))
70 parent_group.group_id not in c.repo_groups_choices))
73 if add_parent_group:
71 if add_parent_group:
74 c.repo_groups_choices.append(parent_group.group_id)
72 c.repo_groups_choices.append(parent_group.group_id)
75 c.repo_groups.append(RepoGroup._generate_choice(parent_group))
73 c.repo_groups.append(RepoGroup._generate_choice(parent_group))
76 return c
74 return c
77
75
78 def _can_create_repo_group(self, parent_group_id=None):
76 def _can_create_repo_group(self, parent_group_id=None):
79 is_admin = HasPermissionAll('hg.admin')('group create controller')
77 is_admin = HasPermissionAll('hg.admin')('group create controller')
80 create_repo_group = HasPermissionAll(
78 create_repo_group = HasPermissionAll(
81 'hg.repogroup.create.true')('group create controller')
79 'hg.repogroup.create.true')('group create controller')
82 if is_admin or (create_repo_group and not parent_group_id):
80 if is_admin or (create_repo_group and not parent_group_id):
83 # we're global admin, or we have global repo group create
81 # we're global admin, or we have global repo group create
84 # permission
82 # permission
85 # we're ok and we can create TOP level groups
83 # we're ok and we can create TOP level groups
86 return True
84 return True
87 elif parent_group_id:
85 elif parent_group_id:
88 # we check the permission if we can write to parent group
86 # we check the permission if we can write to parent group
89 group = RepoGroup.get(parent_group_id)
87 group = RepoGroup.get(parent_group_id)
90 group_name = group.group_name if group else None
88 group_name = group.group_name if group else None
91 if HasRepoGroupPermissionAny('group.admin')(
89 if HasRepoGroupPermissionAny('group.admin')(
92 group_name, 'check if user is an admin of group'):
90 group_name, 'check if user is an admin of group'):
93 # we're an admin of passed in group, we're ok.
91 # we're an admin of passed in group, we're ok.
94 return True
92 return True
95 else:
93 else:
96 return False
94 return False
97 return False
95 return False
98
96
99 def _get_schema(self, c, old_values=None):
97 def _get_schema(self, c, old_values=None):
100 return repo_group_schema.RepoGroupSettingsSchema().bind(
98 return repo_group_schema.RepoGroupSettingsSchema().bind(
101 repo_group_repo_group_options=c.repo_groups_choices,
99 repo_group_repo_group_options=c.repo_groups_choices,
102 repo_group_repo_group_items=c.repo_groups,
100 repo_group_repo_group_items=c.repo_groups,
103
101
104 # user caller
102 # user caller
105 user=self._rhodecode_user,
103 user=self._rhodecode_user,
106 old_values=old_values
104 old_values=old_values
107 )
105 )
108
106
109 @LoginRequired()
107 @LoginRequired()
110 @HasRepoGroupPermissionAnyDecorator('group.admin')
108 @HasRepoGroupPermissionAnyDecorator('group.admin')
111 def edit_settings(self):
109 def edit_settings(self):
112 c = self.load_default_context()
110 c = self.load_default_context()
113 c.active = 'settings'
111 c.active = 'settings'
114
112
115 defaults = RepoGroupModel()._get_defaults(self.db_repo_group_name)
113 defaults = RepoGroupModel()._get_defaults(self.db_repo_group_name)
116 defaults['repo_group_owner'] = defaults['user']
114 defaults['repo_group_owner'] = defaults['user']
117
115
118 schema = self._get_schema(c)
116 schema = self._get_schema(c)
119 c.form = RcForm(schema, appstruct=defaults)
117 c.form = RcForm(schema, appstruct=defaults)
120 return self._get_template_context(c)
118 return self._get_template_context(c)
121
119
122 @LoginRequired()
120 @LoginRequired()
123 @HasRepoGroupPermissionAnyDecorator('group.admin')
121 @HasRepoGroupPermissionAnyDecorator('group.admin')
124 @CSRFRequired()
122 @CSRFRequired()
125 def edit_settings_update(self):
123 def edit_settings_update(self):
126 _ = self.request.translate
124 _ = self.request.translate
127 c = self.load_default_context()
125 c = self.load_default_context()
128 c.active = 'settings'
126 c.active = 'settings'
129
127
130 old_repo_group_name = self.db_repo_group_name
128 old_repo_group_name = self.db_repo_group_name
131 new_repo_group_name = old_repo_group_name
129 new_repo_group_name = old_repo_group_name
132
130
133 old_values = RepoGroupModel()._get_defaults(self.db_repo_group_name)
131 old_values = RepoGroupModel()._get_defaults(self.db_repo_group_name)
134 schema = self._get_schema(c, old_values=old_values)
132 schema = self._get_schema(c, old_values=old_values)
135
133
136 c.form = RcForm(schema)
134 c.form = RcForm(schema)
137 pstruct = list(self.request.POST.items())
135 pstruct = list(self.request.POST.items())
138
136
139 try:
137 try:
140 schema_data = c.form.validate(pstruct)
138 schema_data = c.form.validate(pstruct)
141 except deform.ValidationFailure as err_form:
139 except deform.ValidationFailure as err_form:
142 return self._get_template_context(c)
140 return self._get_template_context(c)
143
141
144 # data is now VALID, proceed with updates
142 # data is now VALID, proceed with updates
145 # save validated data back into the updates dict
143 # save validated data back into the updates dict
146 validated_updates = dict(
144 validated_updates = dict(
147 group_name=schema_data['repo_group']['repo_group_name_without_group'],
145 group_name=schema_data['repo_group']['repo_group_name_without_group'],
148 group_parent_id=schema_data['repo_group']['repo_group_id'],
146 group_parent_id=schema_data['repo_group']['repo_group_id'],
149 user=schema_data['repo_group_owner'],
147 user=schema_data['repo_group_owner'],
150 group_description=schema_data['repo_group_description'],
148 group_description=schema_data['repo_group_description'],
151 enable_locking=schema_data['repo_group_enable_locking'],
149 enable_locking=schema_data['repo_group_enable_locking'],
152 )
150 )
153
151
154 try:
152 try:
155 RepoGroupModel().update(self.db_repo_group, validated_updates)
153 RepoGroupModel().update(self.db_repo_group, validated_updates)
156
154
157 audit_logger.store_web(
155 audit_logger.store_web(
158 'repo_group.edit', action_data={'old_data': old_values},
156 'repo_group.edit', action_data={'old_data': old_values},
159 user=c.rhodecode_user)
157 user=c.rhodecode_user)
160
158
161 Session().commit()
159 Session().commit()
162
160
163 # use the new full name for redirect once we know we updated
161 # use the new full name for redirect once we know we updated
164 # the name on filesystem and in DB
162 # the name on filesystem and in DB
165 new_repo_group_name = schema_data['repo_group']['repo_group_name_with_group']
163 new_repo_group_name = schema_data['repo_group']['repo_group_name_with_group']
166
164
167 h.flash(_('Repository Group `{}` updated successfully').format(
165 h.flash(_('Repository Group `{}` updated successfully').format(
168 old_repo_group_name), category='success')
166 old_repo_group_name), category='success')
169
167
170 except Exception:
168 except Exception:
171 log.exception("Exception during update or repository group")
169 log.exception("Exception during update or repository group")
172 h.flash(_('Error occurred during update of repository group %s')
170 h.flash(_('Error occurred during update of repository group %s')
173 % old_repo_group_name, category='error')
171 % old_repo_group_name, category='error')
174
172
175 name_changed = old_repo_group_name != new_repo_group_name
173 name_changed = old_repo_group_name != new_repo_group_name
176 if name_changed:
174 if name_changed:
177 current_perms = self.db_repo_group.permissions(expand_from_user_groups=True)
175 current_perms = self.db_repo_group.permissions(expand_from_user_groups=True)
178 affected_user_ids = [perm['user_id'] for perm in current_perms]
176 affected_user_ids = [perm['user_id'] for perm in current_perms]
179
177
180 # NOTE(marcink): also add owner maybe it has changed
178 # NOTE(marcink): also add owner maybe it has changed
181 owner = User.get_by_username(schema_data['repo_group_owner'])
179 owner = User.get_by_username(schema_data['repo_group_owner'])
182 owner_id = owner.user_id if owner else self._rhodecode_user.user_id
180 owner_id = owner.user_id if owner else self._rhodecode_user.user_id
183 affected_user_ids.extend([self._rhodecode_user.user_id, owner_id])
181 affected_user_ids.extend([self._rhodecode_user.user_id, owner_id])
184 PermissionModel().trigger_permission_flush(affected_user_ids)
182 PermissionModel().trigger_permission_flush(affected_user_ids)
185
183
186 raise HTTPFound(
184 raise HTTPFound(
187 h.route_path('edit_repo_group', repo_group_name=new_repo_group_name))
185 h.route_path('edit_repo_group', repo_group_name=new_repo_group_name))
@@ -1,1227 +1,1225 b''
1
2
3 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 from rhodecode.apps._base import add_route_with_slash
18 from rhodecode.apps._base import add_route_with_slash
21
19
22
20
23 def includeme(config):
21 def includeme(config):
24 from rhodecode.apps.repository.views.repo_artifacts import RepoArtifactsView
22 from rhodecode.apps.repository.views.repo_artifacts import RepoArtifactsView
25 from rhodecode.apps.repository.views.repo_audit_logs import AuditLogsView
23 from rhodecode.apps.repository.views.repo_audit_logs import AuditLogsView
26 from rhodecode.apps.repository.views.repo_automation import RepoAutomationView
24 from rhodecode.apps.repository.views.repo_automation import RepoAutomationView
27 from rhodecode.apps.repository.views.repo_bookmarks import RepoBookmarksView
25 from rhodecode.apps.repository.views.repo_bookmarks import RepoBookmarksView
28 from rhodecode.apps.repository.views.repo_branch_permissions import RepoSettingsBranchPermissionsView
26 from rhodecode.apps.repository.views.repo_branch_permissions import RepoSettingsBranchPermissionsView
29 from rhodecode.apps.repository.views.repo_branches import RepoBranchesView
27 from rhodecode.apps.repository.views.repo_branches import RepoBranchesView
30 from rhodecode.apps.repository.views.repo_caches import RepoCachesView
28 from rhodecode.apps.repository.views.repo_caches import RepoCachesView
31 from rhodecode.apps.repository.views.repo_changelog import RepoChangelogView
29 from rhodecode.apps.repository.views.repo_changelog import RepoChangelogView
32 from rhodecode.apps.repository.views.repo_checks import RepoChecksView
30 from rhodecode.apps.repository.views.repo_checks import RepoChecksView
33 from rhodecode.apps.repository.views.repo_commits import RepoCommitsView
31 from rhodecode.apps.repository.views.repo_commits import RepoCommitsView
34 from rhodecode.apps.repository.views.repo_compare import RepoCompareView
32 from rhodecode.apps.repository.views.repo_compare import RepoCompareView
35 from rhodecode.apps.repository.views.repo_feed import RepoFeedView
33 from rhodecode.apps.repository.views.repo_feed import RepoFeedView
36 from rhodecode.apps.repository.views.repo_files import RepoFilesView
34 from rhodecode.apps.repository.views.repo_files import RepoFilesView
37 from rhodecode.apps.repository.views.repo_forks import RepoForksView
35 from rhodecode.apps.repository.views.repo_forks import RepoForksView
38 from rhodecode.apps.repository.views.repo_maintainance import RepoMaintenanceView
36 from rhodecode.apps.repository.views.repo_maintainance import RepoMaintenanceView
39 from rhodecode.apps.repository.views.repo_permissions import RepoSettingsPermissionsView
37 from rhodecode.apps.repository.views.repo_permissions import RepoSettingsPermissionsView
40 from rhodecode.apps.repository.views.repo_pull_requests import RepoPullRequestsView
38 from rhodecode.apps.repository.views.repo_pull_requests import RepoPullRequestsView
41 from rhodecode.apps.repository.views.repo_review_rules import RepoReviewRulesView
39 from rhodecode.apps.repository.views.repo_review_rules import RepoReviewRulesView
42 from rhodecode.apps.repository.views.repo_settings import RepoSettingsView
40 from rhodecode.apps.repository.views.repo_settings import RepoSettingsView
43 from rhodecode.apps.repository.views.repo_settings_advanced import RepoSettingsAdvancedView
41 from rhodecode.apps.repository.views.repo_settings_advanced import RepoSettingsAdvancedView
44 from rhodecode.apps.repository.views.repo_settings_fields import RepoSettingsFieldsView
42 from rhodecode.apps.repository.views.repo_settings_fields import RepoSettingsFieldsView
45 from rhodecode.apps.repository.views.repo_settings_issue_trackers import RepoSettingsIssueTrackersView
43 from rhodecode.apps.repository.views.repo_settings_issue_trackers import RepoSettingsIssueTrackersView
46 from rhodecode.apps.repository.views.repo_settings_remote import RepoSettingsRemoteView
44 from rhodecode.apps.repository.views.repo_settings_remote import RepoSettingsRemoteView
47 from rhodecode.apps.repository.views.repo_settings_vcs import RepoSettingsVcsView
45 from rhodecode.apps.repository.views.repo_settings_vcs import RepoSettingsVcsView
48 from rhodecode.apps.repository.views.repo_strip import RepoStripView
46 from rhodecode.apps.repository.views.repo_strip import RepoStripView
49 from rhodecode.apps.repository.views.repo_summary import RepoSummaryView
47 from rhodecode.apps.repository.views.repo_summary import RepoSummaryView
50 from rhodecode.apps.repository.views.repo_tags import RepoTagsView
48 from rhodecode.apps.repository.views.repo_tags import RepoTagsView
51
49
52 # repo creating checks, special cases that aren't repo routes
50 # repo creating checks, special cases that aren't repo routes
53 config.add_route(
51 config.add_route(
54 name='repo_creating',
52 name='repo_creating',
55 pattern='/{repo_name:.*?[^/]}/repo_creating')
53 pattern='/{repo_name:.*?[^/]}/repo_creating')
56 config.add_view(
54 config.add_view(
57 RepoChecksView,
55 RepoChecksView,
58 attr='repo_creating',
56 attr='repo_creating',
59 route_name='repo_creating', request_method='GET',
57 route_name='repo_creating', request_method='GET',
60 renderer='rhodecode:templates/admin/repos/repo_creating.mako')
58 renderer='rhodecode:templates/admin/repos/repo_creating.mako')
61
59
62 config.add_route(
60 config.add_route(
63 name='repo_creating_check',
61 name='repo_creating_check',
64 pattern='/{repo_name:.*?[^/]}/repo_creating_check')
62 pattern='/{repo_name:.*?[^/]}/repo_creating_check')
65 config.add_view(
63 config.add_view(
66 RepoChecksView,
64 RepoChecksView,
67 attr='repo_creating_check',
65 attr='repo_creating_check',
68 route_name='repo_creating_check', request_method='GET',
66 route_name='repo_creating_check', request_method='GET',
69 renderer='json_ext')
67 renderer='json_ext')
70
68
71 # Summary
69 # Summary
72 # NOTE(marcink): one additional route is defined in very bottom, catch
70 # NOTE(marcink): one additional route is defined in very bottom, catch
73 # all pattern
71 # all pattern
74 config.add_route(
72 config.add_route(
75 name='repo_summary_explicit',
73 name='repo_summary_explicit',
76 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
74 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
77 config.add_view(
75 config.add_view(
78 RepoSummaryView,
76 RepoSummaryView,
79 attr='summary',
77 attr='summary',
80 route_name='repo_summary_explicit', request_method='GET',
78 route_name='repo_summary_explicit', request_method='GET',
81 renderer='rhodecode:templates/summary/summary.mako')
79 renderer='rhodecode:templates/summary/summary.mako')
82
80
83 config.add_route(
81 config.add_route(
84 name='repo_summary_commits',
82 name='repo_summary_commits',
85 pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True)
83 pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True)
86 config.add_view(
84 config.add_view(
87 RepoSummaryView,
85 RepoSummaryView,
88 attr='summary_commits',
86 attr='summary_commits',
89 route_name='repo_summary_commits', request_method='GET',
87 route_name='repo_summary_commits', request_method='GET',
90 renderer='rhodecode:templates/summary/summary_commits.mako')
88 renderer='rhodecode:templates/summary/summary_commits.mako')
91
89
92 # Commits
90 # Commits
93 config.add_route(
91 config.add_route(
94 name='repo_commit',
92 name='repo_commit',
95 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
93 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
96 config.add_view(
94 config.add_view(
97 RepoCommitsView,
95 RepoCommitsView,
98 attr='repo_commit_show',
96 attr='repo_commit_show',
99 route_name='repo_commit', request_method='GET',
97 route_name='repo_commit', request_method='GET',
100 renderer=None)
98 renderer=None)
101
99
102 config.add_route(
100 config.add_route(
103 name='repo_commit_children',
101 name='repo_commit_children',
104 pattern='/{repo_name:.*?[^/]}/changeset_children/{commit_id}', repo_route=True)
102 pattern='/{repo_name:.*?[^/]}/changeset_children/{commit_id}', repo_route=True)
105 config.add_view(
103 config.add_view(
106 RepoCommitsView,
104 RepoCommitsView,
107 attr='repo_commit_children',
105 attr='repo_commit_children',
108 route_name='repo_commit_children', request_method='GET',
106 route_name='repo_commit_children', request_method='GET',
109 renderer='json_ext', xhr=True)
107 renderer='json_ext', xhr=True)
110
108
111 config.add_route(
109 config.add_route(
112 name='repo_commit_parents',
110 name='repo_commit_parents',
113 pattern='/{repo_name:.*?[^/]}/changeset_parents/{commit_id}', repo_route=True)
111 pattern='/{repo_name:.*?[^/]}/changeset_parents/{commit_id}', repo_route=True)
114 config.add_view(
112 config.add_view(
115 RepoCommitsView,
113 RepoCommitsView,
116 attr='repo_commit_parents',
114 attr='repo_commit_parents',
117 route_name='repo_commit_parents', request_method='GET',
115 route_name='repo_commit_parents', request_method='GET',
118 renderer='json_ext')
116 renderer='json_ext')
119
117
120 config.add_route(
118 config.add_route(
121 name='repo_commit_raw',
119 name='repo_commit_raw',
122 pattern='/{repo_name:.*?[^/]}/changeset-diff/{commit_id}', repo_route=True)
120 pattern='/{repo_name:.*?[^/]}/changeset-diff/{commit_id}', repo_route=True)
123 config.add_view(
121 config.add_view(
124 RepoCommitsView,
122 RepoCommitsView,
125 attr='repo_commit_raw',
123 attr='repo_commit_raw',
126 route_name='repo_commit_raw', request_method='GET',
124 route_name='repo_commit_raw', request_method='GET',
127 renderer=None)
125 renderer=None)
128
126
129 config.add_route(
127 config.add_route(
130 name='repo_commit_patch',
128 name='repo_commit_patch',
131 pattern='/{repo_name:.*?[^/]}/changeset-patch/{commit_id}', repo_route=True)
129 pattern='/{repo_name:.*?[^/]}/changeset-patch/{commit_id}', repo_route=True)
132 config.add_view(
130 config.add_view(
133 RepoCommitsView,
131 RepoCommitsView,
134 attr='repo_commit_patch',
132 attr='repo_commit_patch',
135 route_name='repo_commit_patch', request_method='GET',
133 route_name='repo_commit_patch', request_method='GET',
136 renderer=None)
134 renderer=None)
137
135
138 config.add_route(
136 config.add_route(
139 name='repo_commit_download',
137 name='repo_commit_download',
140 pattern='/{repo_name:.*?[^/]}/changeset-download/{commit_id}', repo_route=True)
138 pattern='/{repo_name:.*?[^/]}/changeset-download/{commit_id}', repo_route=True)
141 config.add_view(
139 config.add_view(
142 RepoCommitsView,
140 RepoCommitsView,
143 attr='repo_commit_download',
141 attr='repo_commit_download',
144 route_name='repo_commit_download', request_method='GET',
142 route_name='repo_commit_download', request_method='GET',
145 renderer=None)
143 renderer=None)
146
144
147 config.add_route(
145 config.add_route(
148 name='repo_commit_data',
146 name='repo_commit_data',
149 pattern='/{repo_name:.*?[^/]}/changeset-data/{commit_id}', repo_route=True)
147 pattern='/{repo_name:.*?[^/]}/changeset-data/{commit_id}', repo_route=True)
150 config.add_view(
148 config.add_view(
151 RepoCommitsView,
149 RepoCommitsView,
152 attr='repo_commit_data',
150 attr='repo_commit_data',
153 route_name='repo_commit_data', request_method='GET',
151 route_name='repo_commit_data', request_method='GET',
154 renderer='json_ext', xhr=True)
152 renderer='json_ext', xhr=True)
155
153
156 config.add_route(
154 config.add_route(
157 name='repo_commit_comment_create',
155 name='repo_commit_comment_create',
158 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/create', repo_route=True)
156 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/create', repo_route=True)
159 config.add_view(
157 config.add_view(
160 RepoCommitsView,
158 RepoCommitsView,
161 attr='repo_commit_comment_create',
159 attr='repo_commit_comment_create',
162 route_name='repo_commit_comment_create', request_method='POST',
160 route_name='repo_commit_comment_create', request_method='POST',
163 renderer='json_ext')
161 renderer='json_ext')
164
162
165 config.add_route(
163 config.add_route(
166 name='repo_commit_comment_preview',
164 name='repo_commit_comment_preview',
167 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True)
165 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True)
168 config.add_view(
166 config.add_view(
169 RepoCommitsView,
167 RepoCommitsView,
170 attr='repo_commit_comment_preview',
168 attr='repo_commit_comment_preview',
171 route_name='repo_commit_comment_preview', request_method='POST',
169 route_name='repo_commit_comment_preview', request_method='POST',
172 renderer='string', xhr=True)
170 renderer='string', xhr=True)
173
171
174 config.add_route(
172 config.add_route(
175 name='repo_commit_comment_history_view',
173 name='repo_commit_comment_history_view',
176 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/history_view/{comment_history_id}', repo_route=True)
174 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/history_view/{comment_history_id}', repo_route=True)
177 config.add_view(
175 config.add_view(
178 RepoCommitsView,
176 RepoCommitsView,
179 attr='repo_commit_comment_history_view',
177 attr='repo_commit_comment_history_view',
180 route_name='repo_commit_comment_history_view', request_method='POST',
178 route_name='repo_commit_comment_history_view', request_method='POST',
181 renderer='string', xhr=True)
179 renderer='string', xhr=True)
182
180
183 config.add_route(
181 config.add_route(
184 name='repo_commit_comment_attachment_upload',
182 name='repo_commit_comment_attachment_upload',
185 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/attachment_upload', repo_route=True)
183 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/attachment_upload', repo_route=True)
186 config.add_view(
184 config.add_view(
187 RepoCommitsView,
185 RepoCommitsView,
188 attr='repo_commit_comment_attachment_upload',
186 attr='repo_commit_comment_attachment_upload',
189 route_name='repo_commit_comment_attachment_upload', request_method='POST',
187 route_name='repo_commit_comment_attachment_upload', request_method='POST',
190 renderer='json_ext', xhr=True)
188 renderer='json_ext', xhr=True)
191
189
192 config.add_route(
190 config.add_route(
193 name='repo_commit_comment_delete',
191 name='repo_commit_comment_delete',
194 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/delete', repo_route=True)
192 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/delete', repo_route=True)
195 config.add_view(
193 config.add_view(
196 RepoCommitsView,
194 RepoCommitsView,
197 attr='repo_commit_comment_delete',
195 attr='repo_commit_comment_delete',
198 route_name='repo_commit_comment_delete', request_method='POST',
196 route_name='repo_commit_comment_delete', request_method='POST',
199 renderer='json_ext')
197 renderer='json_ext')
200
198
201 config.add_route(
199 config.add_route(
202 name='repo_commit_comment_edit',
200 name='repo_commit_comment_edit',
203 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/edit', repo_route=True)
201 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/edit', repo_route=True)
204 config.add_view(
202 config.add_view(
205 RepoCommitsView,
203 RepoCommitsView,
206 attr='repo_commit_comment_edit',
204 attr='repo_commit_comment_edit',
207 route_name='repo_commit_comment_edit', request_method='POST',
205 route_name='repo_commit_comment_edit', request_method='POST',
208 renderer='json_ext')
206 renderer='json_ext')
209
207
210 # still working url for backward compat.
208 # still working url for backward compat.
211 config.add_route(
209 config.add_route(
212 name='repo_commit_raw_deprecated',
210 name='repo_commit_raw_deprecated',
213 pattern='/{repo_name:.*?[^/]}/raw-changeset/{commit_id}', repo_route=True)
211 pattern='/{repo_name:.*?[^/]}/raw-changeset/{commit_id}', repo_route=True)
214 config.add_view(
212 config.add_view(
215 RepoCommitsView,
213 RepoCommitsView,
216 attr='repo_commit_raw',
214 attr='repo_commit_raw',
217 route_name='repo_commit_raw_deprecated', request_method='GET',
215 route_name='repo_commit_raw_deprecated', request_method='GET',
218 renderer=None)
216 renderer=None)
219
217
220 # Files
218 # Files
221 config.add_route(
219 config.add_route(
222 name='repo_archivefile',
220 name='repo_archivefile',
223 pattern='/{repo_name:.*?[^/]}/archive/{fname:.*}', repo_route=True)
221 pattern='/{repo_name:.*?[^/]}/archive/{fname:.*}', repo_route=True)
224 config.add_view(
222 config.add_view(
225 RepoFilesView,
223 RepoFilesView,
226 attr='repo_archivefile',
224 attr='repo_archivefile',
227 route_name='repo_archivefile', request_method='GET',
225 route_name='repo_archivefile', request_method='GET',
228 renderer=None)
226 renderer=None)
229
227
230 config.add_route(
228 config.add_route(
231 name='repo_files_diff',
229 name='repo_files_diff',
232 pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True)
230 pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True)
233 config.add_view(
231 config.add_view(
234 RepoFilesView,
232 RepoFilesView,
235 attr='repo_files_diff',
233 attr='repo_files_diff',
236 route_name='repo_files_diff', request_method='GET',
234 route_name='repo_files_diff', request_method='GET',
237 renderer=None)
235 renderer=None)
238
236
239 config.add_route( # legacy route to make old links work
237 config.add_route( # legacy route to make old links work
240 name='repo_files_diff_2way_redirect',
238 name='repo_files_diff_2way_redirect',
241 pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True)
239 pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True)
242 config.add_view(
240 config.add_view(
243 RepoFilesView,
241 RepoFilesView,
244 attr='repo_files_diff_2way_redirect',
242 attr='repo_files_diff_2way_redirect',
245 route_name='repo_files_diff_2way_redirect', request_method='GET',
243 route_name='repo_files_diff_2way_redirect', request_method='GET',
246 renderer=None)
244 renderer=None)
247
245
248 config.add_route(
246 config.add_route(
249 name='repo_files',
247 name='repo_files',
250 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True)
248 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True)
251 config.add_view(
249 config.add_view(
252 RepoFilesView,
250 RepoFilesView,
253 attr='repo_files',
251 attr='repo_files',
254 route_name='repo_files', request_method='GET',
252 route_name='repo_files', request_method='GET',
255 renderer=None)
253 renderer=None)
256
254
257 config.add_route(
255 config.add_route(
258 name='repo_files:default_path',
256 name='repo_files:default_path',
259 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True)
257 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True)
260 config.add_view(
258 config.add_view(
261 RepoFilesView,
259 RepoFilesView,
262 attr='repo_files',
260 attr='repo_files',
263 route_name='repo_files:default_path', request_method='GET',
261 route_name='repo_files:default_path', request_method='GET',
264 renderer=None)
262 renderer=None)
265
263
266 config.add_route(
264 config.add_route(
267 name='repo_files:default_commit',
265 name='repo_files:default_commit',
268 pattern='/{repo_name:.*?[^/]}/files', repo_route=True)
266 pattern='/{repo_name:.*?[^/]}/files', repo_route=True)
269 config.add_view(
267 config.add_view(
270 RepoFilesView,
268 RepoFilesView,
271 attr='repo_files',
269 attr='repo_files',
272 route_name='repo_files:default_commit', request_method='GET',
270 route_name='repo_files:default_commit', request_method='GET',
273 renderer=None)
271 renderer=None)
274
272
275 config.add_route(
273 config.add_route(
276 name='repo_files:rendered',
274 name='repo_files:rendered',
277 pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True)
275 pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True)
278 config.add_view(
276 config.add_view(
279 RepoFilesView,
277 RepoFilesView,
280 attr='repo_files',
278 attr='repo_files',
281 route_name='repo_files:rendered', request_method='GET',
279 route_name='repo_files:rendered', request_method='GET',
282 renderer=None)
280 renderer=None)
283
281
284 config.add_route(
282 config.add_route(
285 name='repo_files:annotated',
283 name='repo_files:annotated',
286 pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True)
284 pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True)
287 config.add_view(
285 config.add_view(
288 RepoFilesView,
286 RepoFilesView,
289 attr='repo_files',
287 attr='repo_files',
290 route_name='repo_files:annotated', request_method='GET',
288 route_name='repo_files:annotated', request_method='GET',
291 renderer=None)
289 renderer=None)
292
290
293 config.add_route(
291 config.add_route(
294 name='repo_files:annotated_previous',
292 name='repo_files:annotated_previous',
295 pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True)
293 pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True)
296 config.add_view(
294 config.add_view(
297 RepoFilesView,
295 RepoFilesView,
298 attr='repo_files_annotated_previous',
296 attr='repo_files_annotated_previous',
299 route_name='repo_files:annotated_previous', request_method='GET',
297 route_name='repo_files:annotated_previous', request_method='GET',
300 renderer=None)
298 renderer=None)
301
299
302 config.add_route(
300 config.add_route(
303 name='repo_nodetree_full',
301 name='repo_nodetree_full',
304 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True)
302 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True)
305 config.add_view(
303 config.add_view(
306 RepoFilesView,
304 RepoFilesView,
307 attr='repo_nodetree_full',
305 attr='repo_nodetree_full',
308 route_name='repo_nodetree_full', request_method='GET',
306 route_name='repo_nodetree_full', request_method='GET',
309 renderer=None, xhr=True)
307 renderer=None, xhr=True)
310
308
311 config.add_route(
309 config.add_route(
312 name='repo_nodetree_full:default_path',
310 name='repo_nodetree_full:default_path',
313 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True)
311 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True)
314 config.add_view(
312 config.add_view(
315 RepoFilesView,
313 RepoFilesView,
316 attr='repo_nodetree_full',
314 attr='repo_nodetree_full',
317 route_name='repo_nodetree_full:default_path', request_method='GET',
315 route_name='repo_nodetree_full:default_path', request_method='GET',
318 renderer=None, xhr=True)
316 renderer=None, xhr=True)
319
317
320 config.add_route(
318 config.add_route(
321 name='repo_files_nodelist',
319 name='repo_files_nodelist',
322 pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True)
320 pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True)
323 config.add_view(
321 config.add_view(
324 RepoFilesView,
322 RepoFilesView,
325 attr='repo_nodelist',
323 attr='repo_nodelist',
326 route_name='repo_files_nodelist', request_method='GET',
324 route_name='repo_files_nodelist', request_method='GET',
327 renderer='json_ext', xhr=True)
325 renderer='json_ext', xhr=True)
328
326
329 config.add_route(
327 config.add_route(
330 name='repo_file_raw',
328 name='repo_file_raw',
331 pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True)
329 pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True)
332 config.add_view(
330 config.add_view(
333 RepoFilesView,
331 RepoFilesView,
334 attr='repo_file_raw',
332 attr='repo_file_raw',
335 route_name='repo_file_raw', request_method='GET',
333 route_name='repo_file_raw', request_method='GET',
336 renderer=None)
334 renderer=None)
337
335
338 config.add_route(
336 config.add_route(
339 name='repo_file_download',
337 name='repo_file_download',
340 pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True)
338 pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True)
341 config.add_view(
339 config.add_view(
342 RepoFilesView,
340 RepoFilesView,
343 attr='repo_file_download',
341 attr='repo_file_download',
344 route_name='repo_file_download', request_method='GET',
342 route_name='repo_file_download', request_method='GET',
345 renderer=None)
343 renderer=None)
346
344
347 config.add_route( # backward compat to keep old links working
345 config.add_route( # backward compat to keep old links working
348 name='repo_file_download:legacy',
346 name='repo_file_download:legacy',
349 pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}',
347 pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}',
350 repo_route=True)
348 repo_route=True)
351 config.add_view(
349 config.add_view(
352 RepoFilesView,
350 RepoFilesView,
353 attr='repo_file_download',
351 attr='repo_file_download',
354 route_name='repo_file_download:legacy', request_method='GET',
352 route_name='repo_file_download:legacy', request_method='GET',
355 renderer=None)
353 renderer=None)
356
354
357 config.add_route(
355 config.add_route(
358 name='repo_file_history',
356 name='repo_file_history',
359 pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True)
357 pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True)
360 config.add_view(
358 config.add_view(
361 RepoFilesView,
359 RepoFilesView,
362 attr='repo_file_history',
360 attr='repo_file_history',
363 route_name='repo_file_history', request_method='GET',
361 route_name='repo_file_history', request_method='GET',
364 renderer='json_ext')
362 renderer='json_ext')
365
363
366 config.add_route(
364 config.add_route(
367 name='repo_file_authors',
365 name='repo_file_authors',
368 pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
366 pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
369 config.add_view(
367 config.add_view(
370 RepoFilesView,
368 RepoFilesView,
371 attr='repo_file_authors',
369 attr='repo_file_authors',
372 route_name='repo_file_authors', request_method='GET',
370 route_name='repo_file_authors', request_method='GET',
373 renderer='rhodecode:templates/files/file_authors_box.mako')
371 renderer='rhodecode:templates/files/file_authors_box.mako')
374
372
375 config.add_route(
373 config.add_route(
376 name='repo_files_check_head',
374 name='repo_files_check_head',
377 pattern='/{repo_name:.*?[^/]}/check_head/{commit_id}/{f_path:.*}',
375 pattern='/{repo_name:.*?[^/]}/check_head/{commit_id}/{f_path:.*}',
378 repo_route=True)
376 repo_route=True)
379 config.add_view(
377 config.add_view(
380 RepoFilesView,
378 RepoFilesView,
381 attr='repo_files_check_head',
379 attr='repo_files_check_head',
382 route_name='repo_files_check_head', request_method='POST',
380 route_name='repo_files_check_head', request_method='POST',
383 renderer='json_ext', xhr=True)
381 renderer='json_ext', xhr=True)
384
382
385 config.add_route(
383 config.add_route(
386 name='repo_files_remove_file',
384 name='repo_files_remove_file',
387 pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
385 pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
388 repo_route=True)
386 repo_route=True)
389 config.add_view(
387 config.add_view(
390 RepoFilesView,
388 RepoFilesView,
391 attr='repo_files_remove_file',
389 attr='repo_files_remove_file',
392 route_name='repo_files_remove_file', request_method='GET',
390 route_name='repo_files_remove_file', request_method='GET',
393 renderer='rhodecode:templates/files/files_delete.mako')
391 renderer='rhodecode:templates/files/files_delete.mako')
394
392
395 config.add_route(
393 config.add_route(
396 name='repo_files_delete_file',
394 name='repo_files_delete_file',
397 pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}',
395 pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}',
398 repo_route=True)
396 repo_route=True)
399 config.add_view(
397 config.add_view(
400 RepoFilesView,
398 RepoFilesView,
401 attr='repo_files_delete_file',
399 attr='repo_files_delete_file',
402 route_name='repo_files_delete_file', request_method='POST',
400 route_name='repo_files_delete_file', request_method='POST',
403 renderer=None)
401 renderer=None)
404
402
405 config.add_route(
403 config.add_route(
406 name='repo_files_edit_file',
404 name='repo_files_edit_file',
407 pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}',
405 pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}',
408 repo_route=True)
406 repo_route=True)
409 config.add_view(
407 config.add_view(
410 RepoFilesView,
408 RepoFilesView,
411 attr='repo_files_edit_file',
409 attr='repo_files_edit_file',
412 route_name='repo_files_edit_file', request_method='GET',
410 route_name='repo_files_edit_file', request_method='GET',
413 renderer='rhodecode:templates/files/files_edit.mako')
411 renderer='rhodecode:templates/files/files_edit.mako')
414
412
415 config.add_route(
413 config.add_route(
416 name='repo_files_update_file',
414 name='repo_files_update_file',
417 pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}',
415 pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}',
418 repo_route=True)
416 repo_route=True)
419 config.add_view(
417 config.add_view(
420 RepoFilesView,
418 RepoFilesView,
421 attr='repo_files_update_file',
419 attr='repo_files_update_file',
422 route_name='repo_files_update_file', request_method='POST',
420 route_name='repo_files_update_file', request_method='POST',
423 renderer=None)
421 renderer=None)
424
422
425 config.add_route(
423 config.add_route(
426 name='repo_files_add_file',
424 name='repo_files_add_file',
427 pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}',
425 pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}',
428 repo_route=True)
426 repo_route=True)
429 config.add_view(
427 config.add_view(
430 RepoFilesView,
428 RepoFilesView,
431 attr='repo_files_add_file',
429 attr='repo_files_add_file',
432 route_name='repo_files_add_file', request_method='GET',
430 route_name='repo_files_add_file', request_method='GET',
433 renderer='rhodecode:templates/files/files_add.mako')
431 renderer='rhodecode:templates/files/files_add.mako')
434
432
435 config.add_route(
433 config.add_route(
436 name='repo_files_upload_file',
434 name='repo_files_upload_file',
437 pattern='/{repo_name:.*?[^/]}/upload_file/{commit_id}/{f_path:.*}',
435 pattern='/{repo_name:.*?[^/]}/upload_file/{commit_id}/{f_path:.*}',
438 repo_route=True)
436 repo_route=True)
439 config.add_view(
437 config.add_view(
440 RepoFilesView,
438 RepoFilesView,
441 attr='repo_files_add_file',
439 attr='repo_files_add_file',
442 route_name='repo_files_upload_file', request_method='GET',
440 route_name='repo_files_upload_file', request_method='GET',
443 renderer='rhodecode:templates/files/files_upload.mako')
441 renderer='rhodecode:templates/files/files_upload.mako')
444 config.add_view( # POST creates
442 config.add_view( # POST creates
445 RepoFilesView,
443 RepoFilesView,
446 attr='repo_files_upload_file',
444 attr='repo_files_upload_file',
447 route_name='repo_files_upload_file', request_method='POST',
445 route_name='repo_files_upload_file', request_method='POST',
448 renderer='json_ext')
446 renderer='json_ext')
449
447
450 config.add_route(
448 config.add_route(
451 name='repo_files_create_file',
449 name='repo_files_create_file',
452 pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}',
450 pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}',
453 repo_route=True)
451 repo_route=True)
454 config.add_view( # POST creates
452 config.add_view( # POST creates
455 RepoFilesView,
453 RepoFilesView,
456 attr='repo_files_create_file',
454 attr='repo_files_create_file',
457 route_name='repo_files_create_file', request_method='POST',
455 route_name='repo_files_create_file', request_method='POST',
458 renderer=None)
456 renderer=None)
459
457
460 # Refs data
458 # Refs data
461 config.add_route(
459 config.add_route(
462 name='repo_refs_data',
460 name='repo_refs_data',
463 pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True)
461 pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True)
464 config.add_view(
462 config.add_view(
465 RepoSummaryView,
463 RepoSummaryView,
466 attr='repo_refs_data',
464 attr='repo_refs_data',
467 route_name='repo_refs_data', request_method='GET',
465 route_name='repo_refs_data', request_method='GET',
468 renderer='json_ext')
466 renderer='json_ext')
469
467
470 config.add_route(
468 config.add_route(
471 name='repo_refs_changelog_data',
469 name='repo_refs_changelog_data',
472 pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True)
470 pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True)
473 config.add_view(
471 config.add_view(
474 RepoSummaryView,
472 RepoSummaryView,
475 attr='repo_refs_changelog_data',
473 attr='repo_refs_changelog_data',
476 route_name='repo_refs_changelog_data', request_method='GET',
474 route_name='repo_refs_changelog_data', request_method='GET',
477 renderer='json_ext')
475 renderer='json_ext')
478
476
479 config.add_route(
477 config.add_route(
480 name='repo_stats',
478 name='repo_stats',
481 pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True)
479 pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True)
482 config.add_view(
480 config.add_view(
483 RepoSummaryView,
481 RepoSummaryView,
484 attr='repo_stats',
482 attr='repo_stats',
485 route_name='repo_stats', request_method='GET',
483 route_name='repo_stats', request_method='GET',
486 renderer='json_ext')
484 renderer='json_ext')
487
485
488 # Commits
486 # Commits
489 config.add_route(
487 config.add_route(
490 name='repo_commits',
488 name='repo_commits',
491 pattern='/{repo_name:.*?[^/]}/commits', repo_route=True)
489 pattern='/{repo_name:.*?[^/]}/commits', repo_route=True)
492 config.add_view(
490 config.add_view(
493 RepoChangelogView,
491 RepoChangelogView,
494 attr='repo_changelog',
492 attr='repo_changelog',
495 route_name='repo_commits', request_method='GET',
493 route_name='repo_commits', request_method='GET',
496 renderer='rhodecode:templates/commits/changelog.mako')
494 renderer='rhodecode:templates/commits/changelog.mako')
497 # old routes for backward compat
495 # old routes for backward compat
498 config.add_view(
496 config.add_view(
499 RepoChangelogView,
497 RepoChangelogView,
500 attr='repo_changelog',
498 attr='repo_changelog',
501 route_name='repo_changelog', request_method='GET',
499 route_name='repo_changelog', request_method='GET',
502 renderer='rhodecode:templates/commits/changelog.mako')
500 renderer='rhodecode:templates/commits/changelog.mako')
503
501
504 config.add_route(
502 config.add_route(
505 name='repo_commits_elements',
503 name='repo_commits_elements',
506 pattern='/{repo_name:.*?[^/]}/commits_elements', repo_route=True)
504 pattern='/{repo_name:.*?[^/]}/commits_elements', repo_route=True)
507 config.add_view(
505 config.add_view(
508 RepoChangelogView,
506 RepoChangelogView,
509 attr='repo_commits_elements',
507 attr='repo_commits_elements',
510 route_name='repo_commits_elements', request_method=('GET', 'POST'),
508 route_name='repo_commits_elements', request_method=('GET', 'POST'),
511 renderer='rhodecode:templates/commits/changelog_elements.mako',
509 renderer='rhodecode:templates/commits/changelog_elements.mako',
512 xhr=True)
510 xhr=True)
513
511
514 config.add_route(
512 config.add_route(
515 name='repo_commits_elements_file',
513 name='repo_commits_elements_file',
516 pattern='/{repo_name:.*?[^/]}/commits_elements/{commit_id}/{f_path:.*}', repo_route=True)
514 pattern='/{repo_name:.*?[^/]}/commits_elements/{commit_id}/{f_path:.*}', repo_route=True)
517 config.add_view(
515 config.add_view(
518 RepoChangelogView,
516 RepoChangelogView,
519 attr='repo_commits_elements',
517 attr='repo_commits_elements',
520 route_name='repo_commits_elements_file', request_method=('GET', 'POST'),
518 route_name='repo_commits_elements_file', request_method=('GET', 'POST'),
521 renderer='rhodecode:templates/commits/changelog_elements.mako',
519 renderer='rhodecode:templates/commits/changelog_elements.mako',
522 xhr=True)
520 xhr=True)
523
521
524 config.add_route(
522 config.add_route(
525 name='repo_commits_file',
523 name='repo_commits_file',
526 pattern='/{repo_name:.*?[^/]}/commits/{commit_id}/{f_path:.*}', repo_route=True)
524 pattern='/{repo_name:.*?[^/]}/commits/{commit_id}/{f_path:.*}', repo_route=True)
527 config.add_view(
525 config.add_view(
528 RepoChangelogView,
526 RepoChangelogView,
529 attr='repo_changelog',
527 attr='repo_changelog',
530 route_name='repo_commits_file', request_method='GET',
528 route_name='repo_commits_file', request_method='GET',
531 renderer='rhodecode:templates/commits/changelog.mako')
529 renderer='rhodecode:templates/commits/changelog.mako')
532 # old routes for backward compat
530 # old routes for backward compat
533 config.add_view(
531 config.add_view(
534 RepoChangelogView,
532 RepoChangelogView,
535 attr='repo_changelog',
533 attr='repo_changelog',
536 route_name='repo_changelog_file', request_method='GET',
534 route_name='repo_changelog_file', request_method='GET',
537 renderer='rhodecode:templates/commits/changelog.mako')
535 renderer='rhodecode:templates/commits/changelog.mako')
538
536
539 # Changelog (old deprecated name for commits page)
537 # Changelog (old deprecated name for commits page)
540 config.add_route(
538 config.add_route(
541 name='repo_changelog',
539 name='repo_changelog',
542 pattern='/{repo_name:.*?[^/]}/changelog', repo_route=True)
540 pattern='/{repo_name:.*?[^/]}/changelog', repo_route=True)
543 config.add_route(
541 config.add_route(
544 name='repo_changelog_file',
542 name='repo_changelog_file',
545 pattern='/{repo_name:.*?[^/]}/changelog/{commit_id}/{f_path:.*}', repo_route=True)
543 pattern='/{repo_name:.*?[^/]}/changelog/{commit_id}/{f_path:.*}', repo_route=True)
546
544
547 # Compare
545 # Compare
548 config.add_route(
546 config.add_route(
549 name='repo_compare_select',
547 name='repo_compare_select',
550 pattern='/{repo_name:.*?[^/]}/compare', repo_route=True)
548 pattern='/{repo_name:.*?[^/]}/compare', repo_route=True)
551 config.add_view(
549 config.add_view(
552 RepoCompareView,
550 RepoCompareView,
553 attr='compare_select',
551 attr='compare_select',
554 route_name='repo_compare_select', request_method='GET',
552 route_name='repo_compare_select', request_method='GET',
555 renderer='rhodecode:templates/compare/compare_diff.mako')
553 renderer='rhodecode:templates/compare/compare_diff.mako')
556
554
557 config.add_route(
555 config.add_route(
558 name='repo_compare',
556 name='repo_compare',
559 pattern='/{repo_name:.*?[^/]}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}', repo_route=True)
557 pattern='/{repo_name:.*?[^/]}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}', repo_route=True)
560 config.add_view(
558 config.add_view(
561 RepoCompareView,
559 RepoCompareView,
562 attr='compare',
560 attr='compare',
563 route_name='repo_compare', request_method='GET',
561 route_name='repo_compare', request_method='GET',
564 renderer=None)
562 renderer=None)
565
563
566 # Tags
564 # Tags
567 config.add_route(
565 config.add_route(
568 name='tags_home',
566 name='tags_home',
569 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
567 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
570 config.add_view(
568 config.add_view(
571 RepoTagsView,
569 RepoTagsView,
572 attr='tags',
570 attr='tags',
573 route_name='tags_home', request_method='GET',
571 route_name='tags_home', request_method='GET',
574 renderer='rhodecode:templates/tags/tags.mako')
572 renderer='rhodecode:templates/tags/tags.mako')
575
573
576 # Branches
574 # Branches
577 config.add_route(
575 config.add_route(
578 name='branches_home',
576 name='branches_home',
579 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
577 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
580 config.add_view(
578 config.add_view(
581 RepoBranchesView,
579 RepoBranchesView,
582 attr='branches',
580 attr='branches',
583 route_name='branches_home', request_method='GET',
581 route_name='branches_home', request_method='GET',
584 renderer='rhodecode:templates/branches/branches.mako')
582 renderer='rhodecode:templates/branches/branches.mako')
585
583
586 # Bookmarks
584 # Bookmarks
587 config.add_route(
585 config.add_route(
588 name='bookmarks_home',
586 name='bookmarks_home',
589 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
587 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
590 config.add_view(
588 config.add_view(
591 RepoBookmarksView,
589 RepoBookmarksView,
592 attr='bookmarks',
590 attr='bookmarks',
593 route_name='bookmarks_home', request_method='GET',
591 route_name='bookmarks_home', request_method='GET',
594 renderer='rhodecode:templates/bookmarks/bookmarks.mako')
592 renderer='rhodecode:templates/bookmarks/bookmarks.mako')
595
593
596 # Forks
594 # Forks
597 config.add_route(
595 config.add_route(
598 name='repo_fork_new',
596 name='repo_fork_new',
599 pattern='/{repo_name:.*?[^/]}/fork', repo_route=True,
597 pattern='/{repo_name:.*?[^/]}/fork', repo_route=True,
600 repo_forbid_when_archived=True,
598 repo_forbid_when_archived=True,
601 repo_accepted_types=['hg', 'git'])
599 repo_accepted_types=['hg', 'git'])
602 config.add_view(
600 config.add_view(
603 RepoForksView,
601 RepoForksView,
604 attr='repo_fork_new',
602 attr='repo_fork_new',
605 route_name='repo_fork_new', request_method='GET',
603 route_name='repo_fork_new', request_method='GET',
606 renderer='rhodecode:templates/forks/forks.mako')
604 renderer='rhodecode:templates/forks/forks.mako')
607
605
608 config.add_route(
606 config.add_route(
609 name='repo_fork_create',
607 name='repo_fork_create',
610 pattern='/{repo_name:.*?[^/]}/fork/create', repo_route=True,
608 pattern='/{repo_name:.*?[^/]}/fork/create', repo_route=True,
611 repo_forbid_when_archived=True,
609 repo_forbid_when_archived=True,
612 repo_accepted_types=['hg', 'git'])
610 repo_accepted_types=['hg', 'git'])
613 config.add_view(
611 config.add_view(
614 RepoForksView,
612 RepoForksView,
615 attr='repo_fork_create',
613 attr='repo_fork_create',
616 route_name='repo_fork_create', request_method='POST',
614 route_name='repo_fork_create', request_method='POST',
617 renderer='rhodecode:templates/forks/fork.mako')
615 renderer='rhodecode:templates/forks/fork.mako')
618
616
619 config.add_route(
617 config.add_route(
620 name='repo_forks_show_all',
618 name='repo_forks_show_all',
621 pattern='/{repo_name:.*?[^/]}/forks', repo_route=True,
619 pattern='/{repo_name:.*?[^/]}/forks', repo_route=True,
622 repo_accepted_types=['hg', 'git'])
620 repo_accepted_types=['hg', 'git'])
623 config.add_view(
621 config.add_view(
624 RepoForksView,
622 RepoForksView,
625 attr='repo_forks_show_all',
623 attr='repo_forks_show_all',
626 route_name='repo_forks_show_all', request_method='GET',
624 route_name='repo_forks_show_all', request_method='GET',
627 renderer='rhodecode:templates/forks/forks.mako')
625 renderer='rhodecode:templates/forks/forks.mako')
628
626
629 config.add_route(
627 config.add_route(
630 name='repo_forks_data',
628 name='repo_forks_data',
631 pattern='/{repo_name:.*?[^/]}/forks/data', repo_route=True,
629 pattern='/{repo_name:.*?[^/]}/forks/data', repo_route=True,
632 repo_accepted_types=['hg', 'git'])
630 repo_accepted_types=['hg', 'git'])
633 config.add_view(
631 config.add_view(
634 RepoForksView,
632 RepoForksView,
635 attr='repo_forks_data',
633 attr='repo_forks_data',
636 route_name='repo_forks_data', request_method='GET',
634 route_name='repo_forks_data', request_method='GET',
637 renderer='json_ext', xhr=True)
635 renderer='json_ext', xhr=True)
638
636
639 # Pull Requests
637 # Pull Requests
640 config.add_route(
638 config.add_route(
641 name='pullrequest_show',
639 name='pullrequest_show',
642 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}',
640 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}',
643 repo_route=True)
641 repo_route=True)
644 config.add_view(
642 config.add_view(
645 RepoPullRequestsView,
643 RepoPullRequestsView,
646 attr='pull_request_show',
644 attr='pull_request_show',
647 route_name='pullrequest_show', request_method='GET',
645 route_name='pullrequest_show', request_method='GET',
648 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
646 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
649
647
650 config.add_route(
648 config.add_route(
651 name='pullrequest_show_all',
649 name='pullrequest_show_all',
652 pattern='/{repo_name:.*?[^/]}/pull-request',
650 pattern='/{repo_name:.*?[^/]}/pull-request',
653 repo_route=True, repo_accepted_types=['hg', 'git'])
651 repo_route=True, repo_accepted_types=['hg', 'git'])
654 config.add_view(
652 config.add_view(
655 RepoPullRequestsView,
653 RepoPullRequestsView,
656 attr='pull_request_list',
654 attr='pull_request_list',
657 route_name='pullrequest_show_all', request_method='GET',
655 route_name='pullrequest_show_all', request_method='GET',
658 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
656 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
659
657
660 config.add_route(
658 config.add_route(
661 name='pullrequest_show_all_data',
659 name='pullrequest_show_all_data',
662 pattern='/{repo_name:.*?[^/]}/pull-request-data',
660 pattern='/{repo_name:.*?[^/]}/pull-request-data',
663 repo_route=True, repo_accepted_types=['hg', 'git'])
661 repo_route=True, repo_accepted_types=['hg', 'git'])
664 config.add_view(
662 config.add_view(
665 RepoPullRequestsView,
663 RepoPullRequestsView,
666 attr='pull_request_list_data',
664 attr='pull_request_list_data',
667 route_name='pullrequest_show_all_data', request_method='GET',
665 route_name='pullrequest_show_all_data', request_method='GET',
668 renderer='json_ext', xhr=True)
666 renderer='json_ext', xhr=True)
669
667
670 config.add_route(
668 config.add_route(
671 name='pullrequest_repo_refs',
669 name='pullrequest_repo_refs',
672 pattern='/{repo_name:.*?[^/]}/pull-request/refs/{target_repo_name:.*?[^/]}',
670 pattern='/{repo_name:.*?[^/]}/pull-request/refs/{target_repo_name:.*?[^/]}',
673 repo_route=True)
671 repo_route=True)
674 config.add_view(
672 config.add_view(
675 RepoPullRequestsView,
673 RepoPullRequestsView,
676 attr='pull_request_repo_refs',
674 attr='pull_request_repo_refs',
677 route_name='pullrequest_repo_refs', request_method='GET',
675 route_name='pullrequest_repo_refs', request_method='GET',
678 renderer='json_ext', xhr=True)
676 renderer='json_ext', xhr=True)
679
677
680 config.add_route(
678 config.add_route(
681 name='pullrequest_repo_targets',
679 name='pullrequest_repo_targets',
682 pattern='/{repo_name:.*?[^/]}/pull-request/repo-targets',
680 pattern='/{repo_name:.*?[^/]}/pull-request/repo-targets',
683 repo_route=True)
681 repo_route=True)
684 config.add_view(
682 config.add_view(
685 RepoPullRequestsView,
683 RepoPullRequestsView,
686 attr='pullrequest_repo_targets',
684 attr='pullrequest_repo_targets',
687 route_name='pullrequest_repo_targets', request_method='GET',
685 route_name='pullrequest_repo_targets', request_method='GET',
688 renderer='json_ext', xhr=True)
686 renderer='json_ext', xhr=True)
689
687
690 config.add_route(
688 config.add_route(
691 name='pullrequest_new',
689 name='pullrequest_new',
692 pattern='/{repo_name:.*?[^/]}/pull-request/new',
690 pattern='/{repo_name:.*?[^/]}/pull-request/new',
693 repo_route=True, repo_accepted_types=['hg', 'git'],
691 repo_route=True, repo_accepted_types=['hg', 'git'],
694 repo_forbid_when_archived=True)
692 repo_forbid_when_archived=True)
695 config.add_view(
693 config.add_view(
696 RepoPullRequestsView,
694 RepoPullRequestsView,
697 attr='pull_request_new',
695 attr='pull_request_new',
698 route_name='pullrequest_new', request_method='GET',
696 route_name='pullrequest_new', request_method='GET',
699 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
697 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
700
698
701 config.add_route(
699 config.add_route(
702 name='pullrequest_create',
700 name='pullrequest_create',
703 pattern='/{repo_name:.*?[^/]}/pull-request/create',
701 pattern='/{repo_name:.*?[^/]}/pull-request/create',
704 repo_route=True, repo_accepted_types=['hg', 'git'],
702 repo_route=True, repo_accepted_types=['hg', 'git'],
705 repo_forbid_when_archived=True)
703 repo_forbid_when_archived=True)
706 config.add_view(
704 config.add_view(
707 RepoPullRequestsView,
705 RepoPullRequestsView,
708 attr='pull_request_create',
706 attr='pull_request_create',
709 route_name='pullrequest_create', request_method='POST',
707 route_name='pullrequest_create', request_method='POST',
710 renderer=None)
708 renderer=None)
711
709
712 config.add_route(
710 config.add_route(
713 name='pullrequest_update',
711 name='pullrequest_update',
714 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/update',
712 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/update',
715 repo_route=True, repo_forbid_when_archived=True)
713 repo_route=True, repo_forbid_when_archived=True)
716 config.add_view(
714 config.add_view(
717 RepoPullRequestsView,
715 RepoPullRequestsView,
718 attr='pull_request_update',
716 attr='pull_request_update',
719 route_name='pullrequest_update', request_method='POST',
717 route_name='pullrequest_update', request_method='POST',
720 renderer='json_ext')
718 renderer='json_ext')
721
719
722 config.add_route(
720 config.add_route(
723 name='pullrequest_merge',
721 name='pullrequest_merge',
724 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/merge',
722 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/merge',
725 repo_route=True, repo_forbid_when_archived=True)
723 repo_route=True, repo_forbid_when_archived=True)
726 config.add_view(
724 config.add_view(
727 RepoPullRequestsView,
725 RepoPullRequestsView,
728 attr='pull_request_merge',
726 attr='pull_request_merge',
729 route_name='pullrequest_merge', request_method='POST',
727 route_name='pullrequest_merge', request_method='POST',
730 renderer='json_ext')
728 renderer='json_ext')
731
729
732 config.add_route(
730 config.add_route(
733 name='pullrequest_delete',
731 name='pullrequest_delete',
734 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/delete',
732 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/delete',
735 repo_route=True, repo_forbid_when_archived=True)
733 repo_route=True, repo_forbid_when_archived=True)
736 config.add_view(
734 config.add_view(
737 RepoPullRequestsView,
735 RepoPullRequestsView,
738 attr='pull_request_delete',
736 attr='pull_request_delete',
739 route_name='pullrequest_delete', request_method='POST',
737 route_name='pullrequest_delete', request_method='POST',
740 renderer='json_ext')
738 renderer='json_ext')
741
739
742 config.add_route(
740 config.add_route(
743 name='pullrequest_comment_create',
741 name='pullrequest_comment_create',
744 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment',
742 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment',
745 repo_route=True)
743 repo_route=True)
746 config.add_view(
744 config.add_view(
747 RepoPullRequestsView,
745 RepoPullRequestsView,
748 attr='pull_request_comment_create',
746 attr='pull_request_comment_create',
749 route_name='pullrequest_comment_create', request_method='POST',
747 route_name='pullrequest_comment_create', request_method='POST',
750 renderer='json_ext')
748 renderer='json_ext')
751
749
752 config.add_route(
750 config.add_route(
753 name='pullrequest_comment_edit',
751 name='pullrequest_comment_edit',
754 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/edit',
752 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/edit',
755 repo_route=True, repo_accepted_types=['hg', 'git'])
753 repo_route=True, repo_accepted_types=['hg', 'git'])
756 config.add_view(
754 config.add_view(
757 RepoPullRequestsView,
755 RepoPullRequestsView,
758 attr='pull_request_comment_edit',
756 attr='pull_request_comment_edit',
759 route_name='pullrequest_comment_edit', request_method='POST',
757 route_name='pullrequest_comment_edit', request_method='POST',
760 renderer='json_ext')
758 renderer='json_ext')
761
759
762 config.add_route(
760 config.add_route(
763 name='pullrequest_comment_delete',
761 name='pullrequest_comment_delete',
764 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete',
762 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete',
765 repo_route=True, repo_accepted_types=['hg', 'git'])
763 repo_route=True, repo_accepted_types=['hg', 'git'])
766 config.add_view(
764 config.add_view(
767 RepoPullRequestsView,
765 RepoPullRequestsView,
768 attr='pull_request_comment_delete',
766 attr='pull_request_comment_delete',
769 route_name='pullrequest_comment_delete', request_method='POST',
767 route_name='pullrequest_comment_delete', request_method='POST',
770 renderer='json_ext')
768 renderer='json_ext')
771
769
772 config.add_route(
770 config.add_route(
773 name='pullrequest_comments',
771 name='pullrequest_comments',
774 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comments',
772 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comments',
775 repo_route=True)
773 repo_route=True)
776 config.add_view(
774 config.add_view(
777 RepoPullRequestsView,
775 RepoPullRequestsView,
778 attr='pullrequest_comments',
776 attr='pullrequest_comments',
779 route_name='pullrequest_comments', request_method='POST',
777 route_name='pullrequest_comments', request_method='POST',
780 renderer='string_html', xhr=True)
778 renderer='string_html', xhr=True)
781
779
782 config.add_route(
780 config.add_route(
783 name='pullrequest_todos',
781 name='pullrequest_todos',
784 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/todos',
782 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/todos',
785 repo_route=True)
783 repo_route=True)
786 config.add_view(
784 config.add_view(
787 RepoPullRequestsView,
785 RepoPullRequestsView,
788 attr='pullrequest_todos',
786 attr='pullrequest_todos',
789 route_name='pullrequest_todos', request_method='POST',
787 route_name='pullrequest_todos', request_method='POST',
790 renderer='string_html', xhr=True)
788 renderer='string_html', xhr=True)
791
789
792 config.add_route(
790 config.add_route(
793 name='pullrequest_drafts',
791 name='pullrequest_drafts',
794 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/drafts',
792 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/drafts',
795 repo_route=True)
793 repo_route=True)
796 config.add_view(
794 config.add_view(
797 RepoPullRequestsView,
795 RepoPullRequestsView,
798 attr='pullrequest_drafts',
796 attr='pullrequest_drafts',
799 route_name='pullrequest_drafts', request_method='POST',
797 route_name='pullrequest_drafts', request_method='POST',
800 renderer='string_html', xhr=True)
798 renderer='string_html', xhr=True)
801
799
802 # Artifacts, (EE feature)
800 # Artifacts, (EE feature)
803 config.add_route(
801 config.add_route(
804 name='repo_artifacts_list',
802 name='repo_artifacts_list',
805 pattern='/{repo_name:.*?[^/]}/artifacts', repo_route=True)
803 pattern='/{repo_name:.*?[^/]}/artifacts', repo_route=True)
806 config.add_view(
804 config.add_view(
807 RepoArtifactsView,
805 RepoArtifactsView,
808 attr='repo_artifacts',
806 attr='repo_artifacts',
809 route_name='repo_artifacts_list', request_method='GET',
807 route_name='repo_artifacts_list', request_method='GET',
810 renderer='rhodecode:templates/artifacts/artifact_list.mako')
808 renderer='rhodecode:templates/artifacts/artifact_list.mako')
811
809
812 # Settings
810 # Settings
813 config.add_route(
811 config.add_route(
814 name='edit_repo',
812 name='edit_repo',
815 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
813 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
816 config.add_view(
814 config.add_view(
817 RepoSettingsView,
815 RepoSettingsView,
818 attr='edit_settings',
816 attr='edit_settings',
819 route_name='edit_repo', request_method='GET',
817 route_name='edit_repo', request_method='GET',
820 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
818 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
821 # update is POST on edit_repo
819 # update is POST on edit_repo
822 config.add_view(
820 config.add_view(
823 RepoSettingsView,
821 RepoSettingsView,
824 attr='edit_settings_update',
822 attr='edit_settings_update',
825 route_name='edit_repo', request_method='POST',
823 route_name='edit_repo', request_method='POST',
826 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
824 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
827
825
828 # Settings advanced
826 # Settings advanced
829 config.add_route(
827 config.add_route(
830 name='edit_repo_advanced',
828 name='edit_repo_advanced',
831 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
829 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
832 config.add_view(
830 config.add_view(
833 RepoSettingsAdvancedView,
831 RepoSettingsAdvancedView,
834 attr='edit_advanced',
832 attr='edit_advanced',
835 route_name='edit_repo_advanced', request_method='GET',
833 route_name='edit_repo_advanced', request_method='GET',
836 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
834 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
837
835
838 config.add_route(
836 config.add_route(
839 name='edit_repo_advanced_archive',
837 name='edit_repo_advanced_archive',
840 pattern='/{repo_name:.*?[^/]}/settings/advanced/archive', repo_route=True)
838 pattern='/{repo_name:.*?[^/]}/settings/advanced/archive', repo_route=True)
841 config.add_view(
839 config.add_view(
842 RepoSettingsAdvancedView,
840 RepoSettingsAdvancedView,
843 attr='edit_advanced_archive',
841 attr='edit_advanced_archive',
844 route_name='edit_repo_advanced_archive', request_method='POST',
842 route_name='edit_repo_advanced_archive', request_method='POST',
845 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
843 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
846
844
847 config.add_route(
845 config.add_route(
848 name='edit_repo_advanced_delete',
846 name='edit_repo_advanced_delete',
849 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
847 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
850 config.add_view(
848 config.add_view(
851 RepoSettingsAdvancedView,
849 RepoSettingsAdvancedView,
852 attr='edit_advanced_delete',
850 attr='edit_advanced_delete',
853 route_name='edit_repo_advanced_delete', request_method='POST',
851 route_name='edit_repo_advanced_delete', request_method='POST',
854 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
852 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
855
853
856 config.add_route(
854 config.add_route(
857 name='edit_repo_advanced_locking',
855 name='edit_repo_advanced_locking',
858 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
856 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
859 config.add_view(
857 config.add_view(
860 RepoSettingsAdvancedView,
858 RepoSettingsAdvancedView,
861 attr='edit_advanced_toggle_locking',
859 attr='edit_advanced_toggle_locking',
862 route_name='edit_repo_advanced_locking', request_method='POST',
860 route_name='edit_repo_advanced_locking', request_method='POST',
863 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
861 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
864
862
865 config.add_route(
863 config.add_route(
866 name='edit_repo_advanced_journal',
864 name='edit_repo_advanced_journal',
867 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
865 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
868 config.add_view(
866 config.add_view(
869 RepoSettingsAdvancedView,
867 RepoSettingsAdvancedView,
870 attr='edit_advanced_journal',
868 attr='edit_advanced_journal',
871 route_name='edit_repo_advanced_journal', request_method='POST',
869 route_name='edit_repo_advanced_journal', request_method='POST',
872 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
870 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
873
871
874 config.add_route(
872 config.add_route(
875 name='edit_repo_advanced_fork',
873 name='edit_repo_advanced_fork',
876 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
874 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
877 config.add_view(
875 config.add_view(
878 RepoSettingsAdvancedView,
876 RepoSettingsAdvancedView,
879 attr='edit_advanced_fork',
877 attr='edit_advanced_fork',
880 route_name='edit_repo_advanced_fork', request_method='POST',
878 route_name='edit_repo_advanced_fork', request_method='POST',
881 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
879 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
882
880
883 config.add_route(
881 config.add_route(
884 name='edit_repo_advanced_hooks',
882 name='edit_repo_advanced_hooks',
885 pattern='/{repo_name:.*?[^/]}/settings/advanced/hooks', repo_route=True)
883 pattern='/{repo_name:.*?[^/]}/settings/advanced/hooks', repo_route=True)
886 config.add_view(
884 config.add_view(
887 RepoSettingsAdvancedView,
885 RepoSettingsAdvancedView,
888 attr='edit_advanced_install_hooks',
886 attr='edit_advanced_install_hooks',
889 route_name='edit_repo_advanced_hooks', request_method='GET',
887 route_name='edit_repo_advanced_hooks', request_method='GET',
890 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
888 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
891
889
892 # Caches
890 # Caches
893 config.add_route(
891 config.add_route(
894 name='edit_repo_caches',
892 name='edit_repo_caches',
895 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
893 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
896 config.add_view(
894 config.add_view(
897 RepoCachesView,
895 RepoCachesView,
898 attr='repo_caches',
896 attr='repo_caches',
899 route_name='edit_repo_caches', request_method='GET',
897 route_name='edit_repo_caches', request_method='GET',
900 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
898 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
901 config.add_view(
899 config.add_view(
902 RepoCachesView,
900 RepoCachesView,
903 attr='repo_caches_purge',
901 attr='repo_caches_purge',
904 route_name='edit_repo_caches', request_method='POST')
902 route_name='edit_repo_caches', request_method='POST')
905
903
906 # Permissions
904 # Permissions
907 config.add_route(
905 config.add_route(
908 name='edit_repo_perms',
906 name='edit_repo_perms',
909 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
907 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
910 config.add_view(
908 config.add_view(
911 RepoSettingsPermissionsView,
909 RepoSettingsPermissionsView,
912 attr='edit_permissions',
910 attr='edit_permissions',
913 route_name='edit_repo_perms', request_method='GET',
911 route_name='edit_repo_perms', request_method='GET',
914 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
912 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
915 config.add_view(
913 config.add_view(
916 RepoSettingsPermissionsView,
914 RepoSettingsPermissionsView,
917 attr='edit_permissions_update',
915 attr='edit_permissions_update',
918 route_name='edit_repo_perms', request_method='POST',
916 route_name='edit_repo_perms', request_method='POST',
919 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
917 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
920
918
921 config.add_route(
919 config.add_route(
922 name='edit_repo_perms_set_private',
920 name='edit_repo_perms_set_private',
923 pattern='/{repo_name:.*?[^/]}/settings/permissions/set_private', repo_route=True)
921 pattern='/{repo_name:.*?[^/]}/settings/permissions/set_private', repo_route=True)
924 config.add_view(
922 config.add_view(
925 RepoSettingsPermissionsView,
923 RepoSettingsPermissionsView,
926 attr='edit_permissions_set_private_repo',
924 attr='edit_permissions_set_private_repo',
927 route_name='edit_repo_perms_set_private', request_method='POST',
925 route_name='edit_repo_perms_set_private', request_method='POST',
928 renderer='json_ext')
926 renderer='json_ext')
929
927
930 # Permissions Branch (EE feature)
928 # Permissions Branch (EE feature)
931 config.add_route(
929 config.add_route(
932 name='edit_repo_perms_branch',
930 name='edit_repo_perms_branch',
933 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions', repo_route=True)
931 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions', repo_route=True)
934 config.add_view(
932 config.add_view(
935 RepoSettingsBranchPermissionsView,
933 RepoSettingsBranchPermissionsView,
936 attr='branch_permissions',
934 attr='branch_permissions',
937 route_name='edit_repo_perms_branch', request_method='GET',
935 route_name='edit_repo_perms_branch', request_method='GET',
938 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
936 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
939
937
940 config.add_route(
938 config.add_route(
941 name='edit_repo_perms_branch_delete',
939 name='edit_repo_perms_branch_delete',
942 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions/{rule_id}/delete',
940 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions/{rule_id}/delete',
943 repo_route=True)
941 repo_route=True)
944 ## Only implemented in EE
942 ## Only implemented in EE
945
943
946 # Maintenance
944 # Maintenance
947 config.add_route(
945 config.add_route(
948 name='edit_repo_maintenance',
946 name='edit_repo_maintenance',
949 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
947 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
950 config.add_view(
948 config.add_view(
951 RepoMaintenanceView,
949 RepoMaintenanceView,
952 attr='repo_maintenance',
950 attr='repo_maintenance',
953 route_name='edit_repo_maintenance', request_method='GET',
951 route_name='edit_repo_maintenance', request_method='GET',
954 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
952 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
955
953
956 config.add_route(
954 config.add_route(
957 name='edit_repo_maintenance_execute',
955 name='edit_repo_maintenance_execute',
958 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
956 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
959 config.add_view(
957 config.add_view(
960 RepoMaintenanceView,
958 RepoMaintenanceView,
961 attr='repo_maintenance_execute',
959 attr='repo_maintenance_execute',
962 route_name='edit_repo_maintenance_execute', request_method='GET',
960 route_name='edit_repo_maintenance_execute', request_method='GET',
963 renderer='json', xhr=True)
961 renderer='json', xhr=True)
964
962
965 # Fields
963 # Fields
966 config.add_route(
964 config.add_route(
967 name='edit_repo_fields',
965 name='edit_repo_fields',
968 pattern='/{repo_name:.*?[^/]}/settings/fields', repo_route=True)
966 pattern='/{repo_name:.*?[^/]}/settings/fields', repo_route=True)
969 config.add_view(
967 config.add_view(
970 RepoSettingsFieldsView,
968 RepoSettingsFieldsView,
971 attr='repo_field_edit',
969 attr='repo_field_edit',
972 route_name='edit_repo_fields', request_method='GET',
970 route_name='edit_repo_fields', request_method='GET',
973 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
971 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
974
972
975 config.add_route(
973 config.add_route(
976 name='edit_repo_fields_create',
974 name='edit_repo_fields_create',
977 pattern='/{repo_name:.*?[^/]}/settings/fields/create', repo_route=True)
975 pattern='/{repo_name:.*?[^/]}/settings/fields/create', repo_route=True)
978 config.add_view(
976 config.add_view(
979 RepoSettingsFieldsView,
977 RepoSettingsFieldsView,
980 attr='repo_field_create',
978 attr='repo_field_create',
981 route_name='edit_repo_fields_create', request_method='POST',
979 route_name='edit_repo_fields_create', request_method='POST',
982 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
980 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
983
981
984 config.add_route(
982 config.add_route(
985 name='edit_repo_fields_delete',
983 name='edit_repo_fields_delete',
986 pattern='/{repo_name:.*?[^/]}/settings/fields/{field_id}/delete', repo_route=True)
984 pattern='/{repo_name:.*?[^/]}/settings/fields/{field_id}/delete', repo_route=True)
987 config.add_view(
985 config.add_view(
988 RepoSettingsFieldsView,
986 RepoSettingsFieldsView,
989 attr='repo_field_delete',
987 attr='repo_field_delete',
990 route_name='edit_repo_fields_delete', request_method='POST',
988 route_name='edit_repo_fields_delete', request_method='POST',
991 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
989 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
992
990
993 # quick actions: locking
991 # quick actions: locking
994 config.add_route(
992 config.add_route(
995 name='repo_settings_quick_actions',
993 name='repo_settings_quick_actions',
996 pattern='/{repo_name:.*?[^/]}/settings/quick-action', repo_route=True)
994 pattern='/{repo_name:.*?[^/]}/settings/quick-action', repo_route=True)
997 config.add_view(
995 config.add_view(
998 RepoSettingsView,
996 RepoSettingsView,
999 attr='repo_settings_quick_actions',
997 attr='repo_settings_quick_actions',
1000 route_name='repo_settings_quick_actions', request_method='GET',
998 route_name='repo_settings_quick_actions', request_method='GET',
1001 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
999 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1002
1000
1003 # Remote
1001 # Remote
1004 config.add_route(
1002 config.add_route(
1005 name='edit_repo_remote',
1003 name='edit_repo_remote',
1006 pattern='/{repo_name:.*?[^/]}/settings/remote', repo_route=True)
1004 pattern='/{repo_name:.*?[^/]}/settings/remote', repo_route=True)
1007 config.add_view(
1005 config.add_view(
1008 RepoSettingsRemoteView,
1006 RepoSettingsRemoteView,
1009 attr='repo_remote_edit_form',
1007 attr='repo_remote_edit_form',
1010 route_name='edit_repo_remote', request_method='GET',
1008 route_name='edit_repo_remote', request_method='GET',
1011 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1009 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1012
1010
1013 config.add_route(
1011 config.add_route(
1014 name='edit_repo_remote_pull',
1012 name='edit_repo_remote_pull',
1015 pattern='/{repo_name:.*?[^/]}/settings/remote/pull', repo_route=True)
1013 pattern='/{repo_name:.*?[^/]}/settings/remote/pull', repo_route=True)
1016 config.add_view(
1014 config.add_view(
1017 RepoSettingsRemoteView,
1015 RepoSettingsRemoteView,
1018 attr='repo_remote_pull_changes',
1016 attr='repo_remote_pull_changes',
1019 route_name='edit_repo_remote_pull', request_method='POST',
1017 route_name='edit_repo_remote_pull', request_method='POST',
1020 renderer=None)
1018 renderer=None)
1021
1019
1022 config.add_route(
1020 config.add_route(
1023 name='edit_repo_remote_push',
1021 name='edit_repo_remote_push',
1024 pattern='/{repo_name:.*?[^/]}/settings/remote/push', repo_route=True)
1022 pattern='/{repo_name:.*?[^/]}/settings/remote/push', repo_route=True)
1025
1023
1026 # Statistics
1024 # Statistics
1027 config.add_route(
1025 config.add_route(
1028 name='edit_repo_statistics',
1026 name='edit_repo_statistics',
1029 pattern='/{repo_name:.*?[^/]}/settings/statistics', repo_route=True)
1027 pattern='/{repo_name:.*?[^/]}/settings/statistics', repo_route=True)
1030 config.add_view(
1028 config.add_view(
1031 RepoSettingsView,
1029 RepoSettingsView,
1032 attr='edit_statistics_form',
1030 attr='edit_statistics_form',
1033 route_name='edit_repo_statistics', request_method='GET',
1031 route_name='edit_repo_statistics', request_method='GET',
1034 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1032 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1035
1033
1036 config.add_route(
1034 config.add_route(
1037 name='edit_repo_statistics_reset',
1035 name='edit_repo_statistics_reset',
1038 pattern='/{repo_name:.*?[^/]}/settings/statistics/update', repo_route=True)
1036 pattern='/{repo_name:.*?[^/]}/settings/statistics/update', repo_route=True)
1039 config.add_view(
1037 config.add_view(
1040 RepoSettingsView,
1038 RepoSettingsView,
1041 attr='repo_statistics_reset',
1039 attr='repo_statistics_reset',
1042 route_name='edit_repo_statistics_reset', request_method='POST',
1040 route_name='edit_repo_statistics_reset', request_method='POST',
1043 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1041 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1044
1042
1045 # Issue trackers
1043 # Issue trackers
1046 config.add_route(
1044 config.add_route(
1047 name='edit_repo_issuetracker',
1045 name='edit_repo_issuetracker',
1048 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers', repo_route=True)
1046 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers', repo_route=True)
1049 config.add_view(
1047 config.add_view(
1050 RepoSettingsIssueTrackersView,
1048 RepoSettingsIssueTrackersView,
1051 attr='repo_issuetracker',
1049 attr='repo_issuetracker',
1052 route_name='edit_repo_issuetracker', request_method='GET',
1050 route_name='edit_repo_issuetracker', request_method='GET',
1053 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1051 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1054
1052
1055 config.add_route(
1053 config.add_route(
1056 name='edit_repo_issuetracker_test',
1054 name='edit_repo_issuetracker_test',
1057 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/test', repo_route=True)
1055 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/test', repo_route=True)
1058 config.add_view(
1056 config.add_view(
1059 RepoSettingsIssueTrackersView,
1057 RepoSettingsIssueTrackersView,
1060 attr='repo_issuetracker_test',
1058 attr='repo_issuetracker_test',
1061 route_name='edit_repo_issuetracker_test', request_method='POST',
1059 route_name='edit_repo_issuetracker_test', request_method='POST',
1062 renderer='string', xhr=True)
1060 renderer='string', xhr=True)
1063
1061
1064 config.add_route(
1062 config.add_route(
1065 name='edit_repo_issuetracker_delete',
1063 name='edit_repo_issuetracker_delete',
1066 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/delete', repo_route=True)
1064 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/delete', repo_route=True)
1067 config.add_view(
1065 config.add_view(
1068 RepoSettingsIssueTrackersView,
1066 RepoSettingsIssueTrackersView,
1069 attr='repo_issuetracker_delete',
1067 attr='repo_issuetracker_delete',
1070 route_name='edit_repo_issuetracker_delete', request_method='POST',
1068 route_name='edit_repo_issuetracker_delete', request_method='POST',
1071 renderer='json_ext', xhr=True)
1069 renderer='json_ext', xhr=True)
1072
1070
1073 config.add_route(
1071 config.add_route(
1074 name='edit_repo_issuetracker_update',
1072 name='edit_repo_issuetracker_update',
1075 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/update', repo_route=True)
1073 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/update', repo_route=True)
1076 config.add_view(
1074 config.add_view(
1077 RepoSettingsIssueTrackersView,
1075 RepoSettingsIssueTrackersView,
1078 attr='repo_issuetracker_update',
1076 attr='repo_issuetracker_update',
1079 route_name='edit_repo_issuetracker_update', request_method='POST',
1077 route_name='edit_repo_issuetracker_update', request_method='POST',
1080 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1078 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1081
1079
1082 # VCS Settings
1080 # VCS Settings
1083 config.add_route(
1081 config.add_route(
1084 name='edit_repo_vcs',
1082 name='edit_repo_vcs',
1085 pattern='/{repo_name:.*?[^/]}/settings/vcs', repo_route=True)
1083 pattern='/{repo_name:.*?[^/]}/settings/vcs', repo_route=True)
1086 config.add_view(
1084 config.add_view(
1087 RepoSettingsVcsView,
1085 RepoSettingsVcsView,
1088 attr='repo_vcs_settings',
1086 attr='repo_vcs_settings',
1089 route_name='edit_repo_vcs', request_method='GET',
1087 route_name='edit_repo_vcs', request_method='GET',
1090 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1088 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1091
1089
1092 config.add_route(
1090 config.add_route(
1093 name='edit_repo_vcs_update',
1091 name='edit_repo_vcs_update',
1094 pattern='/{repo_name:.*?[^/]}/settings/vcs/update', repo_route=True)
1092 pattern='/{repo_name:.*?[^/]}/settings/vcs/update', repo_route=True)
1095 config.add_view(
1093 config.add_view(
1096 RepoSettingsVcsView,
1094 RepoSettingsVcsView,
1097 attr='repo_settings_vcs_update',
1095 attr='repo_settings_vcs_update',
1098 route_name='edit_repo_vcs_update', request_method='POST',
1096 route_name='edit_repo_vcs_update', request_method='POST',
1099 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1097 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1100
1098
1101 # svn pattern
1099 # svn pattern
1102 config.add_route(
1100 config.add_route(
1103 name='edit_repo_vcs_svn_pattern_delete',
1101 name='edit_repo_vcs_svn_pattern_delete',
1104 pattern='/{repo_name:.*?[^/]}/settings/vcs/svn_pattern/delete', repo_route=True)
1102 pattern='/{repo_name:.*?[^/]}/settings/vcs/svn_pattern/delete', repo_route=True)
1105 config.add_view(
1103 config.add_view(
1106 RepoSettingsVcsView,
1104 RepoSettingsVcsView,
1107 attr='repo_settings_delete_svn_pattern',
1105 attr='repo_settings_delete_svn_pattern',
1108 route_name='edit_repo_vcs_svn_pattern_delete', request_method='POST',
1106 route_name='edit_repo_vcs_svn_pattern_delete', request_method='POST',
1109 renderer='json_ext', xhr=True)
1107 renderer='json_ext', xhr=True)
1110
1108
1111 # Repo Review Rules (EE feature)
1109 # Repo Review Rules (EE feature)
1112 config.add_route(
1110 config.add_route(
1113 name='repo_reviewers',
1111 name='repo_reviewers',
1114 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
1112 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
1115 config.add_view(
1113 config.add_view(
1116 RepoReviewRulesView,
1114 RepoReviewRulesView,
1117 attr='repo_review_rules',
1115 attr='repo_review_rules',
1118 route_name='repo_reviewers', request_method='GET',
1116 route_name='repo_reviewers', request_method='GET',
1119 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1117 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1120
1118
1121 config.add_route(
1119 config.add_route(
1122 name='repo_default_reviewers_data',
1120 name='repo_default_reviewers_data',
1123 pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True)
1121 pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True)
1124 config.add_view(
1122 config.add_view(
1125 RepoReviewRulesView,
1123 RepoReviewRulesView,
1126 attr='repo_default_reviewers_data',
1124 attr='repo_default_reviewers_data',
1127 route_name='repo_default_reviewers_data', request_method='GET',
1125 route_name='repo_default_reviewers_data', request_method='GET',
1128 renderer='json_ext')
1126 renderer='json_ext')
1129
1127
1130 # Repo Automation (EE feature)
1128 # Repo Automation (EE feature)
1131 config.add_route(
1129 config.add_route(
1132 name='repo_automation',
1130 name='repo_automation',
1133 pattern='/{repo_name:.*?[^/]}/settings/automation', repo_route=True)
1131 pattern='/{repo_name:.*?[^/]}/settings/automation', repo_route=True)
1134 config.add_view(
1132 config.add_view(
1135 RepoAutomationView,
1133 RepoAutomationView,
1136 attr='repo_automation',
1134 attr='repo_automation',
1137 route_name='repo_automation', request_method='GET',
1135 route_name='repo_automation', request_method='GET',
1138 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1136 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1139
1137
1140 # Strip
1138 # Strip
1141 config.add_route(
1139 config.add_route(
1142 name='edit_repo_strip',
1140 name='edit_repo_strip',
1143 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
1141 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
1144 config.add_view(
1142 config.add_view(
1145 RepoStripView,
1143 RepoStripView,
1146 attr='strip',
1144 attr='strip',
1147 route_name='edit_repo_strip', request_method='GET',
1145 route_name='edit_repo_strip', request_method='GET',
1148 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1146 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1149
1147
1150 config.add_route(
1148 config.add_route(
1151 name='strip_check',
1149 name='strip_check',
1152 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
1150 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
1153 config.add_view(
1151 config.add_view(
1154 RepoStripView,
1152 RepoStripView,
1155 attr='strip_check',
1153 attr='strip_check',
1156 route_name='strip_check', request_method='POST',
1154 route_name='strip_check', request_method='POST',
1157 renderer='json', xhr=True)
1155 renderer='json', xhr=True)
1158
1156
1159 config.add_route(
1157 config.add_route(
1160 name='strip_execute',
1158 name='strip_execute',
1161 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
1159 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
1162 config.add_view(
1160 config.add_view(
1163 RepoStripView,
1161 RepoStripView,
1164 attr='strip_execute',
1162 attr='strip_execute',
1165 route_name='strip_execute', request_method='POST',
1163 route_name='strip_execute', request_method='POST',
1166 renderer='json', xhr=True)
1164 renderer='json', xhr=True)
1167
1165
1168 # Audit logs
1166 # Audit logs
1169 config.add_route(
1167 config.add_route(
1170 name='edit_repo_audit_logs',
1168 name='edit_repo_audit_logs',
1171 pattern='/{repo_name:.*?[^/]}/settings/audit_logs', repo_route=True)
1169 pattern='/{repo_name:.*?[^/]}/settings/audit_logs', repo_route=True)
1172 config.add_view(
1170 config.add_view(
1173 AuditLogsView,
1171 AuditLogsView,
1174 attr='repo_audit_logs',
1172 attr='repo_audit_logs',
1175 route_name='edit_repo_audit_logs', request_method='GET',
1173 route_name='edit_repo_audit_logs', request_method='GET',
1176 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1174 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1177
1175
1178 # ATOM/RSS Feed, shouldn't contain slashes for outlook compatibility
1176 # ATOM/RSS Feed, shouldn't contain slashes for outlook compatibility
1179 config.add_route(
1177 config.add_route(
1180 name='rss_feed_home',
1178 name='rss_feed_home',
1181 pattern='/{repo_name:.*?[^/]}/feed-rss', repo_route=True)
1179 pattern='/{repo_name:.*?[^/]}/feed-rss', repo_route=True)
1182 config.add_view(
1180 config.add_view(
1183 RepoFeedView,
1181 RepoFeedView,
1184 attr='rss',
1182 attr='rss',
1185 route_name='rss_feed_home', request_method='GET', renderer=None)
1183 route_name='rss_feed_home', request_method='GET', renderer=None)
1186
1184
1187 config.add_route(
1185 config.add_route(
1188 name='rss_feed_home_old',
1186 name='rss_feed_home_old',
1189 pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True)
1187 pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True)
1190 config.add_view(
1188 config.add_view(
1191 RepoFeedView,
1189 RepoFeedView,
1192 attr='rss',
1190 attr='rss',
1193 route_name='rss_feed_home_old', request_method='GET', renderer=None)
1191 route_name='rss_feed_home_old', request_method='GET', renderer=None)
1194
1192
1195 config.add_route(
1193 config.add_route(
1196 name='atom_feed_home',
1194 name='atom_feed_home',
1197 pattern='/{repo_name:.*?[^/]}/feed-atom', repo_route=True)
1195 pattern='/{repo_name:.*?[^/]}/feed-atom', repo_route=True)
1198 config.add_view(
1196 config.add_view(
1199 RepoFeedView,
1197 RepoFeedView,
1200 attr='atom',
1198 attr='atom',
1201 route_name='atom_feed_home', request_method='GET', renderer=None)
1199 route_name='atom_feed_home', request_method='GET', renderer=None)
1202
1200
1203 config.add_route(
1201 config.add_route(
1204 name='atom_feed_home_old',
1202 name='atom_feed_home_old',
1205 pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True)
1203 pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True)
1206 config.add_view(
1204 config.add_view(
1207 RepoFeedView,
1205 RepoFeedView,
1208 attr='atom',
1206 attr='atom',
1209 route_name='atom_feed_home_old', request_method='GET', renderer=None)
1207 route_name='atom_feed_home_old', request_method='GET', renderer=None)
1210
1208
1211 # NOTE(marcink): needs to be at the end for catch-all
1209 # NOTE(marcink): needs to be at the end for catch-all
1212 add_route_with_slash(
1210 add_route_with_slash(
1213 config,
1211 config,
1214 name='repo_summary',
1212 name='repo_summary',
1215 pattern='/{repo_name:.*?[^/]}', repo_route=True)
1213 pattern='/{repo_name:.*?[^/]}', repo_route=True)
1216 config.add_view(
1214 config.add_view(
1217 RepoSummaryView,
1215 RepoSummaryView,
1218 attr='summary',
1216 attr='summary',
1219 route_name='repo_summary', request_method='GET',
1217 route_name='repo_summary', request_method='GET',
1220 renderer='rhodecode:templates/summary/summary.mako')
1218 renderer='rhodecode:templates/summary/summary.mako')
1221
1219
1222 # TODO(marcink): there's no such route??
1220 # TODO(marcink): there's no such route??
1223 config.add_view(
1221 config.add_view(
1224 RepoSummaryView,
1222 RepoSummaryView,
1225 attr='summary',
1223 attr='summary',
1226 route_name='repo_summary_slash', request_method='GET',
1224 route_name='repo_summary_slash', request_method='GET',
1227 renderer='rhodecode:templates/summary/summary.mako') No newline at end of file
1225 renderer='rhodecode:templates/summary/summary.mako')
@@ -1,18 +1,17 b''
1
2 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
2 #
4 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
7 #
6 #
8 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
10 # GNU General Public License for more details.
12 #
11 #
13 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
14 #
16 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -1,113 +1,111 b''
1
2
3 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 from rhodecode.lib import helpers as h, rc_cache
19 from rhodecode.lib import helpers as h, rc_cache
22 from rhodecode.lib.utils2 import safe_int
20 from rhodecode.lib.utils2 import safe_int
23 from rhodecode.model.pull_request import get_diff_info
21 from rhodecode.model.pull_request import get_diff_info
24 from rhodecode.model.db import PullRequestReviewers
22 from rhodecode.model.db import PullRequestReviewers
25 # V3 - Reviewers, with default rules data
23 # V3 - Reviewers, with default rules data
26 # v4 - Added observers metadata
24 # v4 - Added observers metadata
27 # v5 - pr_author/commit_author include/exclude logic
25 # v5 - pr_author/commit_author include/exclude logic
28 REVIEWER_API_VERSION = 'V5'
26 REVIEWER_API_VERSION = 'V5'
29
27
30
28
31 def reviewer_as_json(user, reasons=None, role=None, mandatory=False, rules=None, user_group=None):
29 def reviewer_as_json(user, reasons=None, role=None, mandatory=False, rules=None, user_group=None):
32 """
30 """
33 Returns json struct of a reviewer for frontend
31 Returns json struct of a reviewer for frontend
34
32
35 :param user: the reviewer
33 :param user: the reviewer
36 :param reasons: list of strings of why they are reviewers
34 :param reasons: list of strings of why they are reviewers
37 :param mandatory: bool, to set user as mandatory
35 :param mandatory: bool, to set user as mandatory
38 """
36 """
39 role = role or PullRequestReviewers.ROLE_REVIEWER
37 role = role or PullRequestReviewers.ROLE_REVIEWER
40 if role not in PullRequestReviewers.ROLES:
38 if role not in PullRequestReviewers.ROLES:
41 raise ValueError('role is not one of %s', PullRequestReviewers.ROLES)
39 raise ValueError('role is not one of %s', PullRequestReviewers.ROLES)
42
40
43 return {
41 return {
44 'user_id': user.user_id,
42 'user_id': user.user_id,
45 'reasons': reasons or [],
43 'reasons': reasons or [],
46 'rules': rules or [],
44 'rules': rules or [],
47 'role': role,
45 'role': role,
48 'mandatory': mandatory,
46 'mandatory': mandatory,
49 'user_group': user_group,
47 'user_group': user_group,
50 'username': user.username,
48 'username': user.username,
51 'first_name': user.first_name,
49 'first_name': user.first_name,
52 'last_name': user.last_name,
50 'last_name': user.last_name,
53 'user_link': h.link_to_user(user),
51 'user_link': h.link_to_user(user),
54 'gravatar_link': h.gravatar_url(user.email, 14),
52 'gravatar_link': h.gravatar_url(user.email, 14),
55 }
53 }
56
54
57
55
58 def to_reviewers(e):
56 def to_reviewers(e):
59 if isinstance(e, (tuple, list)):
57 if isinstance(e, (tuple, list)):
60 return map(reviewer_as_json, e)
58 return map(reviewer_as_json, e)
61 else:
59 else:
62 return reviewer_as_json(e)
60 return reviewer_as_json(e)
63
61
64
62
65 def get_default_reviewers_data(current_user, source_repo, source_ref, target_repo, target_ref,
63 def get_default_reviewers_data(current_user, source_repo, source_ref, target_repo, target_ref,
66 include_diff_info=True):
64 include_diff_info=True):
67 """
65 """
68 Return json for default reviewers of a repository
66 Return json for default reviewers of a repository
69 """
67 """
70
68
71 diff_info = {}
69 diff_info = {}
72 if include_diff_info:
70 if include_diff_info:
73 diff_info = get_diff_info(
71 diff_info = get_diff_info(
74 source_repo, source_ref.commit_id, target_repo, target_ref.commit_id)
72 source_repo, source_ref.commit_id, target_repo, target_ref.commit_id)
75
73
76 reasons = ['Default reviewer', 'Repository owner']
74 reasons = ['Default reviewer', 'Repository owner']
77 json_reviewers = [reviewer_as_json(
75 json_reviewers = [reviewer_as_json(
78 user=target_repo.user, reasons=reasons, mandatory=False, rules=None, role=None)]
76 user=target_repo.user, reasons=reasons, mandatory=False, rules=None, role=None)]
79
77
80 compute_key = rc_cache.utils.compute_key_from_params(
78 compute_key = rc_cache.utils.compute_key_from_params(
81 current_user.user_id, source_repo.repo_id, source_ref.type, source_ref.name,
79 current_user.user_id, source_repo.repo_id, source_ref.type, source_ref.name,
82 source_ref.commit_id, target_repo.repo_id, target_ref.type, target_ref.name,
80 source_ref.commit_id, target_repo.repo_id, target_ref.type, target_ref.name,
83 target_ref.commit_id)
81 target_ref.commit_id)
84
82
85 return {
83 return {
86 'api_ver': REVIEWER_API_VERSION, # define version for later possible schema upgrade
84 'api_ver': REVIEWER_API_VERSION, # define version for later possible schema upgrade
87 'compute_key': compute_key,
85 'compute_key': compute_key,
88 'diff_info': diff_info,
86 'diff_info': diff_info,
89 'reviewers': json_reviewers,
87 'reviewers': json_reviewers,
90 'rules': {},
88 'rules': {},
91 'rules_data': {},
89 'rules_data': {},
92 'rules_humanized': [],
90 'rules_humanized': [],
93 }
91 }
94
92
95
93
96 def validate_default_reviewers(review_members, reviewer_rules):
94 def validate_default_reviewers(review_members, reviewer_rules):
97 """
95 """
98 Function to validate submitted reviewers against the saved rules
96 Function to validate submitted reviewers against the saved rules
99 """
97 """
100 reviewers = []
98 reviewers = []
101 reviewer_by_id = {}
99 reviewer_by_id = {}
102 for r in review_members:
100 for r in review_members:
103 reviewer_user_id = safe_int(r['user_id'])
101 reviewer_user_id = safe_int(r['user_id'])
104 entry = (reviewer_user_id, r['reasons'], r['mandatory'], r['role'], r['rules'])
102 entry = (reviewer_user_id, r['reasons'], r['mandatory'], r['role'], r['rules'])
105
103
106 reviewer_by_id[reviewer_user_id] = entry
104 reviewer_by_id[reviewer_user_id] = entry
107 reviewers.append(entry)
105 reviewers.append(entry)
108
106
109 return reviewers
107 return reviewers
110
108
111
109
112 def validate_observers(observer_members, reviewer_rules):
110 def validate_observers(observer_members, reviewer_rules):
113 return {}
111 return {}
@@ -1,19 +1,17 b''
1
2
3 # Copyright (C) 2011-2023 RhodeCode GmbH
1 # Copyright (C) 2011-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -1,45 +1,43 b''
1
2
3 # Copyright (C) 2011-2023 RhodeCode GmbH
1 # Copyright (C) 2011-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 import logging
19 import logging
22
20
23
21
24
22
25 from rhodecode.apps._base import RepoAppView
23 from rhodecode.apps._base import RepoAppView
26 from rhodecode.lib.auth import (
24 from rhodecode.lib.auth import (
27 LoginRequired, HasRepoPermissionAnyDecorator)
25 LoginRequired, HasRepoPermissionAnyDecorator)
28
26
29 log = logging.getLogger(__name__)
27 log = logging.getLogger(__name__)
30
28
31
29
32 class RepoArtifactsView(RepoAppView):
30 class RepoArtifactsView(RepoAppView):
33
31
34 def load_default_context(self):
32 def load_default_context(self):
35 c = self._get_local_tmpl_context(include_app_defaults=True)
33 c = self._get_local_tmpl_context(include_app_defaults=True)
36 c.rhodecode_repo = self.rhodecode_vcs_repo
34 c.rhodecode_repo = self.rhodecode_vcs_repo
37 return c
35 return c
38
36
39 @LoginRequired()
37 @LoginRequired()
40 @HasRepoPermissionAnyDecorator(
38 @HasRepoPermissionAnyDecorator(
41 'repository.read', 'repository.write', 'repository.admin')
39 'repository.read', 'repository.write', 'repository.admin')
42 def repo_artifacts(self):
40 def repo_artifacts(self):
43 c = self.load_default_context()
41 c = self.load_default_context()
44 c.active = 'artifacts'
42 c.active = 'artifacts'
45 return self._get_template_context(c)
43 return self._get_template_context(c)
@@ -1,64 +1,62 b''
1
2
3 # Copyright (C) 2017-2023 RhodeCode GmbH
1 # Copyright (C) 2017-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 import logging
19 import logging
22
20
23
21
24 from rhodecode.apps._base import RepoAppView
22 from rhodecode.apps._base import RepoAppView
25 from rhodecode.lib.helpers import SqlPage
23 from rhodecode.lib.helpers import SqlPage
26 from rhodecode.lib import helpers as h
24 from rhodecode.lib import helpers as h
27 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
25 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
28 from rhodecode.lib.utils2 import safe_int
26 from rhodecode.lib.utils2 import safe_int
29 from rhodecode.model.repo import RepoModel
27 from rhodecode.model.repo import RepoModel
30
28
31 log = logging.getLogger(__name__)
29 log = logging.getLogger(__name__)
32
30
33
31
34 class AuditLogsView(RepoAppView):
32 class AuditLogsView(RepoAppView):
35 def load_default_context(self):
33 def load_default_context(self):
36 c = self._get_local_tmpl_context()
34 c = self._get_local_tmpl_context()
37 return c
35 return c
38
36
39 @LoginRequired()
37 @LoginRequired()
40 @HasRepoPermissionAnyDecorator('repository.admin')
38 @HasRepoPermissionAnyDecorator('repository.admin')
41 def repo_audit_logs(self):
39 def repo_audit_logs(self):
42 _ = self.request.translate
40 _ = self.request.translate
43 c = self.load_default_context()
41 c = self.load_default_context()
44 c.db_repo = self.db_repo
42 c.db_repo = self.db_repo
45
43
46 c.active = 'audit'
44 c.active = 'audit'
47
45
48 p = safe_int(self.request.GET.get('page', 1), 1)
46 p = safe_int(self.request.GET.get('page', 1), 1)
49
47
50 filter_term = self.request.GET.get('filter')
48 filter_term = self.request.GET.get('filter')
51 user_log = RepoModel().get_repo_log(c.db_repo, filter_term)
49 user_log = RepoModel().get_repo_log(c.db_repo, filter_term)
52
50
53 def url_generator(page_num):
51 def url_generator(page_num):
54 query_params = {
52 query_params = {
55 'page': page_num
53 'page': page_num
56 }
54 }
57 if filter_term:
55 if filter_term:
58 query_params['filter'] = filter_term
56 query_params['filter'] = filter_term
59 return self.request.current_route_path(_query=query_params)
57 return self.request.current_route_path(_query=query_params)
60
58
61 c.audit_logs = SqlPage(
59 c.audit_logs = SqlPage(
62 user_log, page=p, items_per_page=10, url_maker=url_generator)
60 user_log, page=p, items_per_page=10, url_maker=url_generator)
63 c.filter_term = filter_term
61 c.filter_term = filter_term
64 return self._get_template_context(c)
62 return self._get_template_context(c)
@@ -1,43 +1,41 b''
1
2
3 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 import logging
19 import logging
22
20
23
21
24
22
25 from rhodecode.apps._base import RepoAppView
23 from rhodecode.apps._base import RepoAppView
26 from rhodecode.apps.repository.utils import get_default_reviewers_data
24 from rhodecode.apps.repository.utils import get_default_reviewers_data
27 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
25 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
28
26
29 log = logging.getLogger(__name__)
27 log = logging.getLogger(__name__)
30
28
31
29
32 class RepoAutomationView(RepoAppView):
30 class RepoAutomationView(RepoAppView):
33 def load_default_context(self):
31 def load_default_context(self):
34 c = self._get_local_tmpl_context()
32 c = self._get_local_tmpl_context()
35 return c
33 return c
36
34
37 @LoginRequired()
35 @LoginRequired()
38 @HasRepoPermissionAnyDecorator('repository.admin')
36 @HasRepoPermissionAnyDecorator('repository.admin')
39 def repo_automation(self):
37 def repo_automation(self):
40 c = self.load_default_context()
38 c = self.load_default_context()
41 c.active = 'automation'
39 c.active = 'automation'
42
40
43 return self._get_template_context(c)
41 return self._get_template_context(c)
@@ -1,53 +1,51 b''
1
2
3 # Copyright (C) 2011-2023 RhodeCode GmbH
1 # Copyright (C) 2011-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 import logging
18 import logging
21
19
22 from pyramid.httpexceptions import HTTPNotFound
20 from pyramid.httpexceptions import HTTPNotFound
23
21
24 from rhodecode.apps._base import BaseReferencesView
22 from rhodecode.apps._base import BaseReferencesView
25 from rhodecode.lib import ext_json
23 from rhodecode.lib import ext_json
26 from rhodecode.lib import helpers as h
24 from rhodecode.lib import helpers as h
27 from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator)
25 from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator)
28 from rhodecode.model.scm import ScmModel
26 from rhodecode.model.scm import ScmModel
29
27
30 log = logging.getLogger(__name__)
28 log = logging.getLogger(__name__)
31
29
32
30
33 class RepoBookmarksView(BaseReferencesView):
31 class RepoBookmarksView(BaseReferencesView):
34
32
35 @LoginRequired()
33 @LoginRequired()
36 @HasRepoPermissionAnyDecorator(
34 @HasRepoPermissionAnyDecorator(
37 'repository.read', 'repository.write', 'repository.admin')
35 'repository.read', 'repository.write', 'repository.admin')
38 def bookmarks(self):
36 def bookmarks(self):
39 c = self.load_default_context()
37 c = self.load_default_context()
40 self._prepare_and_set_clone_url(c)
38 self._prepare_and_set_clone_url(c)
41 c.rhodecode_repo = self.rhodecode_vcs_repo
39 c.rhodecode_repo = self.rhodecode_vcs_repo
42 c.repository_forks = ScmModel().get_forks(self.db_repo)
40 c.repository_forks = ScmModel().get_forks(self.db_repo)
43
41
44 if not h.is_hg(self.db_repo):
42 if not h.is_hg(self.db_repo):
45 raise HTTPNotFound()
43 raise HTTPNotFound()
46
44
47 ref_items = self.rhodecode_vcs_repo.bookmarks.items()
45 ref_items = self.rhodecode_vcs_repo.bookmarks.items()
48 data = self.load_refs_context(
46 data = self.load_refs_context(
49 ref_items=ref_items, partials_template='bookmarks/bookmarks_data.mako')
47 ref_items=ref_items, partials_template='bookmarks/bookmarks_data.mako')
50
48
51 c.has_references = bool(data)
49 c.has_references = bool(data)
52 c.data = ext_json.str_json(data)
50 c.data = ext_json.str_json(data)
53 return self._get_template_context(c)
51 return self._get_template_context(c)
@@ -1,42 +1,40 b''
1
2
3 # Copyright (C) 2011-2023 RhodeCode GmbH
1 # Copyright (C) 2011-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 import logging
19 import logging
22
20
23
21
24
22
25 from rhodecode.apps._base import RepoAppView
23 from rhodecode.apps._base import RepoAppView
26 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
24 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
27
25
28 log = logging.getLogger(__name__)
26 log = logging.getLogger(__name__)
29
27
30
28
31 class RepoSettingsBranchPermissionsView(RepoAppView):
29 class RepoSettingsBranchPermissionsView(RepoAppView):
32
30
33 def load_default_context(self):
31 def load_default_context(self):
34 c = self._get_local_tmpl_context()
32 c = self._get_local_tmpl_context()
35 return c
33 return c
36
34
37 @LoginRequired()
35 @LoginRequired()
38 @HasRepoPermissionAnyDecorator('repository.admin')
36 @HasRepoPermissionAnyDecorator('repository.admin')
39 def branch_permissions(self):
37 def branch_permissions(self):
40 c = self.load_default_context()
38 c = self.load_default_context()
41 c.active = 'permissions_branch'
39 c.active = 'permissions_branch'
42 return self._get_template_context(c)
40 return self._get_template_context(c)
@@ -1,49 +1,47 b''
1
2
3 # Copyright (C) 2011-2023 RhodeCode GmbH
1 # Copyright (C) 2011-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 import logging
19 import logging
22
20
23
21
24 from rhodecode.apps._base import BaseReferencesView
22 from rhodecode.apps._base import BaseReferencesView
25 from rhodecode.lib import ext_json
23 from rhodecode.lib import ext_json
26 from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator)
24 from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator)
27 from rhodecode.model.scm import ScmModel
25 from rhodecode.model.scm import ScmModel
28
26
29 log = logging.getLogger(__name__)
27 log = logging.getLogger(__name__)
30
28
31
29
32 class RepoBranchesView(BaseReferencesView):
30 class RepoBranchesView(BaseReferencesView):
33
31
34 @LoginRequired()
32 @LoginRequired()
35 @HasRepoPermissionAnyDecorator(
33 @HasRepoPermissionAnyDecorator(
36 'repository.read', 'repository.write', 'repository.admin')
34 'repository.read', 'repository.write', 'repository.admin')
37 def branches(self):
35 def branches(self):
38 c = self.load_default_context()
36 c = self.load_default_context()
39 self._prepare_and_set_clone_url(c)
37 self._prepare_and_set_clone_url(c)
40 c.rhodecode_repo = self.rhodecode_vcs_repo
38 c.rhodecode_repo = self.rhodecode_vcs_repo
41 c.repository_forks = ScmModel().get_forks(self.db_repo)
39 c.repository_forks = ScmModel().get_forks(self.db_repo)
42
40
43 ref_items = self.rhodecode_vcs_repo.branches_all.items()
41 ref_items = self.rhodecode_vcs_repo.branches_all.items()
44 data = self.load_refs_context(
42 data = self.load_refs_context(
45 ref_items=ref_items, partials_template='branches/branches_data.mako')
43 ref_items=ref_items, partials_template='branches/branches_data.mako')
46
44
47 c.has_references = bool(data)
45 c.has_references = bool(data)
48 c.data = ext_json.str_json(data)
46 c.data = ext_json.str_json(data)
49 return self._get_template_context(c)
47 return self._get_template_context(c)
@@ -1,93 +1,91 b''
1
2
3 # Copyright (C) 2011-2023 RhodeCode GmbH
1 # Copyright (C) 2011-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 import os
19 import os
22 import logging
20 import logging
23
21
24 from pyramid.httpexceptions import HTTPFound
22 from pyramid.httpexceptions import HTTPFound
25
23
26
24
27 from rhodecode.apps._base import RepoAppView
25 from rhodecode.apps._base import RepoAppView
28 from rhodecode.lib.auth import (
26 from rhodecode.lib.auth import (
29 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
27 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
30 from rhodecode.lib import helpers as h, rc_cache
28 from rhodecode.lib import helpers as h, rc_cache
31 from rhodecode.lib import system_info
29 from rhodecode.lib import system_info
32 from rhodecode.model.meta import Session
30 from rhodecode.model.meta import Session
33 from rhodecode.model.scm import ScmModel
31 from rhodecode.model.scm import ScmModel
34
32
35 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
36
34
37
35
38 class RepoCachesView(RepoAppView):
36 class RepoCachesView(RepoAppView):
39 def load_default_context(self):
37 def load_default_context(self):
40 c = self._get_local_tmpl_context()
38 c = self._get_local_tmpl_context()
41 return c
39 return c
42
40
43 @LoginRequired()
41 @LoginRequired()
44 @HasRepoPermissionAnyDecorator('repository.admin')
42 @HasRepoPermissionAnyDecorator('repository.admin')
45 def repo_caches(self):
43 def repo_caches(self):
46 c = self.load_default_context()
44 c = self.load_default_context()
47 c.active = 'caches'
45 c.active = 'caches'
48 cached_diffs_dir = c.rhodecode_db_repo.cached_diffs_dir
46 cached_diffs_dir = c.rhodecode_db_repo.cached_diffs_dir
49 c.cached_diff_count = len(c.rhodecode_db_repo.cached_diffs())
47 c.cached_diff_count = len(c.rhodecode_db_repo.cached_diffs())
50 c.cached_diff_size = 0
48 c.cached_diff_size = 0
51 if os.path.isdir(cached_diffs_dir):
49 if os.path.isdir(cached_diffs_dir):
52 c.cached_diff_size = system_info.get_storage_size(cached_diffs_dir)
50 c.cached_diff_size = system_info.get_storage_size(cached_diffs_dir)
53 c.shadow_repos = c.rhodecode_db_repo.shadow_repos()
51 c.shadow_repos = c.rhodecode_db_repo.shadow_repos()
54
52
55 cache_namespace_uid = 'repo.{}'.format(self.db_repo.repo_id)
53 cache_namespace_uid = f'repo.{self.db_repo.repo_id}'
56 c.region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
54 c.region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
57 c.backend = c.region.backend
55 c.backend = c.region.backend
58 c.repo_keys = sorted(c.region.backend.list_keys(prefix=cache_namespace_uid))
56 c.repo_keys = sorted(c.region.backend.list_keys(prefix=cache_namespace_uid))
59
57
60 return self._get_template_context(c)
58 return self._get_template_context(c)
61
59
62 @LoginRequired()
60 @LoginRequired()
63 @HasRepoPermissionAnyDecorator('repository.admin')
61 @HasRepoPermissionAnyDecorator('repository.admin')
64 @CSRFRequired()
62 @CSRFRequired()
65 def repo_caches_purge(self):
63 def repo_caches_purge(self):
66 _ = self.request.translate
64 _ = self.request.translate
67 c = self.load_default_context()
65 c = self.load_default_context()
68 c.active = 'caches'
66 c.active = 'caches'
69 invalidated = 0
67 invalidated = 0
70
68
71 try:
69 try:
72 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
70 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
73 Session().commit()
71 Session().commit()
74 invalidated +=1
72 invalidated +=1
75 except Exception:
73 except Exception:
76 log.exception("Exception during cache invalidation")
74 log.exception("Exception during cache invalidation")
77 h.flash(_('An error occurred during cache invalidation'),
75 h.flash(_('An error occurred during cache invalidation'),
78 category='error')
76 category='error')
79
77
80 try:
78 try:
81 invalidated += 1
79 invalidated += 1
82 self.rhodecode_vcs_repo.vcsserver_invalidate_cache(delete=True)
80 self.rhodecode_vcs_repo.vcsserver_invalidate_cache(delete=True)
83 except Exception:
81 except Exception:
84 log.exception("Exception during vcsserver cache invalidation")
82 log.exception("Exception during vcsserver cache invalidation")
85 h.flash(_('An error occurred during vcsserver cache invalidation'),
83 h.flash(_('An error occurred during vcsserver cache invalidation'),
86 category='error')
84 category='error')
87
85
88 if invalidated:
86 if invalidated:
89 h.flash(_('Cache invalidation successful. Stages {}/2').format(invalidated),
87 h.flash(_('Cache invalidation successful. Stages {}/2').format(invalidated),
90 category='success')
88 category='success')
91
89
92 raise HTTPFound(h.route_path(
90 raise HTTPFound(h.route_path(
93 'edit_repo_caches', repo_name=self.db_repo_name)) No newline at end of file
91 'edit_repo_caches', repo_name=self.db_repo_name))
@@ -1,356 +1,355 b''
1
2 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
2 #
4 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
7 #
6 #
8 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
10 # GNU General Public License for more details.
12 #
11 #
13 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
14 #
16 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
18
20
19
21 import logging
20 import logging
22
21
23 from pyramid.httpexceptions import HTTPNotFound, HTTPFound
22 from pyramid.httpexceptions import HTTPNotFound, HTTPFound
24
23
25 from pyramid.renderers import render
24 from pyramid.renderers import render
26 from pyramid.response import Response
25 from pyramid.response import Response
27
26
28 from rhodecode.apps._base import RepoAppView
27 from rhodecode.apps._base import RepoAppView
29 import rhodecode.lib.helpers as h
28 import rhodecode.lib.helpers as h
30 from rhodecode.lib import ext_json
29 from rhodecode.lib import ext_json
31 from rhodecode.lib.auth import (
30 from rhodecode.lib.auth import (
32 LoginRequired, HasRepoPermissionAnyDecorator)
31 LoginRequired, HasRepoPermissionAnyDecorator)
33
32
34 from rhodecode.lib.graphmod import _colored, _dagwalker
33 from rhodecode.lib.graphmod import _colored, _dagwalker
35 from rhodecode.lib.helpers import RepoPage
34 from rhodecode.lib.helpers import RepoPage
36 from rhodecode.lib.utils2 import str2bool
35 from rhodecode.lib.utils2 import str2bool
37 from rhodecode.lib.str_utils import safe_int, safe_str
36 from rhodecode.lib.str_utils import safe_int, safe_str
38 from rhodecode.lib.vcs.exceptions import (
37 from rhodecode.lib.vcs.exceptions import (
39 RepositoryError, CommitDoesNotExistError,
38 RepositoryError, CommitDoesNotExistError,
40 CommitError, NodeDoesNotExistError, EmptyRepositoryError)
39 CommitError, NodeDoesNotExistError, EmptyRepositoryError)
41
40
42 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
43
42
44 DEFAULT_CHANGELOG_SIZE = 20
43 DEFAULT_CHANGELOG_SIZE = 20
45
44
46
45
47 class RepoChangelogView(RepoAppView):
46 class RepoChangelogView(RepoAppView):
48
47
49 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
48 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
50 """
49 """
51 This is a safe way to get commit. If an error occurs it redirects to
50 This is a safe way to get commit. If an error occurs it redirects to
52 tip with proper message
51 tip with proper message
53
52
54 :param commit_id: id of commit to fetch
53 :param commit_id: id of commit to fetch
55 :param redirect_after: toggle redirection
54 :param redirect_after: toggle redirection
56 """
55 """
57 _ = self.request.translate
56 _ = self.request.translate
58
57
59 try:
58 try:
60 return self.rhodecode_vcs_repo.get_commit(commit_id)
59 return self.rhodecode_vcs_repo.get_commit(commit_id)
61 except EmptyRepositoryError:
60 except EmptyRepositoryError:
62 if not redirect_after:
61 if not redirect_after:
63 return None
62 return None
64
63
65 h.flash(h.literal(
64 h.flash(h.literal(
66 _('There are no commits yet')), category='warning')
65 _('There are no commits yet')), category='warning')
67 raise HTTPFound(
66 raise HTTPFound(
68 h.route_path('repo_summary', repo_name=self.db_repo_name))
67 h.route_path('repo_summary', repo_name=self.db_repo_name))
69
68
70 except (CommitDoesNotExistError, LookupError):
69 except (CommitDoesNotExistError, LookupError):
71 msg = _('No such commit exists for this repository')
70 msg = _('No such commit exists for this repository')
72 h.flash(msg, category='error')
71 h.flash(msg, category='error')
73 raise HTTPNotFound()
72 raise HTTPNotFound()
74 except RepositoryError as e:
73 except RepositoryError as e:
75 h.flash(h.escape(safe_str(e)), category='error')
74 h.flash(h.escape(safe_str(e)), category='error')
76 raise HTTPNotFound()
75 raise HTTPNotFound()
77
76
78 def _graph(self, repo, commits, prev_data=None, next_data=None):
77 def _graph(self, repo, commits, prev_data=None, next_data=None):
79 """
78 """
80 Generates a DAG graph for repo
79 Generates a DAG graph for repo
81
80
82 :param repo: repo instance
81 :param repo: repo instance
83 :param commits: list of commits
82 :param commits: list of commits
84 """
83 """
85 if not commits:
84 if not commits:
86 return ext_json.str_json([]), ext_json.str_json([])
85 return ext_json.str_json([]), ext_json.str_json([])
87
86
88 def serialize(commit, parents=True):
87 def serialize(commit, parents=True):
89 data = dict(
88 data = dict(
90 raw_id=commit.raw_id,
89 raw_id=commit.raw_id,
91 idx=commit.idx,
90 idx=commit.idx,
92 branch=None,
91 branch=None,
93 )
92 )
94 if parents:
93 if parents:
95 data['parents'] = [
94 data['parents'] = [
96 serialize(x, parents=False) for x in commit.parents]
95 serialize(x, parents=False) for x in commit.parents]
97 return data
96 return data
98
97
99 prev_data = prev_data or []
98 prev_data = prev_data or []
100 next_data = next_data or []
99 next_data = next_data or []
101
100
102 current = [serialize(x) for x in commits]
101 current = [serialize(x) for x in commits]
103
102
104 commits = prev_data + current + next_data
103 commits = prev_data + current + next_data
105
104
106 dag = _dagwalker(repo, commits)
105 dag = _dagwalker(repo, commits)
107
106
108 data = [[commit_id, vtx, edges, branch]
107 data = [[commit_id, vtx, edges, branch]
109 for commit_id, vtx, edges, branch in _colored(dag)]
108 for commit_id, vtx, edges, branch in _colored(dag)]
110 return ext_json.str_json(data), ext_json.str_json(current)
109 return ext_json.str_json(data), ext_json.str_json(current)
111
110
112 def _check_if_valid_branch(self, branch_name, repo_name, f_path):
111 def _check_if_valid_branch(self, branch_name, repo_name, f_path):
113 if branch_name not in self.rhodecode_vcs_repo.branches_all:
112 if branch_name not in self.rhodecode_vcs_repo.branches_all:
114 h.flash(u'Branch {} is not found.'.format(h.escape(safe_str(branch_name))),
113 h.flash(f'Branch {h.escape(safe_str(branch_name))} is not found.',
115 category='warning')
114 category='warning')
116 redirect_url = h.route_path(
115 redirect_url = h.route_path(
117 'repo_commits_file', repo_name=repo_name,
116 'repo_commits_file', repo_name=repo_name,
118 commit_id=branch_name, f_path=f_path or '')
117 commit_id=branch_name, f_path=f_path or '')
119 raise HTTPFound(redirect_url)
118 raise HTTPFound(redirect_url)
120
119
121 def _load_changelog_data(
120 def _load_changelog_data(
122 self, c, collection, page, chunk_size, branch_name=None,
121 self, c, collection, page, chunk_size, branch_name=None,
123 dynamic=False, f_path=None, commit_id=None):
122 dynamic=False, f_path=None, commit_id=None):
124
123
125 def url_generator(page_num):
124 def url_generator(page_num):
126 query_params = {
125 query_params = {
127 'page': page_num
126 'page': page_num
128 }
127 }
129
128
130 if branch_name:
129 if branch_name:
131 query_params.update({
130 query_params.update({
132 'branch': branch_name
131 'branch': branch_name
133 })
132 })
134
133
135 if f_path:
134 if f_path:
136 # changelog for file
135 # changelog for file
137 return h.route_path(
136 return h.route_path(
138 'repo_commits_file',
137 'repo_commits_file',
139 repo_name=c.rhodecode_db_repo.repo_name,
138 repo_name=c.rhodecode_db_repo.repo_name,
140 commit_id=commit_id, f_path=f_path,
139 commit_id=commit_id, f_path=f_path,
141 _query=query_params)
140 _query=query_params)
142 else:
141 else:
143 return h.route_path(
142 return h.route_path(
144 'repo_commits',
143 'repo_commits',
145 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
144 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
146
145
147 c.total_cs = len(collection)
146 c.total_cs = len(collection)
148 c.showing_commits = min(chunk_size, c.total_cs)
147 c.showing_commits = min(chunk_size, c.total_cs)
149 c.pagination = RepoPage(collection, page=page, item_count=c.total_cs,
148 c.pagination = RepoPage(collection, page=page, item_count=c.total_cs,
150 items_per_page=chunk_size, url_maker=url_generator)
149 items_per_page=chunk_size, url_maker=url_generator)
151
150
152 c.next_page = c.pagination.next_page
151 c.next_page = c.pagination.next_page
153 c.prev_page = c.pagination.previous_page
152 c.prev_page = c.pagination.previous_page
154
153
155 if dynamic:
154 if dynamic:
156 if self.request.GET.get('chunk') != 'next':
155 if self.request.GET.get('chunk') != 'next':
157 c.next_page = None
156 c.next_page = None
158 if self.request.GET.get('chunk') != 'prev':
157 if self.request.GET.get('chunk') != 'prev':
159 c.prev_page = None
158 c.prev_page = None
160
159
161 page_commit_ids = [x.raw_id for x in c.pagination]
160 page_commit_ids = [x.raw_id for x in c.pagination]
162 c.comments = c.rhodecode_db_repo.get_comments(page_commit_ids)
161 c.comments = c.rhodecode_db_repo.get_comments(page_commit_ids)
163 c.statuses = c.rhodecode_db_repo.statuses(page_commit_ids)
162 c.statuses = c.rhodecode_db_repo.statuses(page_commit_ids)
164
163
165 def load_default_context(self):
164 def load_default_context(self):
166 c = self._get_local_tmpl_context(include_app_defaults=True)
165 c = self._get_local_tmpl_context(include_app_defaults=True)
167
166
168 c.rhodecode_repo = self.rhodecode_vcs_repo
167 c.rhodecode_repo = self.rhodecode_vcs_repo
169
168
170 return c
169 return c
171
170
172 @LoginRequired()
171 @LoginRequired()
173 @HasRepoPermissionAnyDecorator(
172 @HasRepoPermissionAnyDecorator(
174 'repository.read', 'repository.write', 'repository.admin')
173 'repository.read', 'repository.write', 'repository.admin')
175 def repo_changelog(self):
174 def repo_changelog(self):
176 c = self.load_default_context()
175 c = self.load_default_context()
177
176
178 commit_id = self.request.matchdict.get('commit_id')
177 commit_id = self.request.matchdict.get('commit_id')
179 f_path = self._get_f_path(self.request.matchdict)
178 f_path = self._get_f_path(self.request.matchdict)
180 show_hidden = str2bool(self.request.GET.get('evolve'))
179 show_hidden = str2bool(self.request.GET.get('evolve'))
181
180
182 chunk_size = 20
181 chunk_size = 20
183
182
184 c.branch_name = branch_name = self.request.GET.get('branch') or ''
183 c.branch_name = branch_name = self.request.GET.get('branch') or ''
185 c.book_name = book_name = self.request.GET.get('bookmark') or ''
184 c.book_name = book_name = self.request.GET.get('bookmark') or ''
186 c.f_path = f_path
185 c.f_path = f_path
187 c.commit_id = commit_id
186 c.commit_id = commit_id
188 c.show_hidden = show_hidden
187 c.show_hidden = show_hidden
189
188
190 hist_limit = safe_int(self.request.GET.get('limit')) or None
189 hist_limit = safe_int(self.request.GET.get('limit')) or None
191
190
192 p = safe_int(self.request.GET.get('page', 1), 1)
191 p = safe_int(self.request.GET.get('page', 1), 1)
193
192
194 c.selected_name = branch_name or book_name
193 c.selected_name = branch_name or book_name
195 if not commit_id and branch_name:
194 if not commit_id and branch_name:
196 self._check_if_valid_branch(branch_name, self.db_repo_name, f_path)
195 self._check_if_valid_branch(branch_name, self.db_repo_name, f_path)
197
196
198 c.changelog_for_path = f_path
197 c.changelog_for_path = f_path
199 pre_load = self.get_commit_preload_attrs()
198 pre_load = self.get_commit_preload_attrs()
200
199
201 partial_xhr = self.request.environ.get('HTTP_X_PARTIAL_XHR')
200 partial_xhr = self.request.environ.get('HTTP_X_PARTIAL_XHR')
202
201
203 try:
202 try:
204 if f_path:
203 if f_path:
205 log.debug('generating changelog for path %s', f_path)
204 log.debug('generating changelog for path %s', f_path)
206 # get the history for the file !
205 # get the history for the file !
207 base_commit = self.rhodecode_vcs_repo.get_commit(commit_id)
206 base_commit = self.rhodecode_vcs_repo.get_commit(commit_id)
208
207
209 try:
208 try:
210 collection = base_commit.get_path_history(
209 collection = base_commit.get_path_history(
211 f_path, limit=hist_limit, pre_load=pre_load)
210 f_path, limit=hist_limit, pre_load=pre_load)
212 if collection and partial_xhr:
211 if collection and partial_xhr:
213 # for ajax call we remove first one since we're looking
212 # for ajax call we remove first one since we're looking
214 # at it right now in the context of a file commit
213 # at it right now in the context of a file commit
215 collection.pop(0)
214 collection.pop(0)
216 except (NodeDoesNotExistError, CommitError):
215 except (NodeDoesNotExistError, CommitError):
217 # this node is not present at tip!
216 # this node is not present at tip!
218 try:
217 try:
219 commit = self._get_commit_or_redirect(commit_id)
218 commit = self._get_commit_or_redirect(commit_id)
220 collection = commit.get_path_history(f_path)
219 collection = commit.get_path_history(f_path)
221 except RepositoryError as e:
220 except RepositoryError as e:
222 h.flash(safe_str(e), category='warning')
221 h.flash(safe_str(e), category='warning')
223 redirect_url = h.route_path(
222 redirect_url = h.route_path(
224 'repo_commits', repo_name=self.db_repo_name)
223 'repo_commits', repo_name=self.db_repo_name)
225 raise HTTPFound(redirect_url)
224 raise HTTPFound(redirect_url)
226 collection = list(reversed(collection))
225 collection = list(reversed(collection))
227 else:
226 else:
228 collection = self.rhodecode_vcs_repo.get_commits(
227 collection = self.rhodecode_vcs_repo.get_commits(
229 branch_name=branch_name, show_hidden=show_hidden,
228 branch_name=branch_name, show_hidden=show_hidden,
230 pre_load=pre_load, translate_tags=False)
229 pre_load=pre_load, translate_tags=False)
231
230
232 self._load_changelog_data(
231 self._load_changelog_data(
233 c, collection, p, chunk_size, c.branch_name,
232 c, collection, p, chunk_size, c.branch_name,
234 f_path=f_path, commit_id=commit_id)
233 f_path=f_path, commit_id=commit_id)
235
234
236 except EmptyRepositoryError as e:
235 except EmptyRepositoryError as e:
237 h.flash(h.escape(safe_str(e)), category='warning')
236 h.flash(h.escape(safe_str(e)), category='warning')
238 raise HTTPFound(
237 raise HTTPFound(
239 h.route_path('repo_summary', repo_name=self.db_repo_name))
238 h.route_path('repo_summary', repo_name=self.db_repo_name))
240 except HTTPFound:
239 except HTTPFound:
241 raise
240 raise
242 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
241 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
243 log.exception(safe_str(e))
242 log.exception(safe_str(e))
244 h.flash(h.escape(safe_str(e)), category='error')
243 h.flash(h.escape(safe_str(e)), category='error')
245
244
246 if commit_id:
245 if commit_id:
247 # from single commit page, we redirect to main commits
246 # from single commit page, we redirect to main commits
248 raise HTTPFound(
247 raise HTTPFound(
249 h.route_path('repo_commits', repo_name=self.db_repo_name))
248 h.route_path('repo_commits', repo_name=self.db_repo_name))
250 else:
249 else:
251 # otherwise we redirect to summary
250 # otherwise we redirect to summary
252 raise HTTPFound(
251 raise HTTPFound(
253 h.route_path('repo_summary', repo_name=self.db_repo_name))
252 h.route_path('repo_summary', repo_name=self.db_repo_name))
254
253
255
254
256
255
257 if partial_xhr or self.request.environ.get('HTTP_X_PJAX'):
256 if partial_xhr or self.request.environ.get('HTTP_X_PJAX'):
258 # case when loading dynamic file history in file view
257 # case when loading dynamic file history in file view
259 # loading from ajax, we don't want the first result, it's popped
258 # loading from ajax, we don't want the first result, it's popped
260 # in the code above
259 # in the code above
261 html = render(
260 html = render(
262 'rhodecode:templates/commits/changelog_file_history.mako',
261 'rhodecode:templates/commits/changelog_file_history.mako',
263 self._get_template_context(c), self.request)
262 self._get_template_context(c), self.request)
264 return Response(html)
263 return Response(html)
265
264
266 commit_ids = []
265 commit_ids = []
267 if not f_path:
266 if not f_path:
268 # only load graph data when not in file history mode
267 # only load graph data when not in file history mode
269 commit_ids = c.pagination
268 commit_ids = c.pagination
270
269
271 c.graph_data, c.graph_commits = self._graph(
270 c.graph_data, c.graph_commits = self._graph(
272 self.rhodecode_vcs_repo, commit_ids)
271 self.rhodecode_vcs_repo, commit_ids)
273
272
274 return self._get_template_context(c)
273 return self._get_template_context(c)
275
274
276 @LoginRequired()
275 @LoginRequired()
277 @HasRepoPermissionAnyDecorator(
276 @HasRepoPermissionAnyDecorator(
278 'repository.read', 'repository.write', 'repository.admin')
277 'repository.read', 'repository.write', 'repository.admin')
279 def repo_commits_elements(self):
278 def repo_commits_elements(self):
280 c = self.load_default_context()
279 c = self.load_default_context()
281 commit_id = self.request.matchdict.get('commit_id')
280 commit_id = self.request.matchdict.get('commit_id')
282 f_path = self._get_f_path(self.request.matchdict)
281 f_path = self._get_f_path(self.request.matchdict)
283 show_hidden = str2bool(self.request.GET.get('evolve'))
282 show_hidden = str2bool(self.request.GET.get('evolve'))
284
283
285 chunk_size = 20
284 chunk_size = 20
286 hist_limit = safe_int(self.request.GET.get('limit')) or None
285 hist_limit = safe_int(self.request.GET.get('limit')) or None
287
286
288 def wrap_for_error(err):
287 def wrap_for_error(err):
289 html = '<tr>' \
288 html = '<tr>' \
290 '<td colspan="9" class="alert alert-error">ERROR: {}</td>' \
289 '<td colspan="9" class="alert alert-error">ERROR: {}</td>' \
291 '</tr>'.format(err)
290 '</tr>'.format(err)
292 return Response(html)
291 return Response(html)
293
292
294 c.branch_name = branch_name = self.request.GET.get('branch') or ''
293 c.branch_name = branch_name = self.request.GET.get('branch') or ''
295 c.book_name = book_name = self.request.GET.get('bookmark') or ''
294 c.book_name = book_name = self.request.GET.get('bookmark') or ''
296 c.f_path = f_path
295 c.f_path = f_path
297 c.commit_id = commit_id
296 c.commit_id = commit_id
298 c.show_hidden = show_hidden
297 c.show_hidden = show_hidden
299
298
300 c.selected_name = branch_name or book_name
299 c.selected_name = branch_name or book_name
301 if branch_name and branch_name not in self.rhodecode_vcs_repo.branches_all:
300 if branch_name and branch_name not in self.rhodecode_vcs_repo.branches_all:
302 return wrap_for_error(
301 return wrap_for_error(
303 safe_str('Branch: {} is not valid'.format(branch_name)))
302 safe_str(f'Branch: {branch_name} is not valid'))
304
303
305 pre_load = self.get_commit_preload_attrs()
304 pre_load = self.get_commit_preload_attrs()
306
305
307 if f_path:
306 if f_path:
308 try:
307 try:
309 base_commit = self.rhodecode_vcs_repo.get_commit(commit_id)
308 base_commit = self.rhodecode_vcs_repo.get_commit(commit_id)
310 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
309 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
311 log.exception(safe_str(e))
310 log.exception(safe_str(e))
312 raise HTTPFound(
311 raise HTTPFound(
313 h.route_path('repo_commits', repo_name=self.db_repo_name))
312 h.route_path('repo_commits', repo_name=self.db_repo_name))
314
313
315 collection = base_commit.get_path_history(
314 collection = base_commit.get_path_history(
316 f_path, limit=hist_limit, pre_load=pre_load)
315 f_path, limit=hist_limit, pre_load=pre_load)
317 collection = list(reversed(collection))
316 collection = list(reversed(collection))
318 else:
317 else:
319 collection = self.rhodecode_vcs_repo.get_commits(
318 collection = self.rhodecode_vcs_repo.get_commits(
320 branch_name=branch_name, show_hidden=show_hidden, pre_load=pre_load,
319 branch_name=branch_name, show_hidden=show_hidden, pre_load=pre_load,
321 translate_tags=False)
320 translate_tags=False)
322
321
323 p = safe_int(self.request.GET.get('page', 1), 1)
322 p = safe_int(self.request.GET.get('page', 1), 1)
324 try:
323 try:
325 self._load_changelog_data(
324 self._load_changelog_data(
326 c, collection, p, chunk_size, dynamic=True,
325 c, collection, p, chunk_size, dynamic=True,
327 f_path=f_path, commit_id=commit_id)
326 f_path=f_path, commit_id=commit_id)
328 except EmptyRepositoryError as e:
327 except EmptyRepositoryError as e:
329 return wrap_for_error(safe_str(e))
328 return wrap_for_error(safe_str(e))
330 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
329 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
331 log.exception('Failed to fetch commits')
330 log.exception('Failed to fetch commits')
332 return wrap_for_error(safe_str(e))
331 return wrap_for_error(safe_str(e))
333
332
334 prev_data = None
333 prev_data = None
335 next_data = None
334 next_data = None
336
335
337 try:
336 try:
338 prev_graph = ext_json.json.loads(self.request.POST.get('graph') or '{}')
337 prev_graph = ext_json.json.loads(self.request.POST.get('graph') or '{}')
339 except ext_json.json.JSONDecodeError:
338 except ext_json.json.JSONDecodeError:
340 prev_graph = {}
339 prev_graph = {}
341
340
342 if self.request.GET.get('chunk') == 'prev':
341 if self.request.GET.get('chunk') == 'prev':
343 next_data = prev_graph
342 next_data = prev_graph
344 elif self.request.GET.get('chunk') == 'next':
343 elif self.request.GET.get('chunk') == 'next':
345 prev_data = prev_graph
344 prev_data = prev_graph
346
345
347 commit_ids = []
346 commit_ids = []
348 if not f_path:
347 if not f_path:
349 # only load graph data when not in file history mode
348 # only load graph data when not in file history mode
350 commit_ids = c.pagination
349 commit_ids = c.pagination
351
350
352 c.graph_data, c.graph_commits = self._graph(
351 c.graph_data, c.graph_commits = self._graph(
353 self.rhodecode_vcs_repo, commit_ids,
352 self.rhodecode_vcs_repo, commit_ids,
354 prev_data=prev_data, next_data=next_data)
353 prev_data=prev_data, next_data=next_data)
355
354
356 return self._get_template_context(c)
355 return self._get_template_context(c)
@@ -1,117 +1,115 b''
1
2
3 # Copyright (C) 2011-2023 RhodeCode GmbH
1 # Copyright (C) 2011-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 import logging
19 import logging
22
20
23 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
21 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
24
22
25 from rhodecode.apps._base import BaseAppView
23 from rhodecode.apps._base import BaseAppView
26 from rhodecode.lib import helpers as h
24 from rhodecode.lib import helpers as h
27 from rhodecode.lib.auth import (NotAnonymous, HasRepoPermissionAny)
25 from rhodecode.lib.auth import (NotAnonymous, HasRepoPermissionAny)
28 from rhodecode.model.db import Repository
26 from rhodecode.model.db import Repository
29 from rhodecode.model.permission import PermissionModel
27 from rhodecode.model.permission import PermissionModel
30 from rhodecode.model.validation_schema.types import RepoNameType
28 from rhodecode.model.validation_schema.types import RepoNameType
31
29
32 log = logging.getLogger(__name__)
30 log = logging.getLogger(__name__)
33
31
34
32
35 class RepoChecksView(BaseAppView):
33 class RepoChecksView(BaseAppView):
36 def load_default_context(self):
34 def load_default_context(self):
37 c = self._get_local_tmpl_context()
35 c = self._get_local_tmpl_context()
38 return c
36 return c
39
37
40 @NotAnonymous()
38 @NotAnonymous()
41 def repo_creating(self):
39 def repo_creating(self):
42 c = self.load_default_context()
40 c = self.load_default_context()
43 repo_name = self.request.matchdict['repo_name']
41 repo_name = self.request.matchdict['repo_name']
44 repo_name = RepoNameType().deserialize(None, repo_name)
42 repo_name = RepoNameType().deserialize(None, repo_name)
45 db_repo = Repository.get_by_repo_name(repo_name)
43 db_repo = Repository.get_by_repo_name(repo_name)
46
44
47 # check if maybe repo is already created
45 # check if maybe repo is already created
48 if db_repo and db_repo.repo_state in [Repository.STATE_CREATED]:
46 if db_repo and db_repo.repo_state in [Repository.STATE_CREATED]:
49 self.flush_permissions_on_creation(db_repo)
47 self.flush_permissions_on_creation(db_repo)
50
48
51 # re-check permissions before redirecting to prevent resource
49 # re-check permissions before redirecting to prevent resource
52 # discovery by checking the 302 code
50 # discovery by checking the 302 code
53 perm_set = ['repository.read', 'repository.write', 'repository.admin']
51 perm_set = ['repository.read', 'repository.write', 'repository.admin']
54 has_perm = HasRepoPermissionAny(*perm_set)(
52 has_perm = HasRepoPermissionAny(*perm_set)(
55 db_repo.repo_name, 'Repo Creating check')
53 db_repo.repo_name, 'Repo Creating check')
56 if not has_perm:
54 if not has_perm:
57 raise HTTPNotFound()
55 raise HTTPNotFound()
58
56
59 raise HTTPFound(h.route_path(
57 raise HTTPFound(h.route_path(
60 'repo_summary', repo_name=db_repo.repo_name))
58 'repo_summary', repo_name=db_repo.repo_name))
61
59
62 c.task_id = self.request.GET.get('task_id')
60 c.task_id = self.request.GET.get('task_id')
63 c.repo_name = repo_name
61 c.repo_name = repo_name
64
62
65 return self._get_template_context(c)
63 return self._get_template_context(c)
66
64
67 @NotAnonymous()
65 @NotAnonymous()
68 def repo_creating_check(self):
66 def repo_creating_check(self):
69 _ = self.request.translate
67 _ = self.request.translate
70 task_id = self.request.GET.get('task_id')
68 task_id = self.request.GET.get('task_id')
71 self.load_default_context()
69 self.load_default_context()
72
70
73 repo_name = self.request.matchdict['repo_name']
71 repo_name = self.request.matchdict['repo_name']
74
72
75 if task_id and task_id not in ['None']:
73 if task_id and task_id not in ['None']:
76 import rhodecode
74 import rhodecode
77 from rhodecode.lib.celerylib.loader import celery_app, exceptions
75 from rhodecode.lib.celerylib.loader import celery_app, exceptions
78 if rhodecode.CELERY_ENABLED:
76 if rhodecode.CELERY_ENABLED:
79 log.debug('celery: checking result for task:%s', task_id)
77 log.debug('celery: checking result for task:%s', task_id)
80 task = celery_app.AsyncResult(task_id)
78 task = celery_app.AsyncResult(task_id)
81 try:
79 try:
82 task.get(timeout=10)
80 task.get(timeout=10)
83 except exceptions.TimeoutError:
81 except exceptions.TimeoutError:
84 task = None
82 task = None
85 if task and task.failed():
83 if task and task.failed():
86 msg = self._log_creation_exception(task.result, repo_name)
84 msg = self._log_creation_exception(task.result, repo_name)
87 h.flash(msg, category='error')
85 h.flash(msg, category='error')
88 raise HTTPFound(h.route_path('home'), code=501)
86 raise HTTPFound(h.route_path('home'), code=501)
89
87
90 db_repo = Repository.get_by_repo_name(repo_name)
88 db_repo = Repository.get_by_repo_name(repo_name)
91 if db_repo and db_repo.repo_state == Repository.STATE_CREATED:
89 if db_repo and db_repo.repo_state == Repository.STATE_CREATED:
92 if db_repo.clone_uri:
90 if db_repo.clone_uri:
93 clone_uri = db_repo.clone_uri_hidden
91 clone_uri = db_repo.clone_uri_hidden
94 h.flash(_('Created repository %s from %s')
92 h.flash(_('Created repository %s from %s')
95 % (db_repo.repo_name, clone_uri), category='success')
93 % (db_repo.repo_name, clone_uri), category='success')
96 else:
94 else:
97 repo_url = h.link_to(
95 repo_url = h.link_to(
98 db_repo.repo_name,
96 db_repo.repo_name,
99 h.route_path('repo_summary', repo_name=db_repo.repo_name))
97 h.route_path('repo_summary', repo_name=db_repo.repo_name))
100 fork = db_repo.fork
98 fork = db_repo.fork
101 if fork:
99 if fork:
102 fork_name = fork.repo_name
100 fork_name = fork.repo_name
103 h.flash(h.literal(_('Forked repository %s as %s')
101 h.flash(h.literal(_('Forked repository %s as %s')
104 % (fork_name, repo_url)), category='success')
102 % (fork_name, repo_url)), category='success')
105 else:
103 else:
106 h.flash(h.literal(_('Created repository %s') % repo_url),
104 h.flash(h.literal(_('Created repository %s') % repo_url),
107 category='success')
105 category='success')
108 self.flush_permissions_on_creation(db_repo)
106 self.flush_permissions_on_creation(db_repo)
109
107
110 return {'result': True}
108 return {'result': True}
111 return {'result': False}
109 return {'result': False}
112
110
113 def flush_permissions_on_creation(self, db_repo):
111 def flush_permissions_on_creation(self, db_repo):
114 # repo is finished and created, we flush the permissions now
112 # repo is finished and created, we flush the permissions now
115 user_group_perms = db_repo.permissions(expand_from_user_groups=True)
113 user_group_perms = db_repo.permissions(expand_from_user_groups=True)
116 affected_user_ids = [perm['user_id'] for perm in user_group_perms]
114 affected_user_ids = [perm['user_id'] for perm in user_group_perms]
117 PermissionModel().trigger_permission_flush(affected_user_ids)
115 PermissionModel().trigger_permission_flush(affected_user_ids)
@@ -1,831 +1,830 b''
1
2 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
2 #
4 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
7 #
6 #
8 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
10 # GNU General Public License for more details.
12 #
11 #
13 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
14 #
16 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
18
20 import logging
19 import logging
21 import collections
20 import collections
22
21
23 from pyramid.httpexceptions import (
22 from pyramid.httpexceptions import (
24 HTTPNotFound, HTTPBadRequest, HTTPFound, HTTPForbidden, HTTPConflict)
23 HTTPNotFound, HTTPBadRequest, HTTPFound, HTTPForbidden, HTTPConflict)
25 from pyramid.renderers import render
24 from pyramid.renderers import render
26 from pyramid.response import Response
25 from pyramid.response import Response
27
26
28 from rhodecode.apps._base import RepoAppView
27 from rhodecode.apps._base import RepoAppView
29 from rhodecode.apps.file_store import utils as store_utils
28 from rhodecode.apps.file_store import utils as store_utils
30 from rhodecode.apps.file_store.exceptions import FileNotAllowedException, FileOverSizeException
29 from rhodecode.apps.file_store.exceptions import FileNotAllowedException, FileOverSizeException
31
30
32 from rhodecode.lib import diffs, codeblocks, channelstream
31 from rhodecode.lib import diffs, codeblocks, channelstream
33 from rhodecode.lib.auth import (
32 from rhodecode.lib.auth import (
34 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
33 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
35 from rhodecode.lib import ext_json
34 from rhodecode.lib import ext_json
36 from collections import OrderedDict
35 from collections import OrderedDict
37 from rhodecode.lib.diffs import (
36 from rhodecode.lib.diffs import (
38 cache_diff, load_cached_diff, diff_cache_exist, get_diff_context,
37 cache_diff, load_cached_diff, diff_cache_exist, get_diff_context,
39 get_diff_whitespace_flag)
38 get_diff_whitespace_flag)
40 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError, CommentVersionMismatch
39 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError, CommentVersionMismatch
41 import rhodecode.lib.helpers as h
40 import rhodecode.lib.helpers as h
42 from rhodecode.lib.utils2 import str2bool, StrictAttributeDict, safe_str
41 from rhodecode.lib.utils2 import str2bool, StrictAttributeDict, safe_str
43 from rhodecode.lib.vcs.backends.base import EmptyCommit
42 from rhodecode.lib.vcs.backends.base import EmptyCommit
44 from rhodecode.lib.vcs.exceptions import (
43 from rhodecode.lib.vcs.exceptions import (
45 RepositoryError, CommitDoesNotExistError)
44 RepositoryError, CommitDoesNotExistError)
46 from rhodecode.model.db import ChangesetComment, ChangesetStatus, FileStore, \
45 from rhodecode.model.db import ChangesetComment, ChangesetStatus, FileStore, \
47 ChangesetCommentHistory
46 ChangesetCommentHistory
48 from rhodecode.model.changeset_status import ChangesetStatusModel
47 from rhodecode.model.changeset_status import ChangesetStatusModel
49 from rhodecode.model.comment import CommentsModel
48 from rhodecode.model.comment import CommentsModel
50 from rhodecode.model.meta import Session
49 from rhodecode.model.meta import Session
51 from rhodecode.model.settings import VcsSettingsModel
50 from rhodecode.model.settings import VcsSettingsModel
52
51
53 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
54
53
55
54
56 def _update_with_GET(params, request):
55 def _update_with_GET(params, request):
57 for k in ['diff1', 'diff2', 'diff']:
56 for k in ['diff1', 'diff2', 'diff']:
58 params[k] += request.GET.getall(k)
57 params[k] += request.GET.getall(k)
59
58
60
59
61 class RepoCommitsView(RepoAppView):
60 class RepoCommitsView(RepoAppView):
62 def load_default_context(self):
61 def load_default_context(self):
63 c = self._get_local_tmpl_context(include_app_defaults=True)
62 c = self._get_local_tmpl_context(include_app_defaults=True)
64 c.rhodecode_repo = self.rhodecode_vcs_repo
63 c.rhodecode_repo = self.rhodecode_vcs_repo
65
64
66 return c
65 return c
67
66
68 def _is_diff_cache_enabled(self, target_repo):
67 def _is_diff_cache_enabled(self, target_repo):
69 caching_enabled = self._get_general_setting(
68 caching_enabled = self._get_general_setting(
70 target_repo, 'rhodecode_diff_cache')
69 target_repo, 'rhodecode_diff_cache')
71 log.debug('Diff caching enabled: %s', caching_enabled)
70 log.debug('Diff caching enabled: %s', caching_enabled)
72 return caching_enabled
71 return caching_enabled
73
72
74 def _commit(self, commit_id_range, method):
73 def _commit(self, commit_id_range, method):
75 _ = self.request.translate
74 _ = self.request.translate
76 c = self.load_default_context()
75 c = self.load_default_context()
77 c.fulldiff = self.request.GET.get('fulldiff')
76 c.fulldiff = self.request.GET.get('fulldiff')
78 redirect_to_combined = str2bool(self.request.GET.get('redirect_combined'))
77 redirect_to_combined = str2bool(self.request.GET.get('redirect_combined'))
79
78
80 # fetch global flags of ignore ws or context lines
79 # fetch global flags of ignore ws or context lines
81 diff_context = get_diff_context(self.request)
80 diff_context = get_diff_context(self.request)
82 hide_whitespace_changes = get_diff_whitespace_flag(self.request)
81 hide_whitespace_changes = get_diff_whitespace_flag(self.request)
83
82
84 # diff_limit will cut off the whole diff if the limit is applied
83 # diff_limit will cut off the whole diff if the limit is applied
85 # otherwise it will just hide the big files from the front-end
84 # otherwise it will just hide the big files from the front-end
86 diff_limit = c.visual.cut_off_limit_diff
85 diff_limit = c.visual.cut_off_limit_diff
87 file_limit = c.visual.cut_off_limit_file
86 file_limit = c.visual.cut_off_limit_file
88
87
89 # get ranges of commit ids if preset
88 # get ranges of commit ids if preset
90 commit_range = commit_id_range.split('...')[:2]
89 commit_range = commit_id_range.split('...')[:2]
91
90
92 try:
91 try:
93 pre_load = ['affected_files', 'author', 'branch', 'date',
92 pre_load = ['affected_files', 'author', 'branch', 'date',
94 'message', 'parents']
93 'message', 'parents']
95 if self.rhodecode_vcs_repo.alias == 'hg':
94 if self.rhodecode_vcs_repo.alias == 'hg':
96 pre_load += ['hidden', 'obsolete', 'phase']
95 pre_load += ['hidden', 'obsolete', 'phase']
97
96
98 if len(commit_range) == 2:
97 if len(commit_range) == 2:
99 commits = self.rhodecode_vcs_repo.get_commits(
98 commits = self.rhodecode_vcs_repo.get_commits(
100 start_id=commit_range[0], end_id=commit_range[1],
99 start_id=commit_range[0], end_id=commit_range[1],
101 pre_load=pre_load, translate_tags=False)
100 pre_load=pre_load, translate_tags=False)
102 commits = list(commits)
101 commits = list(commits)
103 else:
102 else:
104 commits = [self.rhodecode_vcs_repo.get_commit(
103 commits = [self.rhodecode_vcs_repo.get_commit(
105 commit_id=commit_id_range, pre_load=pre_load)]
104 commit_id=commit_id_range, pre_load=pre_load)]
106
105
107 c.commit_ranges = commits
106 c.commit_ranges = commits
108 if not c.commit_ranges:
107 if not c.commit_ranges:
109 raise RepositoryError('The commit range returned an empty result')
108 raise RepositoryError('The commit range returned an empty result')
110 except CommitDoesNotExistError as e:
109 except CommitDoesNotExistError as e:
111 msg = _('No such commit exists. Org exception: `{}`').format(safe_str(e))
110 msg = _('No such commit exists. Org exception: `{}`').format(safe_str(e))
112 h.flash(msg, category='error')
111 h.flash(msg, category='error')
113 raise HTTPNotFound()
112 raise HTTPNotFound()
114 except Exception:
113 except Exception:
115 log.exception("General failure")
114 log.exception("General failure")
116 raise HTTPNotFound()
115 raise HTTPNotFound()
117 single_commit = len(c.commit_ranges) == 1
116 single_commit = len(c.commit_ranges) == 1
118
117
119 if redirect_to_combined and not single_commit:
118 if redirect_to_combined and not single_commit:
120 source_ref = getattr(c.commit_ranges[0].parents[0]
119 source_ref = getattr(c.commit_ranges[0].parents[0]
121 if c.commit_ranges[0].parents else h.EmptyCommit(), 'raw_id')
120 if c.commit_ranges[0].parents else h.EmptyCommit(), 'raw_id')
122 target_ref = c.commit_ranges[-1].raw_id
121 target_ref = c.commit_ranges[-1].raw_id
123 next_url = h.route_path(
122 next_url = h.route_path(
124 'repo_compare',
123 'repo_compare',
125 repo_name=c.repo_name,
124 repo_name=c.repo_name,
126 source_ref_type='rev',
125 source_ref_type='rev',
127 source_ref=source_ref,
126 source_ref=source_ref,
128 target_ref_type='rev',
127 target_ref_type='rev',
129 target_ref=target_ref)
128 target_ref=target_ref)
130 raise HTTPFound(next_url)
129 raise HTTPFound(next_url)
131
130
132 c.changes = OrderedDict()
131 c.changes = OrderedDict()
133 c.lines_added = 0
132 c.lines_added = 0
134 c.lines_deleted = 0
133 c.lines_deleted = 0
135
134
136 # auto collapse if we have more than limit
135 # auto collapse if we have more than limit
137 collapse_limit = diffs.DiffProcessor._collapse_commits_over
136 collapse_limit = diffs.DiffProcessor._collapse_commits_over
138 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
137 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
139
138
140 c.commit_statuses = ChangesetStatus.STATUSES
139 c.commit_statuses = ChangesetStatus.STATUSES
141 c.inline_comments = []
140 c.inline_comments = []
142 c.files = []
141 c.files = []
143
142
144 c.comments = []
143 c.comments = []
145 c.unresolved_comments = []
144 c.unresolved_comments = []
146 c.resolved_comments = []
145 c.resolved_comments = []
147
146
148 # Single commit
147 # Single commit
149 if single_commit:
148 if single_commit:
150 commit = c.commit_ranges[0]
149 commit = c.commit_ranges[0]
151 c.comments = CommentsModel().get_comments(
150 c.comments = CommentsModel().get_comments(
152 self.db_repo.repo_id,
151 self.db_repo.repo_id,
153 revision=commit.raw_id)
152 revision=commit.raw_id)
154
153
155 # comments from PR
154 # comments from PR
156 statuses = ChangesetStatusModel().get_statuses(
155 statuses = ChangesetStatusModel().get_statuses(
157 self.db_repo.repo_id, commit.raw_id,
156 self.db_repo.repo_id, commit.raw_id,
158 with_revisions=True)
157 with_revisions=True)
159
158
160 prs = set()
159 prs = set()
161 reviewers = list()
160 reviewers = list()
162 reviewers_duplicates = set() # to not have duplicates from multiple votes
161 reviewers_duplicates = set() # to not have duplicates from multiple votes
163 for c_status in statuses:
162 for c_status in statuses:
164
163
165 # extract associated pull-requests from votes
164 # extract associated pull-requests from votes
166 if c_status.pull_request:
165 if c_status.pull_request:
167 prs.add(c_status.pull_request)
166 prs.add(c_status.pull_request)
168
167
169 # extract reviewers
168 # extract reviewers
170 _user_id = c_status.author.user_id
169 _user_id = c_status.author.user_id
171 if _user_id not in reviewers_duplicates:
170 if _user_id not in reviewers_duplicates:
172 reviewers.append(
171 reviewers.append(
173 StrictAttributeDict({
172 StrictAttributeDict({
174 'user': c_status.author,
173 'user': c_status.author,
175
174
176 # fake attributed for commit, page that we don't have
175 # fake attributed for commit, page that we don't have
177 # but we share the display with PR page
176 # but we share the display with PR page
178 'mandatory': False,
177 'mandatory': False,
179 'reasons': [],
178 'reasons': [],
180 'rule_user_group_data': lambda: None
179 'rule_user_group_data': lambda: None
181 })
180 })
182 )
181 )
183 reviewers_duplicates.add(_user_id)
182 reviewers_duplicates.add(_user_id)
184
183
185 c.reviewers_count = len(reviewers)
184 c.reviewers_count = len(reviewers)
186 c.observers_count = 0
185 c.observers_count = 0
187
186
188 # from associated statuses, check the pull requests, and
187 # from associated statuses, check the pull requests, and
189 # show comments from them
188 # show comments from them
190 for pr in prs:
189 for pr in prs:
191 c.comments.extend(pr.comments)
190 c.comments.extend(pr.comments)
192
191
193 c.unresolved_comments = CommentsModel()\
192 c.unresolved_comments = CommentsModel()\
194 .get_commit_unresolved_todos(commit.raw_id)
193 .get_commit_unresolved_todos(commit.raw_id)
195 c.resolved_comments = CommentsModel()\
194 c.resolved_comments = CommentsModel()\
196 .get_commit_resolved_todos(commit.raw_id)
195 .get_commit_resolved_todos(commit.raw_id)
197
196
198 c.inline_comments_flat = CommentsModel()\
197 c.inline_comments_flat = CommentsModel()\
199 .get_commit_inline_comments(commit.raw_id)
198 .get_commit_inline_comments(commit.raw_id)
200
199
201 review_statuses = ChangesetStatusModel().aggregate_votes_by_user(
200 review_statuses = ChangesetStatusModel().aggregate_votes_by_user(
202 statuses, reviewers)
201 statuses, reviewers)
203
202
204 c.commit_review_status = ChangesetStatus.STATUS_NOT_REVIEWED
203 c.commit_review_status = ChangesetStatus.STATUS_NOT_REVIEWED
205
204
206 c.commit_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
205 c.commit_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
207
206
208 for review_obj, member, reasons, mandatory, status in review_statuses:
207 for review_obj, member, reasons, mandatory, status in review_statuses:
209 member_reviewer = h.reviewer_as_json(
208 member_reviewer = h.reviewer_as_json(
210 member, reasons=reasons, mandatory=mandatory, role=None,
209 member, reasons=reasons, mandatory=mandatory, role=None,
211 user_group=None
210 user_group=None
212 )
211 )
213
212
214 current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
213 current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
215 member_reviewer['review_status'] = current_review_status
214 member_reviewer['review_status'] = current_review_status
216 member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
215 member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
217 member_reviewer['allowed_to_update'] = False
216 member_reviewer['allowed_to_update'] = False
218 c.commit_set_reviewers_data_json['reviewers'].append(member_reviewer)
217 c.commit_set_reviewers_data_json['reviewers'].append(member_reviewer)
219
218
220 c.commit_set_reviewers_data_json = ext_json.str_json(c.commit_set_reviewers_data_json)
219 c.commit_set_reviewers_data_json = ext_json.str_json(c.commit_set_reviewers_data_json)
221
220
222 # NOTE(marcink): this uses the same voting logic as in pull-requests
221 # NOTE(marcink): this uses the same voting logic as in pull-requests
223 c.commit_review_status = ChangesetStatusModel().calculate_status(review_statuses)
222 c.commit_review_status = ChangesetStatusModel().calculate_status(review_statuses)
224 c.commit_broadcast_channel = channelstream.comment_channel(c.repo_name, commit_obj=commit)
223 c.commit_broadcast_channel = channelstream.comment_channel(c.repo_name, commit_obj=commit)
225
224
226 diff = None
225 diff = None
227 # Iterate over ranges (default commit view is always one commit)
226 # Iterate over ranges (default commit view is always one commit)
228 for commit in c.commit_ranges:
227 for commit in c.commit_ranges:
229 c.changes[commit.raw_id] = []
228 c.changes[commit.raw_id] = []
230
229
231 commit2 = commit
230 commit2 = commit
232 commit1 = commit.first_parent
231 commit1 = commit.first_parent
233
232
234 if method == 'show':
233 if method == 'show':
235 inline_comments = CommentsModel().get_inline_comments(
234 inline_comments = CommentsModel().get_inline_comments(
236 self.db_repo.repo_id, revision=commit.raw_id)
235 self.db_repo.repo_id, revision=commit.raw_id)
237 c.inline_cnt = len(CommentsModel().get_inline_comments_as_list(
236 c.inline_cnt = len(CommentsModel().get_inline_comments_as_list(
238 inline_comments))
237 inline_comments))
239 c.inline_comments = inline_comments
238 c.inline_comments = inline_comments
240
239
241 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(
240 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(
242 self.db_repo)
241 self.db_repo)
243 cache_file_path = diff_cache_exist(
242 cache_file_path = diff_cache_exist(
244 cache_path, 'diff', commit.raw_id,
243 cache_path, 'diff', commit.raw_id,
245 hide_whitespace_changes, diff_context, c.fulldiff)
244 hide_whitespace_changes, diff_context, c.fulldiff)
246
245
247 caching_enabled = self._is_diff_cache_enabled(self.db_repo)
246 caching_enabled = self._is_diff_cache_enabled(self.db_repo)
248 force_recache = str2bool(self.request.GET.get('force_recache'))
247 force_recache = str2bool(self.request.GET.get('force_recache'))
249
248
250 cached_diff = None
249 cached_diff = None
251 if caching_enabled:
250 if caching_enabled:
252 cached_diff = load_cached_diff(cache_file_path)
251 cached_diff = load_cached_diff(cache_file_path)
253
252
254 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
253 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
255 if not force_recache and has_proper_diff_cache:
254 if not force_recache and has_proper_diff_cache:
256 diffset = cached_diff['diff']
255 diffset = cached_diff['diff']
257 else:
256 else:
258 vcs_diff = self.rhodecode_vcs_repo.get_diff(
257 vcs_diff = self.rhodecode_vcs_repo.get_diff(
259 commit1, commit2,
258 commit1, commit2,
260 ignore_whitespace=hide_whitespace_changes,
259 ignore_whitespace=hide_whitespace_changes,
261 context=diff_context)
260 context=diff_context)
262
261
263 diff_processor = diffs.DiffProcessor(vcs_diff, diff_format='newdiff',
262 diff_processor = diffs.DiffProcessor(vcs_diff, diff_format='newdiff',
264 diff_limit=diff_limit,
263 diff_limit=diff_limit,
265 file_limit=file_limit,
264 file_limit=file_limit,
266 show_full_diff=c.fulldiff)
265 show_full_diff=c.fulldiff)
267
266
268 _parsed = diff_processor.prepare()
267 _parsed = diff_processor.prepare()
269
268
270 diffset = codeblocks.DiffSet(
269 diffset = codeblocks.DiffSet(
271 repo_name=self.db_repo_name,
270 repo_name=self.db_repo_name,
272 source_node_getter=codeblocks.diffset_node_getter(commit1),
271 source_node_getter=codeblocks.diffset_node_getter(commit1),
273 target_node_getter=codeblocks.diffset_node_getter(commit2))
272 target_node_getter=codeblocks.diffset_node_getter(commit2))
274
273
275 diffset = self.path_filter.render_patchset_filtered(
274 diffset = self.path_filter.render_patchset_filtered(
276 diffset, _parsed, commit1.raw_id, commit2.raw_id)
275 diffset, _parsed, commit1.raw_id, commit2.raw_id)
277
276
278 # save cached diff
277 # save cached diff
279 if caching_enabled:
278 if caching_enabled:
280 cache_diff(cache_file_path, diffset, None)
279 cache_diff(cache_file_path, diffset, None)
281
280
282 c.limited_diff = diffset.limited_diff
281 c.limited_diff = diffset.limited_diff
283 c.changes[commit.raw_id] = diffset
282 c.changes[commit.raw_id] = diffset
284 else:
283 else:
285 # TODO(marcink): no cache usage here...
284 # TODO(marcink): no cache usage here...
286 _diff = self.rhodecode_vcs_repo.get_diff(
285 _diff = self.rhodecode_vcs_repo.get_diff(
287 commit1, commit2,
286 commit1, commit2,
288 ignore_whitespace=hide_whitespace_changes, context=diff_context)
287 ignore_whitespace=hide_whitespace_changes, context=diff_context)
289 diff_processor = diffs.DiffProcessor(_diff, diff_format='newdiff',
288 diff_processor = diffs.DiffProcessor(_diff, diff_format='newdiff',
290 diff_limit=diff_limit,
289 diff_limit=diff_limit,
291 file_limit=file_limit, show_full_diff=c.fulldiff)
290 file_limit=file_limit, show_full_diff=c.fulldiff)
292 # downloads/raw we only need RAW diff nothing else
291 # downloads/raw we only need RAW diff nothing else
293 diff = self.path_filter.get_raw_patch(diff_processor)
292 diff = self.path_filter.get_raw_patch(diff_processor)
294 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
293 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
295
294
296 # sort comments by how they were generated
295 # sort comments by how they were generated
297 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
296 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
298 c.at_version_num = None
297 c.at_version_num = None
299
298
300 if len(c.commit_ranges) == 1:
299 if len(c.commit_ranges) == 1:
301 c.commit = c.commit_ranges[0]
300 c.commit = c.commit_ranges[0]
302 c.parent_tmpl = ''.join(
301 c.parent_tmpl = ''.join(
303 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
302 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
304
303
305 if method == 'download':
304 if method == 'download':
306 response = Response(diff)
305 response = Response(diff)
307 response.content_type = 'text/plain'
306 response.content_type = 'text/plain'
308 response.content_disposition = (
307 response.content_disposition = (
309 'attachment; filename=%s.diff' % commit_id_range[:12])
308 'attachment; filename=%s.diff' % commit_id_range[:12])
310 return response
309 return response
311 elif method == 'patch':
310 elif method == 'patch':
312
311
313 c.diff = safe_str(diff)
312 c.diff = safe_str(diff)
314 patch = render(
313 patch = render(
315 'rhodecode:templates/changeset/patch_changeset.mako',
314 'rhodecode:templates/changeset/patch_changeset.mako',
316 self._get_template_context(c), self.request)
315 self._get_template_context(c), self.request)
317 response = Response(patch)
316 response = Response(patch)
318 response.content_type = 'text/plain'
317 response.content_type = 'text/plain'
319 return response
318 return response
320 elif method == 'raw':
319 elif method == 'raw':
321 response = Response(diff)
320 response = Response(diff)
322 response.content_type = 'text/plain'
321 response.content_type = 'text/plain'
323 return response
322 return response
324 elif method == 'show':
323 elif method == 'show':
325 if len(c.commit_ranges) == 1:
324 if len(c.commit_ranges) == 1:
326 html = render(
325 html = render(
327 'rhodecode:templates/changeset/changeset.mako',
326 'rhodecode:templates/changeset/changeset.mako',
328 self._get_template_context(c), self.request)
327 self._get_template_context(c), self.request)
329 return Response(html)
328 return Response(html)
330 else:
329 else:
331 c.ancestor = None
330 c.ancestor = None
332 c.target_repo = self.db_repo
331 c.target_repo = self.db_repo
333 html = render(
332 html = render(
334 'rhodecode:templates/changeset/changeset_range.mako',
333 'rhodecode:templates/changeset/changeset_range.mako',
335 self._get_template_context(c), self.request)
334 self._get_template_context(c), self.request)
336 return Response(html)
335 return Response(html)
337
336
338 raise HTTPBadRequest()
337 raise HTTPBadRequest()
339
338
340 @LoginRequired()
339 @LoginRequired()
341 @HasRepoPermissionAnyDecorator(
340 @HasRepoPermissionAnyDecorator(
342 'repository.read', 'repository.write', 'repository.admin')
341 'repository.read', 'repository.write', 'repository.admin')
343 def repo_commit_show(self):
342 def repo_commit_show(self):
344 commit_id = self.request.matchdict['commit_id']
343 commit_id = self.request.matchdict['commit_id']
345 return self._commit(commit_id, method='show')
344 return self._commit(commit_id, method='show')
346
345
347 @LoginRequired()
346 @LoginRequired()
348 @HasRepoPermissionAnyDecorator(
347 @HasRepoPermissionAnyDecorator(
349 'repository.read', 'repository.write', 'repository.admin')
348 'repository.read', 'repository.write', 'repository.admin')
350 def repo_commit_raw(self):
349 def repo_commit_raw(self):
351 commit_id = self.request.matchdict['commit_id']
350 commit_id = self.request.matchdict['commit_id']
352 return self._commit(commit_id, method='raw')
351 return self._commit(commit_id, method='raw')
353
352
354 @LoginRequired()
353 @LoginRequired()
355 @HasRepoPermissionAnyDecorator(
354 @HasRepoPermissionAnyDecorator(
356 'repository.read', 'repository.write', 'repository.admin')
355 'repository.read', 'repository.write', 'repository.admin')
357 def repo_commit_patch(self):
356 def repo_commit_patch(self):
358 commit_id = self.request.matchdict['commit_id']
357 commit_id = self.request.matchdict['commit_id']
359 return self._commit(commit_id, method='patch')
358 return self._commit(commit_id, method='patch')
360
359
361 @LoginRequired()
360 @LoginRequired()
362 @HasRepoPermissionAnyDecorator(
361 @HasRepoPermissionAnyDecorator(
363 'repository.read', 'repository.write', 'repository.admin')
362 'repository.read', 'repository.write', 'repository.admin')
364 def repo_commit_download(self):
363 def repo_commit_download(self):
365 commit_id = self.request.matchdict['commit_id']
364 commit_id = self.request.matchdict['commit_id']
366 return self._commit(commit_id, method='download')
365 return self._commit(commit_id, method='download')
367
366
368 def _commit_comments_create(self, commit_id, comments):
367 def _commit_comments_create(self, commit_id, comments):
369 _ = self.request.translate
368 _ = self.request.translate
370 data = {}
369 data = {}
371 if not comments:
370 if not comments:
372 return
371 return
373
372
374 commit = self.db_repo.get_commit(commit_id)
373 commit = self.db_repo.get_commit(commit_id)
375
374
376 all_drafts = len([x for x in comments if str2bool(x['is_draft'])]) == len(comments)
375 all_drafts = len([x for x in comments if str2bool(x['is_draft'])]) == len(comments)
377 for entry in comments:
376 for entry in comments:
378 c = self.load_default_context()
377 c = self.load_default_context()
379 comment_type = entry['comment_type']
378 comment_type = entry['comment_type']
380 text = entry['text']
379 text = entry['text']
381 status = entry['status']
380 status = entry['status']
382 is_draft = str2bool(entry['is_draft'])
381 is_draft = str2bool(entry['is_draft'])
383 resolves_comment_id = entry['resolves_comment_id']
382 resolves_comment_id = entry['resolves_comment_id']
384 f_path = entry['f_path']
383 f_path = entry['f_path']
385 line_no = entry['line']
384 line_no = entry['line']
386 target_elem_id = 'file-{}'.format(h.safeid(h.safe_str(f_path)))
385 target_elem_id = f'file-{h.safeid(h.safe_str(f_path))}'
387
386
388 if status:
387 if status:
389 text = text or (_('Status change %(transition_icon)s %(status)s')
388 text = text or (_('Status change %(transition_icon)s %(status)s')
390 % {'transition_icon': '>',
389 % {'transition_icon': '>',
391 'status': ChangesetStatus.get_status_lbl(status)})
390 'status': ChangesetStatus.get_status_lbl(status)})
392
391
393 comment = CommentsModel().create(
392 comment = CommentsModel().create(
394 text=text,
393 text=text,
395 repo=self.db_repo.repo_id,
394 repo=self.db_repo.repo_id,
396 user=self._rhodecode_db_user.user_id,
395 user=self._rhodecode_db_user.user_id,
397 commit_id=commit_id,
396 commit_id=commit_id,
398 f_path=f_path,
397 f_path=f_path,
399 line_no=line_no,
398 line_no=line_no,
400 status_change=(ChangesetStatus.get_status_lbl(status)
399 status_change=(ChangesetStatus.get_status_lbl(status)
401 if status else None),
400 if status else None),
402 status_change_type=status,
401 status_change_type=status,
403 comment_type=comment_type,
402 comment_type=comment_type,
404 is_draft=is_draft,
403 is_draft=is_draft,
405 resolves_comment_id=resolves_comment_id,
404 resolves_comment_id=resolves_comment_id,
406 auth_user=self._rhodecode_user,
405 auth_user=self._rhodecode_user,
407 send_email=not is_draft, # skip notification for draft comments
406 send_email=not is_draft, # skip notification for draft comments
408 )
407 )
409 is_inline = comment.is_inline
408 is_inline = comment.is_inline
410
409
411 # get status if set !
410 # get status if set !
412 if status:
411 if status:
413 # `dont_allow_on_closed_pull_request = True` means
412 # `dont_allow_on_closed_pull_request = True` means
414 # if latest status was from pull request and it's closed
413 # if latest status was from pull request and it's closed
415 # disallow changing status !
414 # disallow changing status !
416
415
417 try:
416 try:
418 ChangesetStatusModel().set_status(
417 ChangesetStatusModel().set_status(
419 self.db_repo.repo_id,
418 self.db_repo.repo_id,
420 status,
419 status,
421 self._rhodecode_db_user.user_id,
420 self._rhodecode_db_user.user_id,
422 comment,
421 comment,
423 revision=commit_id,
422 revision=commit_id,
424 dont_allow_on_closed_pull_request=True
423 dont_allow_on_closed_pull_request=True
425 )
424 )
426 except StatusChangeOnClosedPullRequestError:
425 except StatusChangeOnClosedPullRequestError:
427 msg = _('Changing the status of a commit associated with '
426 msg = _('Changing the status of a commit associated with '
428 'a closed pull request is not allowed')
427 'a closed pull request is not allowed')
429 log.exception(msg)
428 log.exception(msg)
430 h.flash(msg, category='warning')
429 h.flash(msg, category='warning')
431 raise HTTPFound(h.route_path(
430 raise HTTPFound(h.route_path(
432 'repo_commit', repo_name=self.db_repo_name,
431 'repo_commit', repo_name=self.db_repo_name,
433 commit_id=commit_id))
432 commit_id=commit_id))
434
433
435 Session().flush()
434 Session().flush()
436 # this is somehow required to get access to some relationship
435 # this is somehow required to get access to some relationship
437 # loaded on comment
436 # loaded on comment
438 Session().refresh(comment)
437 Session().refresh(comment)
439
438
440 # skip notifications for drafts
439 # skip notifications for drafts
441 if not is_draft:
440 if not is_draft:
442 CommentsModel().trigger_commit_comment_hook(
441 CommentsModel().trigger_commit_comment_hook(
443 self.db_repo, self._rhodecode_user, 'create',
442 self.db_repo, self._rhodecode_user, 'create',
444 data={'comment': comment, 'commit': commit})
443 data={'comment': comment, 'commit': commit})
445
444
446 comment_id = comment.comment_id
445 comment_id = comment.comment_id
447 data[comment_id] = {
446 data[comment_id] = {
448 'target_id': target_elem_id
447 'target_id': target_elem_id
449 }
448 }
450 Session().flush()
449 Session().flush()
451
450
452 c.co = comment
451 c.co = comment
453 c.at_version_num = 0
452 c.at_version_num = 0
454 c.is_new = True
453 c.is_new = True
455 rendered_comment = render(
454 rendered_comment = render(
456 'rhodecode:templates/changeset/changeset_comment_block.mako',
455 'rhodecode:templates/changeset/changeset_comment_block.mako',
457 self._get_template_context(c), self.request)
456 self._get_template_context(c), self.request)
458
457
459 data[comment_id].update(comment.get_dict())
458 data[comment_id].update(comment.get_dict())
460 data[comment_id].update({'rendered_text': rendered_comment})
459 data[comment_id].update({'rendered_text': rendered_comment})
461
460
462 # finalize, commit and redirect
461 # finalize, commit and redirect
463 Session().commit()
462 Session().commit()
464
463
465 # skip channelstream for draft comments
464 # skip channelstream for draft comments
466 if not all_drafts:
465 if not all_drafts:
467 comment_broadcast_channel = channelstream.comment_channel(
466 comment_broadcast_channel = channelstream.comment_channel(
468 self.db_repo_name, commit_obj=commit)
467 self.db_repo_name, commit_obj=commit)
469
468
470 comment_data = data
469 comment_data = data
471 posted_comment_type = 'inline' if is_inline else 'general'
470 posted_comment_type = 'inline' if is_inline else 'general'
472 if len(data) == 1:
471 if len(data) == 1:
473 msg = _('posted {} new {} comment').format(len(data), posted_comment_type)
472 msg = _('posted {} new {} comment').format(len(data), posted_comment_type)
474 else:
473 else:
475 msg = _('posted {} new {} comments').format(len(data), posted_comment_type)
474 msg = _('posted {} new {} comments').format(len(data), posted_comment_type)
476
475
477 channelstream.comment_channelstream_push(
476 channelstream.comment_channelstream_push(
478 self.request, comment_broadcast_channel, self._rhodecode_user, msg,
477 self.request, comment_broadcast_channel, self._rhodecode_user, msg,
479 comment_data=comment_data)
478 comment_data=comment_data)
480
479
481 return data
480 return data
482
481
483 @LoginRequired()
482 @LoginRequired()
484 @NotAnonymous()
483 @NotAnonymous()
485 @HasRepoPermissionAnyDecorator(
484 @HasRepoPermissionAnyDecorator(
486 'repository.read', 'repository.write', 'repository.admin')
485 'repository.read', 'repository.write', 'repository.admin')
487 @CSRFRequired()
486 @CSRFRequired()
488 def repo_commit_comment_create(self):
487 def repo_commit_comment_create(self):
489 _ = self.request.translate
488 _ = self.request.translate
490 commit_id = self.request.matchdict['commit_id']
489 commit_id = self.request.matchdict['commit_id']
491
490
492 multi_commit_ids = []
491 multi_commit_ids = []
493 for _commit_id in self.request.POST.get('commit_ids', '').split(','):
492 for _commit_id in self.request.POST.get('commit_ids', '').split(','):
494 if _commit_id not in ['', None, EmptyCommit.raw_id]:
493 if _commit_id not in ['', None, EmptyCommit.raw_id]:
495 if _commit_id not in multi_commit_ids:
494 if _commit_id not in multi_commit_ids:
496 multi_commit_ids.append(_commit_id)
495 multi_commit_ids.append(_commit_id)
497
496
498 commit_ids = multi_commit_ids or [commit_id]
497 commit_ids = multi_commit_ids or [commit_id]
499
498
500 data = []
499 data = []
501 # Multiple comments for each passed commit id
500 # Multiple comments for each passed commit id
502 for current_id in filter(None, commit_ids):
501 for current_id in filter(None, commit_ids):
503 comment_data = {
502 comment_data = {
504 'comment_type': self.request.POST.get('comment_type'),
503 'comment_type': self.request.POST.get('comment_type'),
505 'text': self.request.POST.get('text'),
504 'text': self.request.POST.get('text'),
506 'status': self.request.POST.get('changeset_status', None),
505 'status': self.request.POST.get('changeset_status', None),
507 'is_draft': self.request.POST.get('draft'),
506 'is_draft': self.request.POST.get('draft'),
508 'resolves_comment_id': self.request.POST.get('resolves_comment_id', None),
507 'resolves_comment_id': self.request.POST.get('resolves_comment_id', None),
509 'close_pull_request': self.request.POST.get('close_pull_request'),
508 'close_pull_request': self.request.POST.get('close_pull_request'),
510 'f_path': self.request.POST.get('f_path'),
509 'f_path': self.request.POST.get('f_path'),
511 'line': self.request.POST.get('line'),
510 'line': self.request.POST.get('line'),
512 }
511 }
513 comment = self._commit_comments_create(commit_id=current_id, comments=[comment_data])
512 comment = self._commit_comments_create(commit_id=current_id, comments=[comment_data])
514 data.append(comment)
513 data.append(comment)
515
514
516 return data if len(data) > 1 else data[0]
515 return data if len(data) > 1 else data[0]
517
516
518 @LoginRequired()
517 @LoginRequired()
519 @NotAnonymous()
518 @NotAnonymous()
520 @HasRepoPermissionAnyDecorator(
519 @HasRepoPermissionAnyDecorator(
521 'repository.read', 'repository.write', 'repository.admin')
520 'repository.read', 'repository.write', 'repository.admin')
522 @CSRFRequired()
521 @CSRFRequired()
523 def repo_commit_comment_preview(self):
522 def repo_commit_comment_preview(self):
524 # Technically a CSRF token is not needed as no state changes with this
523 # Technically a CSRF token is not needed as no state changes with this
525 # call. However, as this is a POST is better to have it, so automated
524 # call. However, as this is a POST is better to have it, so automated
526 # tools don't flag it as potential CSRF.
525 # tools don't flag it as potential CSRF.
527 # Post is required because the payload could be bigger than the maximum
526 # Post is required because the payload could be bigger than the maximum
528 # allowed by GET.
527 # allowed by GET.
529
528
530 text = self.request.POST.get('text')
529 text = self.request.POST.get('text')
531 renderer = self.request.POST.get('renderer') or 'rst'
530 renderer = self.request.POST.get('renderer') or 'rst'
532 if text:
531 if text:
533 return h.render(text, renderer=renderer, mentions=True,
532 return h.render(text, renderer=renderer, mentions=True,
534 repo_name=self.db_repo_name)
533 repo_name=self.db_repo_name)
535 return ''
534 return ''
536
535
537 @LoginRequired()
536 @LoginRequired()
538 @HasRepoPermissionAnyDecorator(
537 @HasRepoPermissionAnyDecorator(
539 'repository.read', 'repository.write', 'repository.admin')
538 'repository.read', 'repository.write', 'repository.admin')
540 @CSRFRequired()
539 @CSRFRequired()
541 def repo_commit_comment_history_view(self):
540 def repo_commit_comment_history_view(self):
542 c = self.load_default_context()
541 c = self.load_default_context()
543 comment_id = self.request.matchdict['comment_id']
542 comment_id = self.request.matchdict['comment_id']
544 comment_history_id = self.request.matchdict['comment_history_id']
543 comment_history_id = self.request.matchdict['comment_history_id']
545
544
546 comment = ChangesetComment.get_or_404(comment_id)
545 comment = ChangesetComment.get_or_404(comment_id)
547 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
546 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
548 if comment.draft and not comment_owner:
547 if comment.draft and not comment_owner:
549 # if we see draft comments history, we only allow this for owner
548 # if we see draft comments history, we only allow this for owner
550 raise HTTPNotFound()
549 raise HTTPNotFound()
551
550
552 comment_history = ChangesetCommentHistory.get_or_404(comment_history_id)
551 comment_history = ChangesetCommentHistory.get_or_404(comment_history_id)
553 is_repo_comment = comment_history.comment.repo.repo_id == self.db_repo.repo_id
552 is_repo_comment = comment_history.comment.repo.repo_id == self.db_repo.repo_id
554
553
555 if is_repo_comment:
554 if is_repo_comment:
556 c.comment_history = comment_history
555 c.comment_history = comment_history
557
556
558 rendered_comment = render(
557 rendered_comment = render(
559 'rhodecode:templates/changeset/comment_history.mako',
558 'rhodecode:templates/changeset/comment_history.mako',
560 self._get_template_context(c), self.request)
559 self._get_template_context(c), self.request)
561 return rendered_comment
560 return rendered_comment
562 else:
561 else:
563 log.warning('No permissions for user %s to show comment_history_id: %s',
562 log.warning('No permissions for user %s to show comment_history_id: %s',
564 self._rhodecode_db_user, comment_history_id)
563 self._rhodecode_db_user, comment_history_id)
565 raise HTTPNotFound()
564 raise HTTPNotFound()
566
565
567 @LoginRequired()
566 @LoginRequired()
568 @NotAnonymous()
567 @NotAnonymous()
569 @HasRepoPermissionAnyDecorator(
568 @HasRepoPermissionAnyDecorator(
570 'repository.read', 'repository.write', 'repository.admin')
569 'repository.read', 'repository.write', 'repository.admin')
571 @CSRFRequired()
570 @CSRFRequired()
572 def repo_commit_comment_attachment_upload(self):
571 def repo_commit_comment_attachment_upload(self):
573 c = self.load_default_context()
572 c = self.load_default_context()
574 upload_key = 'attachment'
573 upload_key = 'attachment'
575
574
576 file_obj = self.request.POST.get(upload_key)
575 file_obj = self.request.POST.get(upload_key)
577
576
578 if file_obj is None:
577 if file_obj is None:
579 self.request.response.status = 400
578 self.request.response.status = 400
580 return {'store_fid': None,
579 return {'store_fid': None,
581 'access_path': None,
580 'access_path': None,
582 'error': '{} data field is missing'.format(upload_key)}
581 'error': f'{upload_key} data field is missing'}
583
582
584 if not hasattr(file_obj, 'filename'):
583 if not hasattr(file_obj, 'filename'):
585 self.request.response.status = 400
584 self.request.response.status = 400
586 return {'store_fid': None,
585 return {'store_fid': None,
587 'access_path': None,
586 'access_path': None,
588 'error': 'filename cannot be read from the data field'}
587 'error': 'filename cannot be read from the data field'}
589
588
590 filename = file_obj.filename
589 filename = file_obj.filename
591 file_display_name = filename
590 file_display_name = filename
592
591
593 metadata = {
592 metadata = {
594 'user_uploaded': {'username': self._rhodecode_user.username,
593 'user_uploaded': {'username': self._rhodecode_user.username,
595 'user_id': self._rhodecode_user.user_id,
594 'user_id': self._rhodecode_user.user_id,
596 'ip': self._rhodecode_user.ip_addr}}
595 'ip': self._rhodecode_user.ip_addr}}
597
596
598 # TODO(marcink): allow .ini configuration for allowed_extensions, and file-size
597 # TODO(marcink): allow .ini configuration for allowed_extensions, and file-size
599 allowed_extensions = [
598 allowed_extensions = [
600 'gif', '.jpeg', '.jpg', '.png', '.docx', '.gz', '.log', '.pdf',
599 'gif', '.jpeg', '.jpg', '.png', '.docx', '.gz', '.log', '.pdf',
601 '.pptx', '.txt', '.xlsx', '.zip']
600 '.pptx', '.txt', '.xlsx', '.zip']
602 max_file_size = 10 * 1024 * 1024 # 10MB, also validated via dropzone.js
601 max_file_size = 10 * 1024 * 1024 # 10MB, also validated via dropzone.js
603
602
604 try:
603 try:
605 storage = store_utils.get_file_storage(self.request.registry.settings)
604 storage = store_utils.get_file_storage(self.request.registry.settings)
606 store_uid, metadata = storage.save_file(
605 store_uid, metadata = storage.save_file(
607 file_obj.file, filename, extra_metadata=metadata,
606 file_obj.file, filename, extra_metadata=metadata,
608 extensions=allowed_extensions, max_filesize=max_file_size)
607 extensions=allowed_extensions, max_filesize=max_file_size)
609 except FileNotAllowedException:
608 except FileNotAllowedException:
610 self.request.response.status = 400
609 self.request.response.status = 400
611 permitted_extensions = ', '.join(allowed_extensions)
610 permitted_extensions = ', '.join(allowed_extensions)
612 error_msg = 'File `{}` is not allowed. ' \
611 error_msg = 'File `{}` is not allowed. ' \
613 'Only following extensions are permitted: {}'.format(
612 'Only following extensions are permitted: {}'.format(
614 filename, permitted_extensions)
613 filename, permitted_extensions)
615 return {'store_fid': None,
614 return {'store_fid': None,
616 'access_path': None,
615 'access_path': None,
617 'error': error_msg}
616 'error': error_msg}
618 except FileOverSizeException:
617 except FileOverSizeException:
619 self.request.response.status = 400
618 self.request.response.status = 400
620 limit_mb = h.format_byte_size_binary(max_file_size)
619 limit_mb = h.format_byte_size_binary(max_file_size)
621 return {'store_fid': None,
620 return {'store_fid': None,
622 'access_path': None,
621 'access_path': None,
623 'error': 'File {} is exceeding allowed limit of {}.'.format(
622 'error': 'File {} is exceeding allowed limit of {}.'.format(
624 filename, limit_mb)}
623 filename, limit_mb)}
625
624
626 try:
625 try:
627 entry = FileStore.create(
626 entry = FileStore.create(
628 file_uid=store_uid, filename=metadata["filename"],
627 file_uid=store_uid, filename=metadata["filename"],
629 file_hash=metadata["sha256"], file_size=metadata["size"],
628 file_hash=metadata["sha256"], file_size=metadata["size"],
630 file_display_name=file_display_name,
629 file_display_name=file_display_name,
631 file_description=u'comment attachment `{}`'.format(safe_str(filename)),
630 file_description=f'comment attachment `{safe_str(filename)}`',
632 hidden=True, check_acl=True, user_id=self._rhodecode_user.user_id,
631 hidden=True, check_acl=True, user_id=self._rhodecode_user.user_id,
633 scope_repo_id=self.db_repo.repo_id
632 scope_repo_id=self.db_repo.repo_id
634 )
633 )
635 Session().add(entry)
634 Session().add(entry)
636 Session().commit()
635 Session().commit()
637 log.debug('Stored upload in DB as %s', entry)
636 log.debug('Stored upload in DB as %s', entry)
638 except Exception:
637 except Exception:
639 log.exception('Failed to store file %s', filename)
638 log.exception('Failed to store file %s', filename)
640 self.request.response.status = 400
639 self.request.response.status = 400
641 return {'store_fid': None,
640 return {'store_fid': None,
642 'access_path': None,
641 'access_path': None,
643 'error': 'File {} failed to store in DB.'.format(filename)}
642 'error': f'File {filename} failed to store in DB.'}
644
643
645 Session().commit()
644 Session().commit()
646
645
647 data = {
646 data = {
648 'store_fid': store_uid,
647 'store_fid': store_uid,
649 'access_path': h.route_path(
648 'access_path': h.route_path(
650 'download_file', fid=store_uid),
649 'download_file', fid=store_uid),
651 'fqn_access_path': h.route_url(
650 'fqn_access_path': h.route_url(
652 'download_file', fid=store_uid),
651 'download_file', fid=store_uid),
653 # for EE those are replaced by FQN links on repo-only like
652 # for EE those are replaced by FQN links on repo-only like
654 'repo_access_path': h.route_url(
653 'repo_access_path': h.route_url(
655 'download_file', fid=store_uid),
654 'download_file', fid=store_uid),
656 'repo_fqn_access_path': h.route_url(
655 'repo_fqn_access_path': h.route_url(
657 'download_file', fid=store_uid),
656 'download_file', fid=store_uid),
658 }
657 }
659 # this data is a part of CE/EE additional code
658 # this data is a part of CE/EE additional code
660 if c.rhodecode_edition_id == 'EE':
659 if c.rhodecode_edition_id == 'EE':
661 data.update({
660 data.update({
662 'repo_access_path': h.route_path(
661 'repo_access_path': h.route_path(
663 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
662 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
664 'repo_fqn_access_path': h.route_url(
663 'repo_fqn_access_path': h.route_url(
665 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
664 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
666 })
665 })
667
666
668 return data
667 return data
669
668
670 @LoginRequired()
669 @LoginRequired()
671 @NotAnonymous()
670 @NotAnonymous()
672 @HasRepoPermissionAnyDecorator(
671 @HasRepoPermissionAnyDecorator(
673 'repository.read', 'repository.write', 'repository.admin')
672 'repository.read', 'repository.write', 'repository.admin')
674 @CSRFRequired()
673 @CSRFRequired()
675 def repo_commit_comment_delete(self):
674 def repo_commit_comment_delete(self):
676 commit_id = self.request.matchdict['commit_id']
675 commit_id = self.request.matchdict['commit_id']
677 comment_id = self.request.matchdict['comment_id']
676 comment_id = self.request.matchdict['comment_id']
678
677
679 comment = ChangesetComment.get_or_404(comment_id)
678 comment = ChangesetComment.get_or_404(comment_id)
680 if not comment:
679 if not comment:
681 log.debug('Comment with id:%s not found, skipping', comment_id)
680 log.debug('Comment with id:%s not found, skipping', comment_id)
682 # comment already deleted in another call probably
681 # comment already deleted in another call probably
683 return True
682 return True
684
683
685 if comment.immutable:
684 if comment.immutable:
686 # don't allow deleting comments that are immutable
685 # don't allow deleting comments that are immutable
687 raise HTTPForbidden()
686 raise HTTPForbidden()
688
687
689 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
688 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
690 super_admin = h.HasPermissionAny('hg.admin')()
689 super_admin = h.HasPermissionAny('hg.admin')()
691 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
690 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
692 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
691 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
693 comment_repo_admin = is_repo_admin and is_repo_comment
692 comment_repo_admin = is_repo_admin and is_repo_comment
694
693
695 if comment.draft and not comment_owner:
694 if comment.draft and not comment_owner:
696 # We never allow to delete draft comments for other than owners
695 # We never allow to delete draft comments for other than owners
697 raise HTTPNotFound()
696 raise HTTPNotFound()
698
697
699 if super_admin or comment_owner or comment_repo_admin:
698 if super_admin or comment_owner or comment_repo_admin:
700 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
699 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
701 Session().commit()
700 Session().commit()
702 return True
701 return True
703 else:
702 else:
704 log.warning('No permissions for user %s to delete comment_id: %s',
703 log.warning('No permissions for user %s to delete comment_id: %s',
705 self._rhodecode_db_user, comment_id)
704 self._rhodecode_db_user, comment_id)
706 raise HTTPNotFound()
705 raise HTTPNotFound()
707
706
708 @LoginRequired()
707 @LoginRequired()
709 @NotAnonymous()
708 @NotAnonymous()
710 @HasRepoPermissionAnyDecorator(
709 @HasRepoPermissionAnyDecorator(
711 'repository.read', 'repository.write', 'repository.admin')
710 'repository.read', 'repository.write', 'repository.admin')
712 @CSRFRequired()
711 @CSRFRequired()
713 def repo_commit_comment_edit(self):
712 def repo_commit_comment_edit(self):
714 self.load_default_context()
713 self.load_default_context()
715
714
716 commit_id = self.request.matchdict['commit_id']
715 commit_id = self.request.matchdict['commit_id']
717 comment_id = self.request.matchdict['comment_id']
716 comment_id = self.request.matchdict['comment_id']
718 comment = ChangesetComment.get_or_404(comment_id)
717 comment = ChangesetComment.get_or_404(comment_id)
719
718
720 if comment.immutable:
719 if comment.immutable:
721 # don't allow deleting comments that are immutable
720 # don't allow deleting comments that are immutable
722 raise HTTPForbidden()
721 raise HTTPForbidden()
723
722
724 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
723 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
725 super_admin = h.HasPermissionAny('hg.admin')()
724 super_admin = h.HasPermissionAny('hg.admin')()
726 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
725 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
727 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
726 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
728 comment_repo_admin = is_repo_admin and is_repo_comment
727 comment_repo_admin = is_repo_admin and is_repo_comment
729
728
730 if super_admin or comment_owner or comment_repo_admin:
729 if super_admin or comment_owner or comment_repo_admin:
731 text = self.request.POST.get('text')
730 text = self.request.POST.get('text')
732 version = self.request.POST.get('version')
731 version = self.request.POST.get('version')
733 if text == comment.text:
732 if text == comment.text:
734 log.warning(
733 log.warning(
735 'Comment(repo): '
734 'Comment(repo): '
736 'Trying to create new version '
735 'Trying to create new version '
737 'with the same comment body {}'.format(
736 'with the same comment body {}'.format(
738 comment_id,
737 comment_id,
739 )
738 )
740 )
739 )
741 raise HTTPNotFound()
740 raise HTTPNotFound()
742
741
743 if version.isdigit():
742 if version.isdigit():
744 version = int(version)
743 version = int(version)
745 else:
744 else:
746 log.warning(
745 log.warning(
747 'Comment(repo): Wrong version type {} {} '
746 'Comment(repo): Wrong version type {} {} '
748 'for comment {}'.format(
747 'for comment {}'.format(
749 version,
748 version,
750 type(version),
749 type(version),
751 comment_id,
750 comment_id,
752 )
751 )
753 )
752 )
754 raise HTTPNotFound()
753 raise HTTPNotFound()
755
754
756 try:
755 try:
757 comment_history = CommentsModel().edit(
756 comment_history = CommentsModel().edit(
758 comment_id=comment_id,
757 comment_id=comment_id,
759 text=text,
758 text=text,
760 auth_user=self._rhodecode_user,
759 auth_user=self._rhodecode_user,
761 version=version,
760 version=version,
762 )
761 )
763 except CommentVersionMismatch:
762 except CommentVersionMismatch:
764 raise HTTPConflict()
763 raise HTTPConflict()
765
764
766 if not comment_history:
765 if not comment_history:
767 raise HTTPNotFound()
766 raise HTTPNotFound()
768
767
769 if not comment.draft:
768 if not comment.draft:
770 commit = self.db_repo.get_commit(commit_id)
769 commit = self.db_repo.get_commit(commit_id)
771 CommentsModel().trigger_commit_comment_hook(
770 CommentsModel().trigger_commit_comment_hook(
772 self.db_repo, self._rhodecode_user, 'edit',
771 self.db_repo, self._rhodecode_user, 'edit',
773 data={'comment': comment, 'commit': commit})
772 data={'comment': comment, 'commit': commit})
774
773
775 Session().commit()
774 Session().commit()
776 return {
775 return {
777 'comment_history_id': comment_history.comment_history_id,
776 'comment_history_id': comment_history.comment_history_id,
778 'comment_id': comment.comment_id,
777 'comment_id': comment.comment_id,
779 'comment_version': comment_history.version,
778 'comment_version': comment_history.version,
780 'comment_author_username': comment_history.author.username,
779 'comment_author_username': comment_history.author.username,
781 'comment_author_gravatar': h.gravatar_url(comment_history.author.email, 16, request=self.request),
780 'comment_author_gravatar': h.gravatar_url(comment_history.author.email, 16, request=self.request),
782 'comment_created_on': h.age_component(comment_history.created_on,
781 'comment_created_on': h.age_component(comment_history.created_on,
783 time_is_local=True),
782 time_is_local=True),
784 }
783 }
785 else:
784 else:
786 log.warning('No permissions for user %s to edit comment_id: %s',
785 log.warning('No permissions for user %s to edit comment_id: %s',
787 self._rhodecode_db_user, comment_id)
786 self._rhodecode_db_user, comment_id)
788 raise HTTPNotFound()
787 raise HTTPNotFound()
789
788
790 @LoginRequired()
789 @LoginRequired()
791 @HasRepoPermissionAnyDecorator(
790 @HasRepoPermissionAnyDecorator(
792 'repository.read', 'repository.write', 'repository.admin')
791 'repository.read', 'repository.write', 'repository.admin')
793 def repo_commit_data(self):
792 def repo_commit_data(self):
794 commit_id = self.request.matchdict['commit_id']
793 commit_id = self.request.matchdict['commit_id']
795 self.load_default_context()
794 self.load_default_context()
796
795
797 try:
796 try:
798 return self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
797 return self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
799 except CommitDoesNotExistError as e:
798 except CommitDoesNotExistError as e:
800 return EmptyCommit(message=str(e))
799 return EmptyCommit(message=str(e))
801
800
802 @LoginRequired()
801 @LoginRequired()
803 @HasRepoPermissionAnyDecorator(
802 @HasRepoPermissionAnyDecorator(
804 'repository.read', 'repository.write', 'repository.admin')
803 'repository.read', 'repository.write', 'repository.admin')
805 def repo_commit_children(self):
804 def repo_commit_children(self):
806 commit_id = self.request.matchdict['commit_id']
805 commit_id = self.request.matchdict['commit_id']
807 self.load_default_context()
806 self.load_default_context()
808
807
809 try:
808 try:
810 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
809 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
811 children = commit.children
810 children = commit.children
812 except CommitDoesNotExistError:
811 except CommitDoesNotExistError:
813 children = []
812 children = []
814
813
815 result = {"results": children}
814 result = {"results": children}
816 return result
815 return result
817
816
818 @LoginRequired()
817 @LoginRequired()
819 @HasRepoPermissionAnyDecorator(
818 @HasRepoPermissionAnyDecorator(
820 'repository.read', 'repository.write', 'repository.admin')
819 'repository.read', 'repository.write', 'repository.admin')
821 def repo_commit_parents(self):
820 def repo_commit_parents(self):
822 commit_id = self.request.matchdict['commit_id']
821 commit_id = self.request.matchdict['commit_id']
823 self.load_default_context()
822 self.load_default_context()
824
823
825 try:
824 try:
826 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
825 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
827 parents = commit.parents
826 parents = commit.parents
828 except CommitDoesNotExistError:
827 except CommitDoesNotExistError:
829 parents = []
828 parents = []
830 result = {"results": parents}
829 result = {"results": parents}
831 return result
830 return result
@@ -1,307 +1,305 b''
1
2
3 # Copyright (C) 2012-2023 RhodeCode GmbH
1 # Copyright (C) 2012-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21
19
22 import logging
20 import logging
23
21
24 from pyramid.httpexceptions import HTTPBadRequest, HTTPNotFound, HTTPFound
22 from pyramid.httpexceptions import HTTPBadRequest, HTTPNotFound, HTTPFound
25
23
26 from pyramid.renderers import render
24 from pyramid.renderers import render
27 from pyramid.response import Response
25 from pyramid.response import Response
28
26
29 from rhodecode.apps._base import RepoAppView
27 from rhodecode.apps._base import RepoAppView
30
28
31 from rhodecode.lib import helpers as h
29 from rhodecode.lib import helpers as h
32 from rhodecode.lib import diffs, codeblocks
30 from rhodecode.lib import diffs, codeblocks
33 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
31 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
34 from rhodecode.lib.utils import safe_str
32 from rhodecode.lib.utils import safe_str
35 from rhodecode.lib.utils2 import str2bool
33 from rhodecode.lib.utils2 import str2bool
36 from rhodecode.lib.view_utils import parse_path_ref, get_commit_from_ref_name
34 from rhodecode.lib.view_utils import parse_path_ref, get_commit_from_ref_name
37 from rhodecode.lib.vcs.exceptions import (
35 from rhodecode.lib.vcs.exceptions import (
38 EmptyRepositoryError, RepositoryError, RepositoryRequirementError,
36 EmptyRepositoryError, RepositoryError, RepositoryRequirementError,
39 NodeDoesNotExistError)
37 NodeDoesNotExistError)
40 from rhodecode.model.db import Repository, ChangesetStatus
38 from rhodecode.model.db import Repository, ChangesetStatus
41
39
42 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
43
41
44
42
45 class RepoCompareView(RepoAppView):
43 class RepoCompareView(RepoAppView):
46 def load_default_context(self):
44 def load_default_context(self):
47 c = self._get_local_tmpl_context(include_app_defaults=True)
45 c = self._get_local_tmpl_context(include_app_defaults=True)
48 c.rhodecode_repo = self.rhodecode_vcs_repo
46 c.rhodecode_repo = self.rhodecode_vcs_repo
49 return c
47 return c
50
48
51 def _get_commit_or_redirect(
49 def _get_commit_or_redirect(
52 self, ref, ref_type, repo, redirect_after=True, partial=False):
50 self, ref, ref_type, repo, redirect_after=True, partial=False):
53 """
51 """
54 This is a safe way to get a commit. If an error occurs it
52 This is a safe way to get a commit. If an error occurs it
55 redirects to a commit with a proper message. If partial is set
53 redirects to a commit with a proper message. If partial is set
56 then it does not do redirect raise and throws an exception instead.
54 then it does not do redirect raise and throws an exception instead.
57 """
55 """
58 _ = self.request.translate
56 _ = self.request.translate
59 try:
57 try:
60 return get_commit_from_ref_name(repo, safe_str(ref), ref_type)
58 return get_commit_from_ref_name(repo, safe_str(ref), ref_type)
61 except EmptyRepositoryError:
59 except EmptyRepositoryError:
62 if not redirect_after:
60 if not redirect_after:
63 return repo.scm_instance().EMPTY_COMMIT
61 return repo.scm_instance().EMPTY_COMMIT
64 h.flash(h.literal(_('There are no commits yet')),
62 h.flash(h.literal(_('There are no commits yet')),
65 category='warning')
63 category='warning')
66 if not partial:
64 if not partial:
67 raise HTTPFound(
65 raise HTTPFound(
68 h.route_path('repo_summary', repo_name=repo.repo_name))
66 h.route_path('repo_summary', repo_name=repo.repo_name))
69 raise HTTPBadRequest()
67 raise HTTPBadRequest()
70
68
71 except RepositoryError as e:
69 except RepositoryError as e:
72 log.exception(safe_str(e))
70 log.exception(safe_str(e))
73 h.flash(h.escape(safe_str(e)), category='warning')
71 h.flash(h.escape(safe_str(e)), category='warning')
74 if not partial:
72 if not partial:
75 raise HTTPFound(
73 raise HTTPFound(
76 h.route_path('repo_summary', repo_name=repo.repo_name))
74 h.route_path('repo_summary', repo_name=repo.repo_name))
77 raise HTTPBadRequest()
75 raise HTTPBadRequest()
78
76
79 @LoginRequired()
77 @LoginRequired()
80 @HasRepoPermissionAnyDecorator(
78 @HasRepoPermissionAnyDecorator(
81 'repository.read', 'repository.write', 'repository.admin')
79 'repository.read', 'repository.write', 'repository.admin')
82 def compare_select(self):
80 def compare_select(self):
83 _ = self.request.translate
81 _ = self.request.translate
84 c = self.load_default_context()
82 c = self.load_default_context()
85
83
86 source_repo = self.db_repo_name
84 source_repo = self.db_repo_name
87 target_repo = self.request.GET.get('target_repo', source_repo)
85 target_repo = self.request.GET.get('target_repo', source_repo)
88 c.source_repo = Repository.get_by_repo_name(source_repo)
86 c.source_repo = Repository.get_by_repo_name(source_repo)
89 c.target_repo = Repository.get_by_repo_name(target_repo)
87 c.target_repo = Repository.get_by_repo_name(target_repo)
90
88
91 if c.source_repo is None or c.target_repo is None:
89 if c.source_repo is None or c.target_repo is None:
92 raise HTTPNotFound()
90 raise HTTPNotFound()
93
91
94 c.compare_home = True
92 c.compare_home = True
95 c.commit_ranges = []
93 c.commit_ranges = []
96 c.collapse_all_commits = False
94 c.collapse_all_commits = False
97 c.diffset = None
95 c.diffset = None
98 c.limited_diff = False
96 c.limited_diff = False
99 c.source_ref = c.target_ref = _('Select commit')
97 c.source_ref = c.target_ref = _('Select commit')
100 c.source_ref_type = ""
98 c.source_ref_type = ""
101 c.target_ref_type = ""
99 c.target_ref_type = ""
102 c.commit_statuses = ChangesetStatus.STATUSES
100 c.commit_statuses = ChangesetStatus.STATUSES
103 c.preview_mode = False
101 c.preview_mode = False
104 c.file_path = None
102 c.file_path = None
105
103
106 return self._get_template_context(c)
104 return self._get_template_context(c)
107
105
108 @LoginRequired()
106 @LoginRequired()
109 @HasRepoPermissionAnyDecorator(
107 @HasRepoPermissionAnyDecorator(
110 'repository.read', 'repository.write', 'repository.admin')
108 'repository.read', 'repository.write', 'repository.admin')
111 def compare(self):
109 def compare(self):
112 _ = self.request.translate
110 _ = self.request.translate
113 c = self.load_default_context()
111 c = self.load_default_context()
114
112
115 source_ref_type = self.request.matchdict['source_ref_type']
113 source_ref_type = self.request.matchdict['source_ref_type']
116 source_ref = self.request.matchdict['source_ref']
114 source_ref = self.request.matchdict['source_ref']
117 target_ref_type = self.request.matchdict['target_ref_type']
115 target_ref_type = self.request.matchdict['target_ref_type']
118 target_ref = self.request.matchdict['target_ref']
116 target_ref = self.request.matchdict['target_ref']
119
117
120 # source_ref will be evaluated in source_repo
118 # source_ref will be evaluated in source_repo
121 source_repo_name = self.db_repo_name
119 source_repo_name = self.db_repo_name
122 source_path, source_id = parse_path_ref(source_ref)
120 source_path, source_id = parse_path_ref(source_ref)
123
121
124 # target_ref will be evaluated in target_repo
122 # target_ref will be evaluated in target_repo
125 target_repo_name = self.request.GET.get('target_repo', source_repo_name)
123 target_repo_name = self.request.GET.get('target_repo', source_repo_name)
126 target_path, target_id = parse_path_ref(
124 target_path, target_id = parse_path_ref(
127 target_ref, default_path=self.request.GET.get('f_path', ''))
125 target_ref, default_path=self.request.GET.get('f_path', ''))
128
126
129 # if merge is True
127 # if merge is True
130 # Show what changes since the shared ancestor commit of target/source
128 # Show what changes since the shared ancestor commit of target/source
131 # the source would get if it was merged with target. Only commits
129 # the source would get if it was merged with target. Only commits
132 # which are in target but not in source will be shown.
130 # which are in target but not in source will be shown.
133 merge = str2bool(self.request.GET.get('merge'))
131 merge = str2bool(self.request.GET.get('merge'))
134 # if merge is False
132 # if merge is False
135 # Show a raw diff of source/target refs even if no ancestor exists
133 # Show a raw diff of source/target refs even if no ancestor exists
136
134
137 # c.fulldiff disables cut_off_limit
135 # c.fulldiff disables cut_off_limit
138 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
136 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
139
137
140 # fetch global flags of ignore ws or context lines
138 # fetch global flags of ignore ws or context lines
141 diff_context = diffs.get_diff_context(self.request)
139 diff_context = diffs.get_diff_context(self.request)
142 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
140 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
143
141
144 c.file_path = target_path
142 c.file_path = target_path
145 c.commit_statuses = ChangesetStatus.STATUSES
143 c.commit_statuses = ChangesetStatus.STATUSES
146
144
147 # if partial, returns just compare_commits.html (commits log)
145 # if partial, returns just compare_commits.html (commits log)
148 partial = self.request.is_xhr
146 partial = self.request.is_xhr
149
147
150 # swap url for compare_diff page
148 # swap url for compare_diff page
151 c.swap_url = h.route_path(
149 c.swap_url = h.route_path(
152 'repo_compare',
150 'repo_compare',
153 repo_name=target_repo_name,
151 repo_name=target_repo_name,
154 source_ref_type=target_ref_type,
152 source_ref_type=target_ref_type,
155 source_ref=target_ref,
153 source_ref=target_ref,
156 target_repo=source_repo_name,
154 target_repo=source_repo_name,
157 target_ref_type=source_ref_type,
155 target_ref_type=source_ref_type,
158 target_ref=source_ref,
156 target_ref=source_ref,
159 _query=dict(merge=merge and '1' or '', f_path=target_path))
157 _query=dict(merge=merge and '1' or '', f_path=target_path))
160
158
161 source_repo = Repository.get_by_repo_name(source_repo_name)
159 source_repo = Repository.get_by_repo_name(source_repo_name)
162 target_repo = Repository.get_by_repo_name(target_repo_name)
160 target_repo = Repository.get_by_repo_name(target_repo_name)
163
161
164 if source_repo is None:
162 if source_repo is None:
165 log.error('Could not find the source repo: {}'
163 log.error('Could not find the source repo: {}'
166 .format(source_repo_name))
164 .format(source_repo_name))
167 h.flash(_('Could not find the source repo: `{}`')
165 h.flash(_('Could not find the source repo: `{}`')
168 .format(h.escape(source_repo_name)), category='error')
166 .format(h.escape(source_repo_name)), category='error')
169 raise HTTPFound(
167 raise HTTPFound(
170 h.route_path('repo_compare_select', repo_name=self.db_repo_name))
168 h.route_path('repo_compare_select', repo_name=self.db_repo_name))
171
169
172 if target_repo is None:
170 if target_repo is None:
173 log.error('Could not find the target repo: {}'
171 log.error('Could not find the target repo: {}'
174 .format(source_repo_name))
172 .format(source_repo_name))
175 h.flash(_('Could not find the target repo: `{}`')
173 h.flash(_('Could not find the target repo: `{}`')
176 .format(h.escape(target_repo_name)), category='error')
174 .format(h.escape(target_repo_name)), category='error')
177 raise HTTPFound(
175 raise HTTPFound(
178 h.route_path('repo_compare_select', repo_name=self.db_repo_name))
176 h.route_path('repo_compare_select', repo_name=self.db_repo_name))
179
177
180 source_scm = source_repo.scm_instance()
178 source_scm = source_repo.scm_instance()
181 target_scm = target_repo.scm_instance()
179 target_scm = target_repo.scm_instance()
182
180
183 source_alias = source_scm.alias
181 source_alias = source_scm.alias
184 target_alias = target_scm.alias
182 target_alias = target_scm.alias
185 if source_alias != target_alias:
183 if source_alias != target_alias:
186 msg = _('The comparison of two different kinds of remote repos '
184 msg = _('The comparison of two different kinds of remote repos '
187 'is not available')
185 'is not available')
188 log.error(msg)
186 log.error(msg)
189 h.flash(msg, category='error')
187 h.flash(msg, category='error')
190 raise HTTPFound(
188 raise HTTPFound(
191 h.route_path('repo_compare_select', repo_name=self.db_repo_name))
189 h.route_path('repo_compare_select', repo_name=self.db_repo_name))
192
190
193 source_commit = self._get_commit_or_redirect(
191 source_commit = self._get_commit_or_redirect(
194 ref=source_id, ref_type=source_ref_type, repo=source_repo,
192 ref=source_id, ref_type=source_ref_type, repo=source_repo,
195 partial=partial)
193 partial=partial)
196 target_commit = self._get_commit_or_redirect(
194 target_commit = self._get_commit_or_redirect(
197 ref=target_id, ref_type=target_ref_type, repo=target_repo,
195 ref=target_id, ref_type=target_ref_type, repo=target_repo,
198 partial=partial)
196 partial=partial)
199
197
200 c.compare_home = False
198 c.compare_home = False
201 c.source_repo = source_repo
199 c.source_repo = source_repo
202 c.target_repo = target_repo
200 c.target_repo = target_repo
203 c.source_ref = source_ref
201 c.source_ref = source_ref
204 c.target_ref = target_ref
202 c.target_ref = target_ref
205 c.source_ref_type = source_ref_type
203 c.source_ref_type = source_ref_type
206 c.target_ref_type = target_ref_type
204 c.target_ref_type = target_ref_type
207
205
208 pre_load = ["author", "date", "message", "branch"]
206 pre_load = ["author", "date", "message", "branch"]
209 c.ancestor = None
207 c.ancestor = None
210
208
211 try:
209 try:
212 c.commit_ranges = source_scm.compare(
210 c.commit_ranges = source_scm.compare(
213 source_commit.raw_id, target_commit.raw_id,
211 source_commit.raw_id, target_commit.raw_id,
214 target_scm, merge, pre_load=pre_load) or []
212 target_scm, merge, pre_load=pre_load) or []
215 if merge:
213 if merge:
216 c.ancestor = source_scm.get_common_ancestor(
214 c.ancestor = source_scm.get_common_ancestor(
217 source_commit.raw_id, target_commit.raw_id, target_scm)
215 source_commit.raw_id, target_commit.raw_id, target_scm)
218 except RepositoryRequirementError:
216 except RepositoryRequirementError:
219 msg = _('Could not compare repos with different '
217 msg = _('Could not compare repos with different '
220 'large file settings')
218 'large file settings')
221 log.error(msg)
219 log.error(msg)
222 if partial:
220 if partial:
223 return Response(msg)
221 return Response(msg)
224 h.flash(msg, category='error')
222 h.flash(msg, category='error')
225 raise HTTPFound(
223 raise HTTPFound(
226 h.route_path('repo_compare_select',
224 h.route_path('repo_compare_select',
227 repo_name=self.db_repo_name))
225 repo_name=self.db_repo_name))
228
226
229 c.statuses = self.db_repo.statuses(
227 c.statuses = self.db_repo.statuses(
230 [x.raw_id for x in c.commit_ranges])
228 [x.raw_id for x in c.commit_ranges])
231
229
232 # auto collapse if we have more than limit
230 # auto collapse if we have more than limit
233 collapse_limit = diffs.DiffProcessor._collapse_commits_over
231 collapse_limit = diffs.DiffProcessor._collapse_commits_over
234 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
232 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
235
233
236 if partial: # for PR ajax commits loader
234 if partial: # for PR ajax commits loader
237 if not c.ancestor:
235 if not c.ancestor:
238 return Response('') # cannot merge if there is no ancestor
236 return Response('') # cannot merge if there is no ancestor
239
237
240 html = render(
238 html = render(
241 'rhodecode:templates/compare/compare_commits.mako',
239 'rhodecode:templates/compare/compare_commits.mako',
242 self._get_template_context(c), self.request)
240 self._get_template_context(c), self.request)
243 return Response(html)
241 return Response(html)
244
242
245 if c.ancestor:
243 if c.ancestor:
246 # case we want a simple diff without incoming commits,
244 # case we want a simple diff without incoming commits,
247 # previewing what will be merged.
245 # previewing what will be merged.
248 # Make the diff on target repo (which is known to have target_ref)
246 # Make the diff on target repo (which is known to have target_ref)
249 log.debug('Using ancestor %s as source_ref instead of %s',
247 log.debug('Using ancestor %s as source_ref instead of %s',
250 c.ancestor, source_ref)
248 c.ancestor, source_ref)
251 source_repo = target_repo
249 source_repo = target_repo
252 source_commit = target_repo.get_commit(commit_id=c.ancestor)
250 source_commit = target_repo.get_commit(commit_id=c.ancestor)
253
251
254 # diff_limit will cut off the whole diff if the limit is applied
252 # diff_limit will cut off the whole diff if the limit is applied
255 # otherwise it will just hide the big files from the front-end
253 # otherwise it will just hide the big files from the front-end
256 diff_limit = c.visual.cut_off_limit_diff
254 diff_limit = c.visual.cut_off_limit_diff
257 file_limit = c.visual.cut_off_limit_file
255 file_limit = c.visual.cut_off_limit_file
258
256
259 log.debug('calculating diff between '
257 log.debug('calculating diff between '
260 'source_ref:%s and target_ref:%s for repo `%s`',
258 'source_ref:%s and target_ref:%s for repo `%s`',
261 source_commit, target_commit,
259 source_commit, target_commit,
262 safe_str(source_repo.scm_instance().path))
260 safe_str(source_repo.scm_instance().path))
263
261
264 if source_commit.repository != target_commit.repository:
262 if source_commit.repository != target_commit.repository:
265
263
266 msg = _(
264 msg = _(
267 "Repositories unrelated. "
265 "Repositories unrelated. "
268 "Cannot compare commit %(commit1)s from repository %(repo1)s "
266 "Cannot compare commit %(commit1)s from repository %(repo1)s "
269 "with commit %(commit2)s from repository %(repo2)s.") % {
267 "with commit %(commit2)s from repository %(repo2)s.") % {
270 'commit1': h.show_id(source_commit),
268 'commit1': h.show_id(source_commit),
271 'repo1': source_repo.repo_name,
269 'repo1': source_repo.repo_name,
272 'commit2': h.show_id(target_commit),
270 'commit2': h.show_id(target_commit),
273 'repo2': target_repo.repo_name,
271 'repo2': target_repo.repo_name,
274 }
272 }
275 h.flash(msg, category='error')
273 h.flash(msg, category='error')
276 raise HTTPFound(
274 raise HTTPFound(
277 h.route_path('repo_compare_select',
275 h.route_path('repo_compare_select',
278 repo_name=self.db_repo_name))
276 repo_name=self.db_repo_name))
279
277
280 txt_diff = source_repo.scm_instance().get_diff(
278 txt_diff = source_repo.scm_instance().get_diff(
281 commit1=source_commit, commit2=target_commit,
279 commit1=source_commit, commit2=target_commit,
282 path=target_path, path1=source_path,
280 path=target_path, path1=source_path,
283 ignore_whitespace=hide_whitespace_changes, context=diff_context)
281 ignore_whitespace=hide_whitespace_changes, context=diff_context)
284
282
285 diff_processor = diffs.DiffProcessor(txt_diff, diff_format='newdiff',
283 diff_processor = diffs.DiffProcessor(txt_diff, diff_format='newdiff',
286 diff_limit=diff_limit,
284 diff_limit=diff_limit,
287 file_limit=file_limit,
285 file_limit=file_limit,
288 show_full_diff=c.fulldiff)
286 show_full_diff=c.fulldiff)
289 _parsed = diff_processor.prepare()
287 _parsed = diff_processor.prepare()
290
288
291 diffset = codeblocks.DiffSet(
289 diffset = codeblocks.DiffSet(
292 repo_name=source_repo.repo_name,
290 repo_name=source_repo.repo_name,
293 source_node_getter=codeblocks.diffset_node_getter(source_commit),
291 source_node_getter=codeblocks.diffset_node_getter(source_commit),
294 target_repo_name=self.db_repo_name,
292 target_repo_name=self.db_repo_name,
295 target_node_getter=codeblocks.diffset_node_getter(target_commit),
293 target_node_getter=codeblocks.diffset_node_getter(target_commit),
296 )
294 )
297 c.diffset = self.path_filter.render_patchset_filtered(
295 c.diffset = self.path_filter.render_patchset_filtered(
298 diffset, _parsed, source_ref, target_ref)
296 diffset, _parsed, source_ref, target_ref)
299
297
300 c.preview_mode = merge
298 c.preview_mode = merge
301 c.source_commit = source_commit
299 c.source_commit = source_commit
302 c.target_commit = target_commit
300 c.target_commit = target_commit
303
301
304 html = render(
302 html = render(
305 'rhodecode:templates/compare/compare_diff.mako',
303 'rhodecode:templates/compare/compare_diff.mako',
306 self._get_template_context(c), self.request)
304 self._get_template_context(c), self.request)
307 return Response(html) No newline at end of file
305 return Response(html)
@@ -1,215 +1,213 b''
1
2
3 # Copyright (C) 2017-2023 RhodeCode GmbH
1 # Copyright (C) 2017-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 import logging
19 import logging
22 import datetime
20 import datetime
23
21
24 from pyramid.response import Response
22 from pyramid.response import Response
25
23
26 from rhodecode.apps._base import RepoAppView
24 from rhodecode.apps._base import RepoAppView
27 from rhodecode.lib.feedgenerator import Rss201rev2Feed, Atom1Feed
25 from rhodecode.lib.feedgenerator import Rss201rev2Feed, Atom1Feed
28 from rhodecode.lib import audit_logger
26 from rhodecode.lib import audit_logger
29 from rhodecode.lib import rc_cache
27 from rhodecode.lib import rc_cache
30 from rhodecode.lib import helpers as h
28 from rhodecode.lib import helpers as h
31 from rhodecode.lib.auth import (
29 from rhodecode.lib.auth import (
32 LoginRequired, HasRepoPermissionAnyDecorator)
30 LoginRequired, HasRepoPermissionAnyDecorator)
33 from rhodecode.lib.diffs import DiffProcessor, LimitedDiffContainer
31 from rhodecode.lib.diffs import DiffProcessor, LimitedDiffContainer
34 from rhodecode.lib.utils2 import str2bool, safe_int, md5_safe
32 from rhodecode.lib.utils2 import str2bool, safe_int, md5_safe
35 from rhodecode.model.db import UserApiKeys, CacheKey
33 from rhodecode.model.db import UserApiKeys, CacheKey
36
34
37 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
38
36
39
37
40 class RepoFeedView(RepoAppView):
38 class RepoFeedView(RepoAppView):
41 def load_default_context(self):
39 def load_default_context(self):
42 c = self._get_local_tmpl_context()
40 c = self._get_local_tmpl_context()
43 self._load_defaults()
41 self._load_defaults()
44 return c
42 return c
45
43
46 def _get_config(self):
44 def _get_config(self):
47 import rhodecode
45 import rhodecode
48 config = rhodecode.CONFIG
46 config = rhodecode.CONFIG
49
47
50 return {
48 return {
51 'language': 'en-us',
49 'language': 'en-us',
52 'feed_ttl': '5', # TTL of feed,
50 'feed_ttl': '5', # TTL of feed,
53 'feed_include_diff':
51 'feed_include_diff':
54 str2bool(config.get('rss_include_diff', False)),
52 str2bool(config.get('rss_include_diff', False)),
55 'feed_items_per_page':
53 'feed_items_per_page':
56 safe_int(config.get('rss_items_per_page', 20)),
54 safe_int(config.get('rss_items_per_page', 20)),
57 'feed_diff_limit':
55 'feed_diff_limit':
58 # we need to protect from parsing huge diffs here other way
56 # we need to protect from parsing huge diffs here other way
59 # we can kill the server
57 # we can kill the server
60 safe_int(config.get('rss_cut_off_limit', 32 * 1024)),
58 safe_int(config.get('rss_cut_off_limit', 32 * 1024)),
61 }
59 }
62
60
63 def _load_defaults(self):
61 def _load_defaults(self):
64 _ = self.request.translate
62 _ = self.request.translate
65 config = self._get_config()
63 config = self._get_config()
66 # common values for feeds
64 # common values for feeds
67 self.description = _('Changes on %s repository')
65 self.description = _('Changes on %s repository')
68 self.title = _('%s %s feed') % (self.db_repo_name, '%s')
66 self.title = _('%s %s feed') % (self.db_repo_name, '%s')
69 self.language = config["language"]
67 self.language = config["language"]
70 self.ttl = config["feed_ttl"]
68 self.ttl = config["feed_ttl"]
71 self.feed_include_diff = config['feed_include_diff']
69 self.feed_include_diff = config['feed_include_diff']
72 self.feed_diff_limit = config['feed_diff_limit']
70 self.feed_diff_limit = config['feed_diff_limit']
73 self.feed_items_per_page = config['feed_items_per_page']
71 self.feed_items_per_page = config['feed_items_per_page']
74
72
75 def _changes(self, commit):
73 def _changes(self, commit):
76 diff = commit.diff()
74 diff = commit.diff()
77 diff_processor = DiffProcessor(diff, diff_format='newdiff',
75 diff_processor = DiffProcessor(diff, diff_format='newdiff',
78 diff_limit=self.feed_diff_limit)
76 diff_limit=self.feed_diff_limit)
79 _parsed = diff_processor.prepare(inline_diff=False)
77 _parsed = diff_processor.prepare(inline_diff=False)
80 limited_diff = isinstance(_parsed, LimitedDiffContainer)
78 limited_diff = isinstance(_parsed, LimitedDiffContainer)
81
79
82 return diff_processor, _parsed, limited_diff
80 return diff_processor, _parsed, limited_diff
83
81
84 def _get_title(self, commit):
82 def _get_title(self, commit):
85 return h.chop_at_smart(commit.message, '\n', suffix_if_chopped='...')
83 return h.chop_at_smart(commit.message, '\n', suffix_if_chopped='...')
86
84
87 def _get_description(self, commit):
85 def _get_description(self, commit):
88 _renderer = self.request.get_partial_renderer(
86 _renderer = self.request.get_partial_renderer(
89 'rhodecode:templates/feed/atom_feed_entry.mako')
87 'rhodecode:templates/feed/atom_feed_entry.mako')
90 diff_processor, parsed_diff, limited_diff = self._changes(commit)
88 diff_processor, parsed_diff, limited_diff = self._changes(commit)
91 filtered_parsed_diff, has_hidden_changes = self.path_filter.filter_patchset(parsed_diff)
89 filtered_parsed_diff, has_hidden_changes = self.path_filter.filter_patchset(parsed_diff)
92 return _renderer(
90 return _renderer(
93 'body',
91 'body',
94 commit=commit,
92 commit=commit,
95 parsed_diff=filtered_parsed_diff,
93 parsed_diff=filtered_parsed_diff,
96 limited_diff=limited_diff,
94 limited_diff=limited_diff,
97 feed_include_diff=self.feed_include_diff,
95 feed_include_diff=self.feed_include_diff,
98 diff_processor=diff_processor,
96 diff_processor=diff_processor,
99 has_hidden_changes=has_hidden_changes
97 has_hidden_changes=has_hidden_changes
100 )
98 )
101
99
102 def _set_timezone(self, date, tzinfo=datetime.timezone.utc):
100 def _set_timezone(self, date, tzinfo=datetime.UTC):
103 if not getattr(date, "tzinfo", None):
101 if not getattr(date, "tzinfo", None):
104 date.replace(tzinfo=tzinfo)
102 date.replace(tzinfo=tzinfo)
105 return date
103 return date
106
104
107 def _get_commits(self):
105 def _get_commits(self):
108 pre_load = ['author', 'branch', 'date', 'message', 'parents']
106 pre_load = ['author', 'branch', 'date', 'message', 'parents']
109 if self.rhodecode_vcs_repo.is_empty():
107 if self.rhodecode_vcs_repo.is_empty():
110 return []
108 return []
111
109
112 collection = self.rhodecode_vcs_repo.get_commits(
110 collection = self.rhodecode_vcs_repo.get_commits(
113 branch_name=None, show_hidden=False, pre_load=pre_load,
111 branch_name=None, show_hidden=False, pre_load=pre_load,
114 translate_tags=False)
112 translate_tags=False)
115
113
116 return list(collection[-self.feed_items_per_page:])
114 return list(collection[-self.feed_items_per_page:])
117
115
118 def uid(self, repo_id, commit_id):
116 def uid(self, repo_id, commit_id):
119 return '{}:{}'.format(
117 return '{}:{}'.format(
120 md5_safe(repo_id, return_type='str'),
118 md5_safe(repo_id, return_type='str'),
121 md5_safe(commit_id, return_type='str')
119 md5_safe(commit_id, return_type='str')
122 )
120 )
123
121
124 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
122 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
125 @HasRepoPermissionAnyDecorator(
123 @HasRepoPermissionAnyDecorator(
126 'repository.read', 'repository.write', 'repository.admin')
124 'repository.read', 'repository.write', 'repository.admin')
127 def atom(self):
125 def atom(self):
128 """
126 """
129 Produce an atom-1.0 feed via feedgenerator module
127 Produce an atom-1.0 feed via feedgenerator module
130 """
128 """
131 self.load_default_context()
129 self.load_default_context()
132 force_recache = self.get_recache_flag()
130 force_recache = self.get_recache_flag()
133
131
134 cache_namespace_uid = 'repo_feed.{}'.format(self.db_repo.repo_id)
132 cache_namespace_uid = f'repo_feed.{self.db_repo.repo_id}'
135 condition = not (self.path_filter.is_enabled or force_recache)
133 condition = not (self.path_filter.is_enabled or force_recache)
136 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
134 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
137
135
138 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
136 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
139 condition=condition)
137 condition=condition)
140 def generate_atom_feed(repo_id, _repo_name, _commit_id, _feed_type):
138 def generate_atom_feed(repo_id, _repo_name, _commit_id, _feed_type):
141 feed = Atom1Feed(
139 feed = Atom1Feed(
142 title=self.title % 'atom',
140 title=self.title % 'atom',
143 link=h.route_url('repo_summary', repo_name=_repo_name),
141 link=h.route_url('repo_summary', repo_name=_repo_name),
144 description=self.description % _repo_name,
142 description=self.description % _repo_name,
145 language=self.language,
143 language=self.language,
146 ttl=self.ttl
144 ttl=self.ttl
147 )
145 )
148
146
149 for commit in reversed(self._get_commits()):
147 for commit in reversed(self._get_commits()):
150 date = self._set_timezone(commit.date)
148 date = self._set_timezone(commit.date)
151 feed.add_item(
149 feed.add_item(
152 unique_id=self.uid(str(repo_id), commit.raw_id),
150 unique_id=self.uid(str(repo_id), commit.raw_id),
153 title=self._get_title(commit),
151 title=self._get_title(commit),
154 author_name=commit.author,
152 author_name=commit.author,
155 description=self._get_description(commit),
153 description=self._get_description(commit),
156 link=h.route_url(
154 link=h.route_url(
157 'repo_commit', repo_name=_repo_name,
155 'repo_commit', repo_name=_repo_name,
158 commit_id=commit.raw_id),
156 commit_id=commit.raw_id),
159 pubdate=date,)
157 pubdate=date,)
160
158
161 return feed.content_type, feed.writeString('utf-8')
159 return feed.content_type, feed.writeString('utf-8')
162
160
163 commit_id = self.db_repo.changeset_cache.get('raw_id')
161 commit_id = self.db_repo.changeset_cache.get('raw_id')
164 content_type, feed = generate_atom_feed(
162 content_type, feed = generate_atom_feed(
165 self.db_repo.repo_id, self.db_repo.repo_name, commit_id, 'atom')
163 self.db_repo.repo_id, self.db_repo.repo_name, commit_id, 'atom')
166
164
167 response = Response(feed)
165 response = Response(feed)
168 response.content_type = content_type
166 response.content_type = content_type
169 return response
167 return response
170
168
171 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
169 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
172 @HasRepoPermissionAnyDecorator(
170 @HasRepoPermissionAnyDecorator(
173 'repository.read', 'repository.write', 'repository.admin')
171 'repository.read', 'repository.write', 'repository.admin')
174 def rss(self):
172 def rss(self):
175 """
173 """
176 Produce an rss2 feed via feedgenerator module
174 Produce an rss2 feed via feedgenerator module
177 """
175 """
178 self.load_default_context()
176 self.load_default_context()
179 force_recache = self.get_recache_flag()
177 force_recache = self.get_recache_flag()
180
178
181 cache_namespace_uid = 'repo_feed.{}'.format(self.db_repo.repo_id)
179 cache_namespace_uid = f'repo_feed.{self.db_repo.repo_id}'
182 condition = not (self.path_filter.is_enabled or force_recache)
180 condition = not (self.path_filter.is_enabled or force_recache)
183 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
181 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
184
182
185 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
183 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
186 condition=condition)
184 condition=condition)
187 def generate_rss_feed(repo_id, _repo_name, _commit_id, _feed_type):
185 def generate_rss_feed(repo_id, _repo_name, _commit_id, _feed_type):
188 feed = Rss201rev2Feed(
186 feed = Rss201rev2Feed(
189 title=self.title % 'rss',
187 title=self.title % 'rss',
190 link=h.route_url('repo_summary', repo_name=_repo_name),
188 link=h.route_url('repo_summary', repo_name=_repo_name),
191 description=self.description % _repo_name,
189 description=self.description % _repo_name,
192 language=self.language,
190 language=self.language,
193 ttl=self.ttl
191 ttl=self.ttl
194 )
192 )
195
193
196 for commit in reversed(self._get_commits()):
194 for commit in reversed(self._get_commits()):
197 date = self._set_timezone(commit.date)
195 date = self._set_timezone(commit.date)
198 feed.add_item(
196 feed.add_item(
199 unique_id=self.uid(str(repo_id), commit.raw_id),
197 unique_id=self.uid(str(repo_id), commit.raw_id),
200 title=self._get_title(commit),
198 title=self._get_title(commit),
201 author_name=commit.author,
199 author_name=commit.author,
202 description=self._get_description(commit),
200 description=self._get_description(commit),
203 link=h.route_url(
201 link=h.route_url(
204 'repo_commit', repo_name=_repo_name,
202 'repo_commit', repo_name=_repo_name,
205 commit_id=commit.raw_id),
203 commit_id=commit.raw_id),
206 pubdate=date,)
204 pubdate=date,)
207 return feed.content_type, feed.writeString('utf-8')
205 return feed.content_type, feed.writeString('utf-8')
208
206
209 commit_id = self.db_repo.changeset_cache.get('raw_id')
207 commit_id = self.db_repo.changeset_cache.get('raw_id')
210 content_type, feed = generate_rss_feed(
208 content_type, feed = generate_rss_feed(
211 self.db_repo.repo_id, self.db_repo.repo_name, commit_id, 'rss')
209 self.db_repo.repo_id, self.db_repo.repo_name, commit_id, 'rss')
212
210
213 response = Response(feed)
211 response = Response(feed)
214 response.content_type = content_type
212 response.content_type = content_type
215 return response
213 return response
@@ -1,1587 +1,1585 b''
1
2
3 # Copyright (C) 2011-2023 RhodeCode GmbH
1 # Copyright (C) 2011-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 import itertools
19 import itertools
22 import logging
20 import logging
23 import os
21 import os
24 import collections
22 import collections
25 import urllib.request
23 import urllib.request
26 import urllib.parse
24 import urllib.parse
27 import urllib.error
25 import urllib.error
28 import pathlib
26 import pathlib
29
27
30 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
28 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
31
29
32 from pyramid.renderers import render
30 from pyramid.renderers import render
33 from pyramid.response import Response
31 from pyramid.response import Response
34
32
35 import rhodecode
33 import rhodecode
36 from rhodecode.apps._base import RepoAppView
34 from rhodecode.apps._base import RepoAppView
37
35
38
36
39 from rhodecode.lib import diffs, helpers as h, rc_cache
37 from rhodecode.lib import diffs, helpers as h, rc_cache
40 from rhodecode.lib import audit_logger
38 from rhodecode.lib import audit_logger
41 from rhodecode.lib.hash_utils import sha1_safe
39 from rhodecode.lib.hash_utils import sha1_safe
42 from rhodecode.lib.rc_cache.archive_cache import get_archival_cache_store, get_archival_config, ReentrantLock
40 from rhodecode.lib.rc_cache.archive_cache import get_archival_cache_store, get_archival_config, ReentrantLock
43 from rhodecode.lib.str_utils import safe_bytes
41 from rhodecode.lib.str_utils import safe_bytes
44 from rhodecode.lib.view_utils import parse_path_ref
42 from rhodecode.lib.view_utils import parse_path_ref
45 from rhodecode.lib.exceptions import NonRelativePathError
43 from rhodecode.lib.exceptions import NonRelativePathError
46 from rhodecode.lib.codeblocks import (
44 from rhodecode.lib.codeblocks import (
47 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
45 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
48 from rhodecode.lib.utils2 import convert_line_endings, detect_mode
46 from rhodecode.lib.utils2 import convert_line_endings, detect_mode
49 from rhodecode.lib.type_utils import str2bool
47 from rhodecode.lib.type_utils import str2bool
50 from rhodecode.lib.str_utils import safe_str, safe_int
48 from rhodecode.lib.str_utils import safe_str, safe_int
51 from rhodecode.lib.auth import (
49 from rhodecode.lib.auth import (
52 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
50 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
53 from rhodecode.lib.vcs import path as vcspath
51 from rhodecode.lib.vcs import path as vcspath
54 from rhodecode.lib.vcs.backends.base import EmptyCommit
52 from rhodecode.lib.vcs.backends.base import EmptyCommit
55 from rhodecode.lib.vcs.conf import settings
53 from rhodecode.lib.vcs.conf import settings
56 from rhodecode.lib.vcs.nodes import FileNode
54 from rhodecode.lib.vcs.nodes import FileNode
57 from rhodecode.lib.vcs.exceptions import (
55 from rhodecode.lib.vcs.exceptions import (
58 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
56 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
59 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
57 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
60 NodeDoesNotExistError, CommitError, NodeError)
58 NodeDoesNotExistError, CommitError, NodeError)
61
59
62 from rhodecode.model.scm import ScmModel
60 from rhodecode.model.scm import ScmModel
63 from rhodecode.model.db import Repository
61 from rhodecode.model.db import Repository
64
62
65 log = logging.getLogger(__name__)
63 log = logging.getLogger(__name__)
66
64
67
65
68 def get_archive_name(db_repo_name, commit_sha, ext, subrepos=False, path_sha='', with_hash=True):
66 def get_archive_name(db_repo_name, commit_sha, ext, subrepos=False, path_sha='', with_hash=True):
69 # original backward compat name of archive
67 # original backward compat name of archive
70 clean_name = safe_str(db_repo_name.replace('/', '_'))
68 clean_name = safe_str(db_repo_name.replace('/', '_'))
71
69
72 # e.g vcsserver-sub-1-abcfdef-archive-all.zip
70 # e.g vcsserver-sub-1-abcfdef-archive-all.zip
73 # vcsserver-sub-0-abcfdef-COMMIT_SHA-PATH_SHA.zip
71 # vcsserver-sub-0-abcfdef-COMMIT_SHA-PATH_SHA.zip
74
72
75 sub_repo = 'sub-1' if subrepos else 'sub-0'
73 sub_repo = 'sub-1' if subrepos else 'sub-0'
76 commit = commit_sha if with_hash else 'archive'
74 commit = commit_sha if with_hash else 'archive'
77 path_marker = (path_sha if with_hash else '') or 'all'
75 path_marker = (path_sha if with_hash else '') or 'all'
78 archive_name = f'{clean_name}-{sub_repo}-{commit}-{path_marker}{ext}'
76 archive_name = f'{clean_name}-{sub_repo}-{commit}-{path_marker}{ext}'
79
77
80 return archive_name
78 return archive_name
81
79
82
80
83 def get_path_sha(at_path):
81 def get_path_sha(at_path):
84 return safe_str(sha1_safe(at_path)[:8])
82 return safe_str(sha1_safe(at_path)[:8])
85
83
86
84
87 def _get_archive_spec(fname):
85 def _get_archive_spec(fname):
88 log.debug('Detecting archive spec for: `%s`', fname)
86 log.debug('Detecting archive spec for: `%s`', fname)
89
87
90 fileformat = None
88 fileformat = None
91 ext = None
89 ext = None
92 content_type = None
90 content_type = None
93 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
91 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
94
92
95 if fname.endswith(extension):
93 if fname.endswith(extension):
96 fileformat = a_type
94 fileformat = a_type
97 log.debug('archive is of type: %s', fileformat)
95 log.debug('archive is of type: %s', fileformat)
98 ext = extension
96 ext = extension
99 break
97 break
100
98
101 if not fileformat:
99 if not fileformat:
102 raise ValueError()
100 raise ValueError()
103
101
104 # left over part of whole fname is the commit
102 # left over part of whole fname is the commit
105 commit_id = fname[:-len(ext)]
103 commit_id = fname[:-len(ext)]
106
104
107 return commit_id, ext, fileformat, content_type
105 return commit_id, ext, fileformat, content_type
108
106
109
107
110 class RepoFilesView(RepoAppView):
108 class RepoFilesView(RepoAppView):
111
109
112 @staticmethod
110 @staticmethod
113 def adjust_file_path_for_svn(f_path, repo):
111 def adjust_file_path_for_svn(f_path, repo):
114 """
112 """
115 Computes the relative path of `f_path`.
113 Computes the relative path of `f_path`.
116
114
117 This is mainly based on prefix matching of the recognized tags and
115 This is mainly based on prefix matching of the recognized tags and
118 branches in the underlying repository.
116 branches in the underlying repository.
119 """
117 """
120 tags_and_branches = itertools.chain(
118 tags_and_branches = itertools.chain(
121 repo.branches.keys(),
119 repo.branches.keys(),
122 repo.tags.keys())
120 repo.tags.keys())
123 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
121 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
124
122
125 for name in tags_and_branches:
123 for name in tags_and_branches:
126 if f_path.startswith(f'{name}/'):
124 if f_path.startswith(f'{name}/'):
127 f_path = vcspath.relpath(f_path, name)
125 f_path = vcspath.relpath(f_path, name)
128 break
126 break
129 return f_path
127 return f_path
130
128
131 def load_default_context(self):
129 def load_default_context(self):
132 c = self._get_local_tmpl_context(include_app_defaults=True)
130 c = self._get_local_tmpl_context(include_app_defaults=True)
133 c.rhodecode_repo = self.rhodecode_vcs_repo
131 c.rhodecode_repo = self.rhodecode_vcs_repo
134 c.enable_downloads = self.db_repo.enable_downloads
132 c.enable_downloads = self.db_repo.enable_downloads
135 return c
133 return c
136
134
137 def _ensure_not_locked(self, commit_id='tip'):
135 def _ensure_not_locked(self, commit_id='tip'):
138 _ = self.request.translate
136 _ = self.request.translate
139
137
140 repo = self.db_repo
138 repo = self.db_repo
141 if repo.enable_locking and repo.locked[0]:
139 if repo.enable_locking and repo.locked[0]:
142 h.flash(_('This repository has been locked by %s on %s')
140 h.flash(_('This repository has been locked by %s on %s')
143 % (h.person_by_id(repo.locked[0]),
141 % (h.person_by_id(repo.locked[0]),
144 h.format_date(h.time_to_datetime(repo.locked[1]))),
142 h.format_date(h.time_to_datetime(repo.locked[1]))),
145 'warning')
143 'warning')
146 files_url = h.route_path(
144 files_url = h.route_path(
147 'repo_files:default_path',
145 'repo_files:default_path',
148 repo_name=self.db_repo_name, commit_id=commit_id)
146 repo_name=self.db_repo_name, commit_id=commit_id)
149 raise HTTPFound(files_url)
147 raise HTTPFound(files_url)
150
148
151 def forbid_non_head(self, is_head, f_path, commit_id='tip', json_mode=False):
149 def forbid_non_head(self, is_head, f_path, commit_id='tip', json_mode=False):
152 _ = self.request.translate
150 _ = self.request.translate
153
151
154 if not is_head:
152 if not is_head:
155 message = _('Cannot modify file. '
153 message = _('Cannot modify file. '
156 'Given commit `{}` is not head of a branch.').format(commit_id)
154 'Given commit `{}` is not head of a branch.').format(commit_id)
157 h.flash(message, category='warning')
155 h.flash(message, category='warning')
158
156
159 if json_mode:
157 if json_mode:
160 return message
158 return message
161
159
162 files_url = h.route_path(
160 files_url = h.route_path(
163 'repo_files', repo_name=self.db_repo_name, commit_id=commit_id,
161 'repo_files', repo_name=self.db_repo_name, commit_id=commit_id,
164 f_path=f_path)
162 f_path=f_path)
165 raise HTTPFound(files_url)
163 raise HTTPFound(files_url)
166
164
167 def check_branch_permission(self, branch_name, commit_id='tip', json_mode=False):
165 def check_branch_permission(self, branch_name, commit_id='tip', json_mode=False):
168 _ = self.request.translate
166 _ = self.request.translate
169
167
170 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
168 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
171 self.db_repo_name, branch_name)
169 self.db_repo_name, branch_name)
172 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
170 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
173 message = _('Branch `{}` changes forbidden by rule {}.').format(
171 message = _('Branch `{}` changes forbidden by rule {}.').format(
174 h.escape(branch_name), h.escape(rule))
172 h.escape(branch_name), h.escape(rule))
175 h.flash(message, 'warning')
173 h.flash(message, 'warning')
176
174
177 if json_mode:
175 if json_mode:
178 return message
176 return message
179
177
180 files_url = h.route_path(
178 files_url = h.route_path(
181 'repo_files:default_path', repo_name=self.db_repo_name, commit_id=commit_id)
179 'repo_files:default_path', repo_name=self.db_repo_name, commit_id=commit_id)
182
180
183 raise HTTPFound(files_url)
181 raise HTTPFound(files_url)
184
182
185 def _get_commit_and_path(self):
183 def _get_commit_and_path(self):
186 default_commit_id = self.db_repo.landing_ref_name
184 default_commit_id = self.db_repo.landing_ref_name
187 default_f_path = '/'
185 default_f_path = '/'
188
186
189 commit_id = self.request.matchdict.get(
187 commit_id = self.request.matchdict.get(
190 'commit_id', default_commit_id)
188 'commit_id', default_commit_id)
191 f_path = self._get_f_path(self.request.matchdict, default_f_path)
189 f_path = self._get_f_path(self.request.matchdict, default_f_path)
192 return commit_id, f_path
190 return commit_id, f_path
193
191
194 def _get_default_encoding(self, c):
192 def _get_default_encoding(self, c):
195 enc_list = getattr(c, 'default_encodings', [])
193 enc_list = getattr(c, 'default_encodings', [])
196 return enc_list[0] if enc_list else 'UTF-8'
194 return enc_list[0] if enc_list else 'UTF-8'
197
195
198 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
196 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
199 """
197 """
200 This is a safe way to get commit. If an error occurs it redirects to
198 This is a safe way to get commit. If an error occurs it redirects to
201 tip with proper message
199 tip with proper message
202
200
203 :param commit_id: id of commit to fetch
201 :param commit_id: id of commit to fetch
204 :param redirect_after: toggle redirection
202 :param redirect_after: toggle redirection
205 """
203 """
206 _ = self.request.translate
204 _ = self.request.translate
207
205
208 try:
206 try:
209 return self.rhodecode_vcs_repo.get_commit(commit_id)
207 return self.rhodecode_vcs_repo.get_commit(commit_id)
210 except EmptyRepositoryError:
208 except EmptyRepositoryError:
211 if not redirect_after:
209 if not redirect_after:
212 return None
210 return None
213
211
214 add_new = upload_new = ""
212 add_new = upload_new = ""
215 if h.HasRepoPermissionAny(
213 if h.HasRepoPermissionAny(
216 'repository.write', 'repository.admin')(self.db_repo_name):
214 'repository.write', 'repository.admin')(self.db_repo_name):
217 _url = h.route_path(
215 _url = h.route_path(
218 'repo_files_add_file',
216 'repo_files_add_file',
219 repo_name=self.db_repo_name, commit_id=0, f_path='')
217 repo_name=self.db_repo_name, commit_id=0, f_path='')
220 add_new = h.link_to(
218 add_new = h.link_to(
221 _('add a new file'), _url, class_="alert-link")
219 _('add a new file'), _url, class_="alert-link")
222
220
223 _url_upld = h.route_path(
221 _url_upld = h.route_path(
224 'repo_files_upload_file',
222 'repo_files_upload_file',
225 repo_name=self.db_repo_name, commit_id=0, f_path='')
223 repo_name=self.db_repo_name, commit_id=0, f_path='')
226 upload_new = h.link_to(
224 upload_new = h.link_to(
227 _('upload a new file'), _url_upld, class_="alert-link")
225 _('upload a new file'), _url_upld, class_="alert-link")
228
226
229 h.flash(h.literal(
227 h.flash(h.literal(
230 _('There are no files yet. Click here to %s or %s.') % (add_new, upload_new)), category='warning')
228 _('There are no files yet. Click here to %s or %s.') % (add_new, upload_new)), category='warning')
231 raise HTTPFound(
229 raise HTTPFound(
232 h.route_path('repo_summary', repo_name=self.db_repo_name))
230 h.route_path('repo_summary', repo_name=self.db_repo_name))
233
231
234 except (CommitDoesNotExistError, LookupError) as e:
232 except (CommitDoesNotExistError, LookupError) as e:
235 msg = _('No such commit exists for this repository. Commit: {}').format(commit_id)
233 msg = _('No such commit exists for this repository. Commit: {}').format(commit_id)
236 h.flash(msg, category='error')
234 h.flash(msg, category='error')
237 raise HTTPNotFound()
235 raise HTTPNotFound()
238 except RepositoryError as e:
236 except RepositoryError as e:
239 h.flash(h.escape(safe_str(e)), category='error')
237 h.flash(h.escape(safe_str(e)), category='error')
240 raise HTTPNotFound()
238 raise HTTPNotFound()
241
239
242 def _get_filenode_or_redirect(self, commit_obj, path, pre_load=None):
240 def _get_filenode_or_redirect(self, commit_obj, path, pre_load=None):
243 """
241 """
244 Returns file_node, if error occurs or given path is directory,
242 Returns file_node, if error occurs or given path is directory,
245 it'll redirect to top level path
243 it'll redirect to top level path
246 """
244 """
247 _ = self.request.translate
245 _ = self.request.translate
248
246
249 try:
247 try:
250 file_node = commit_obj.get_node(path, pre_load=pre_load)
248 file_node = commit_obj.get_node(path, pre_load=pre_load)
251 if file_node.is_dir():
249 if file_node.is_dir():
252 raise RepositoryError('The given path is a directory')
250 raise RepositoryError('The given path is a directory')
253 except CommitDoesNotExistError:
251 except CommitDoesNotExistError:
254 log.exception('No such commit exists for this repository')
252 log.exception('No such commit exists for this repository')
255 h.flash(_('No such commit exists for this repository'), category='error')
253 h.flash(_('No such commit exists for this repository'), category='error')
256 raise HTTPNotFound()
254 raise HTTPNotFound()
257 except RepositoryError as e:
255 except RepositoryError as e:
258 log.warning('Repository error while fetching filenode `%s`. Err:%s', path, e)
256 log.warning('Repository error while fetching filenode `%s`. Err:%s', path, e)
259 h.flash(h.escape(safe_str(e)), category='error')
257 h.flash(h.escape(safe_str(e)), category='error')
260 raise HTTPNotFound()
258 raise HTTPNotFound()
261
259
262 return file_node
260 return file_node
263
261
264 def _is_valid_head(self, commit_id, repo, landing_ref):
262 def _is_valid_head(self, commit_id, repo, landing_ref):
265 branch_name = sha_commit_id = ''
263 branch_name = sha_commit_id = ''
266 is_head = False
264 is_head = False
267 log.debug('Checking if commit_id `%s` is a head for %s.', commit_id, repo)
265 log.debug('Checking if commit_id `%s` is a head for %s.', commit_id, repo)
268
266
269 for _branch_name, branch_commit_id in repo.branches.items():
267 for _branch_name, branch_commit_id in repo.branches.items():
270 # simple case we pass in branch name, it's a HEAD
268 # simple case we pass in branch name, it's a HEAD
271 if commit_id == _branch_name:
269 if commit_id == _branch_name:
272 is_head = True
270 is_head = True
273 branch_name = _branch_name
271 branch_name = _branch_name
274 sha_commit_id = branch_commit_id
272 sha_commit_id = branch_commit_id
275 break
273 break
276 # case when we pass in full sha commit_id, which is a head
274 # case when we pass in full sha commit_id, which is a head
277 elif commit_id == branch_commit_id:
275 elif commit_id == branch_commit_id:
278 is_head = True
276 is_head = True
279 branch_name = _branch_name
277 branch_name = _branch_name
280 sha_commit_id = branch_commit_id
278 sha_commit_id = branch_commit_id
281 break
279 break
282
280
283 if h.is_svn(repo) and not repo.is_empty():
281 if h.is_svn(repo) and not repo.is_empty():
284 # Note: Subversion only has one head.
282 # Note: Subversion only has one head.
285 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
283 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
286 is_head = True
284 is_head = True
287 return branch_name, sha_commit_id, is_head
285 return branch_name, sha_commit_id, is_head
288
286
289 # checked branches, means we only need to try to get the branch/commit_sha
287 # checked branches, means we only need to try to get the branch/commit_sha
290 if repo.is_empty():
288 if repo.is_empty():
291 is_head = True
289 is_head = True
292 branch_name = landing_ref
290 branch_name = landing_ref
293 sha_commit_id = EmptyCommit().raw_id
291 sha_commit_id = EmptyCommit().raw_id
294 else:
292 else:
295 commit = repo.get_commit(commit_id=commit_id)
293 commit = repo.get_commit(commit_id=commit_id)
296 if commit:
294 if commit:
297 branch_name = commit.branch
295 branch_name = commit.branch
298 sha_commit_id = commit.raw_id
296 sha_commit_id = commit.raw_id
299
297
300 return branch_name, sha_commit_id, is_head
298 return branch_name, sha_commit_id, is_head
301
299
302 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False, at_rev=None):
300 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False, at_rev=None):
303
301
304 repo_id = self.db_repo.repo_id
302 repo_id = self.db_repo.repo_id
305 force_recache = self.get_recache_flag()
303 force_recache = self.get_recache_flag()
306
304
307 cache_seconds = safe_int(
305 cache_seconds = safe_int(
308 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
306 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
309 cache_on = not force_recache and cache_seconds > 0
307 cache_on = not force_recache and cache_seconds > 0
310 log.debug(
308 log.debug(
311 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
309 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
312 'with caching: %s[TTL: %ss]' % (
310 'with caching: %s[TTL: %ss]' % (
313 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
311 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
314
312
315 cache_namespace_uid = 'repo.{}'.format(repo_id)
313 cache_namespace_uid = f'repo.{repo_id}'
316 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
314 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
317
315
318 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
316 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
319 def compute_file_tree(ver, _name_hash, _repo_id, _commit_id, _f_path, _full_load, _at_rev):
317 def compute_file_tree(ver, _name_hash, _repo_id, _commit_id, _f_path, _full_load, _at_rev):
320 log.debug('Generating cached file tree at ver:%s for repo_id: %s, %s, %s',
318 log.debug('Generating cached file tree at ver:%s for repo_id: %s, %s, %s',
321 ver, _repo_id, _commit_id, _f_path)
319 ver, _repo_id, _commit_id, _f_path)
322
320
323 c.full_load = _full_load
321 c.full_load = _full_load
324 return render(
322 return render(
325 'rhodecode:templates/files/files_browser_tree.mako',
323 'rhodecode:templates/files/files_browser_tree.mako',
326 self._get_template_context(c), self.request, _at_rev)
324 self._get_template_context(c), self.request, _at_rev)
327
325
328 return compute_file_tree(
326 return compute_file_tree(
329 rc_cache.FILE_TREE_CACHE_VER, self.db_repo.repo_name_hash,
327 rc_cache.FILE_TREE_CACHE_VER, self.db_repo.repo_name_hash,
330 self.db_repo.repo_id, commit_id, f_path, full_load, at_rev)
328 self.db_repo.repo_id, commit_id, f_path, full_load, at_rev)
331
329
332 def create_pure_path(self, *parts):
330 def create_pure_path(self, *parts):
333 # Split paths and sanitize them, removing any ../ etc
331 # Split paths and sanitize them, removing any ../ etc
334 sanitized_path = [
332 sanitized_path = [
335 x for x in pathlib.PurePath(*parts).parts
333 x for x in pathlib.PurePath(*parts).parts
336 if x not in ['.', '..']]
334 if x not in ['.', '..']]
337
335
338 pure_path = pathlib.PurePath(*sanitized_path)
336 pure_path = pathlib.PurePath(*sanitized_path)
339 return pure_path
337 return pure_path
340
338
341 def _is_lf_enabled(self, target_repo):
339 def _is_lf_enabled(self, target_repo):
342 lf_enabled = False
340 lf_enabled = False
343
341
344 lf_key_for_vcs_map = {
342 lf_key_for_vcs_map = {
345 'hg': 'extensions_largefiles',
343 'hg': 'extensions_largefiles',
346 'git': 'vcs_git_lfs_enabled'
344 'git': 'vcs_git_lfs_enabled'
347 }
345 }
348
346
349 lf_key_for_vcs = lf_key_for_vcs_map.get(target_repo.repo_type)
347 lf_key_for_vcs = lf_key_for_vcs_map.get(target_repo.repo_type)
350
348
351 if lf_key_for_vcs:
349 if lf_key_for_vcs:
352 lf_enabled = self._get_repo_setting(target_repo, lf_key_for_vcs)
350 lf_enabled = self._get_repo_setting(target_repo, lf_key_for_vcs)
353
351
354 return lf_enabled
352 return lf_enabled
355
353
356 @LoginRequired()
354 @LoginRequired()
357 @HasRepoPermissionAnyDecorator(
355 @HasRepoPermissionAnyDecorator(
358 'repository.read', 'repository.write', 'repository.admin')
356 'repository.read', 'repository.write', 'repository.admin')
359 def repo_archivefile(self):
357 def repo_archivefile(self):
360 # archive cache config
358 # archive cache config
361 from rhodecode import CONFIG
359 from rhodecode import CONFIG
362 _ = self.request.translate
360 _ = self.request.translate
363 self.load_default_context()
361 self.load_default_context()
364 default_at_path = '/'
362 default_at_path = '/'
365 fname = self.request.matchdict['fname']
363 fname = self.request.matchdict['fname']
366 subrepos = self.request.GET.get('subrepos') == 'true'
364 subrepos = self.request.GET.get('subrepos') == 'true'
367 with_hash = str2bool(self.request.GET.get('with_hash', '1'))
365 with_hash = str2bool(self.request.GET.get('with_hash', '1'))
368 at_path = self.request.GET.get('at_path') or default_at_path
366 at_path = self.request.GET.get('at_path') or default_at_path
369
367
370 if not self.db_repo.enable_downloads:
368 if not self.db_repo.enable_downloads:
371 return Response(_('Downloads disabled'))
369 return Response(_('Downloads disabled'))
372
370
373 try:
371 try:
374 commit_id, ext, fileformat, content_type = \
372 commit_id, ext, fileformat, content_type = \
375 _get_archive_spec(fname)
373 _get_archive_spec(fname)
376 except ValueError:
374 except ValueError:
377 return Response(_('Unknown archive type for: `{}`').format(
375 return Response(_('Unknown archive type for: `{}`').format(
378 h.escape(fname)))
376 h.escape(fname)))
379
377
380 try:
378 try:
381 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
379 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
382 except CommitDoesNotExistError:
380 except CommitDoesNotExistError:
383 return Response(_('Unknown commit_id {}').format(
381 return Response(_('Unknown commit_id {}').format(
384 h.escape(commit_id)))
382 h.escape(commit_id)))
385 except EmptyRepositoryError:
383 except EmptyRepositoryError:
386 return Response(_('Empty repository'))
384 return Response(_('Empty repository'))
387
385
388 # we used a ref, or a shorter version, lets redirect client ot use explicit hash
386 # we used a ref, or a shorter version, lets redirect client ot use explicit hash
389 if commit_id != commit.raw_id:
387 if commit_id != commit.raw_id:
390 fname='{}{}'.format(commit.raw_id, ext)
388 fname=f'{commit.raw_id}{ext}'
391 raise HTTPFound(self.request.current_route_path(fname=fname))
389 raise HTTPFound(self.request.current_route_path(fname=fname))
392
390
393 try:
391 try:
394 at_path = commit.get_node(at_path).path or default_at_path
392 at_path = commit.get_node(at_path).path or default_at_path
395 except Exception:
393 except Exception:
396 return Response(_('No node at path {} for this repository').format(h.escape(at_path)))
394 return Response(_('No node at path {} for this repository').format(h.escape(at_path)))
397
395
398 path_sha = get_path_sha(at_path)
396 path_sha = get_path_sha(at_path)
399
397
400 # used for cache etc, consistent unique archive name
398 # used for cache etc, consistent unique archive name
401 archive_name_key = get_archive_name(
399 archive_name_key = get_archive_name(
402 self.db_repo_name, commit_sha=commit.short_id, ext=ext, subrepos=subrepos,
400 self.db_repo_name, commit_sha=commit.short_id, ext=ext, subrepos=subrepos,
403 path_sha=path_sha, with_hash=True)
401 path_sha=path_sha, with_hash=True)
404
402
405 if not with_hash:
403 if not with_hash:
406 path_sha = ''
404 path_sha = ''
407
405
408 # what end client gets served
406 # what end client gets served
409 response_archive_name = get_archive_name(
407 response_archive_name = get_archive_name(
410 self.db_repo_name, commit_sha=commit.short_id, ext=ext, subrepos=subrepos,
408 self.db_repo_name, commit_sha=commit.short_id, ext=ext, subrepos=subrepos,
411 path_sha=path_sha, with_hash=with_hash)
409 path_sha=path_sha, with_hash=with_hash)
412
410
413 # remove extension from our archive directory name
411 # remove extension from our archive directory name
414 archive_dir_name = response_archive_name[:-len(ext)]
412 archive_dir_name = response_archive_name[:-len(ext)]
415
413
416 archive_cache_disable = self.request.GET.get('no_cache')
414 archive_cache_disable = self.request.GET.get('no_cache')
417
415
418 d_cache = get_archival_cache_store(config=CONFIG)
416 d_cache = get_archival_cache_store(config=CONFIG)
419 # NOTE: we get the config to pass to a call to lazy-init the SAME type of cache on vcsserver
417 # NOTE: we get the config to pass to a call to lazy-init the SAME type of cache on vcsserver
420 d_cache_conf = get_archival_config(config=CONFIG)
418 d_cache_conf = get_archival_config(config=CONFIG)
421
419
422 reentrant_lock_key = archive_name_key + '.lock'
420 reentrant_lock_key = archive_name_key + '.lock'
423 with ReentrantLock(d_cache, reentrant_lock_key):
421 with ReentrantLock(d_cache, reentrant_lock_key):
424 # This is also a cache key
422 # This is also a cache key
425 use_cached_archive = False
423 use_cached_archive = False
426 if archive_name_key in d_cache and not archive_cache_disable:
424 if archive_name_key in d_cache and not archive_cache_disable:
427 reader, tag = d_cache.get(archive_name_key, read=True, tag=True, retry=True)
425 reader, tag = d_cache.get(archive_name_key, read=True, tag=True, retry=True)
428 use_cached_archive = True
426 use_cached_archive = True
429 log.debug('Found cached archive as key=%s tag=%s, serving archive from cache reader=%s',
427 log.debug('Found cached archive as key=%s tag=%s, serving archive from cache reader=%s',
430 archive_name_key, tag, reader.name)
428 archive_name_key, tag, reader.name)
431 else:
429 else:
432 reader = None
430 reader = None
433 log.debug('Archive with key=%s is not yet cached, creating one now...', archive_name_key)
431 log.debug('Archive with key=%s is not yet cached, creating one now...', archive_name_key)
434
432
435 # generate new archive, as previous was not found in the cache
433 # generate new archive, as previous was not found in the cache
436 if not reader:
434 if not reader:
437 # first remove expired items, before generating a new one :)
435 # first remove expired items, before generating a new one :)
438 # we di this manually because automatic eviction is disabled
436 # we di this manually because automatic eviction is disabled
439 d_cache.cull(retry=True)
437 d_cache.cull(retry=True)
440
438
441 try:
439 try:
442 commit.archive_repo(archive_name_key, archive_dir_name=archive_dir_name,
440 commit.archive_repo(archive_name_key, archive_dir_name=archive_dir_name,
443 kind=fileformat, subrepos=subrepos,
441 kind=fileformat, subrepos=subrepos,
444 archive_at_path=at_path, cache_config=d_cache_conf)
442 archive_at_path=at_path, cache_config=d_cache_conf)
445 except ImproperArchiveTypeError:
443 except ImproperArchiveTypeError:
446 return _('Unknown archive type')
444 return _('Unknown archive type')
447
445
448 reader, tag = d_cache.get(archive_name_key, read=True, tag=True, retry=True)
446 reader, tag = d_cache.get(archive_name_key, read=True, tag=True, retry=True)
449
447
450 if not reader:
448 if not reader:
451 raise ValueError('archive cache reader is empty, failed to fetch file from distributed archive cache')
449 raise ValueError('archive cache reader is empty, failed to fetch file from distributed archive cache')
452
450
453 def archive_iterator(_reader):
451 def archive_iterator(_reader):
454 while 1:
452 while 1:
455 data = _reader.read(1024)
453 data = _reader.read(1024)
456 if not data:
454 if not data:
457 break
455 break
458 yield data
456 yield data
459
457
460 response = Response(app_iter=archive_iterator(reader))
458 response = Response(app_iter=archive_iterator(reader))
461 response.content_disposition = f'attachment; filename={response_archive_name}'
459 response.content_disposition = f'attachment; filename={response_archive_name}'
462 response.content_type = str(content_type)
460 response.content_type = str(content_type)
463
461
464 try:
462 try:
465 return response
463 return response
466 finally:
464 finally:
467 # store download action
465 # store download action
468 audit_logger.store_web(
466 audit_logger.store_web(
469 'repo.archive.download', action_data={
467 'repo.archive.download', action_data={
470 'user_agent': self.request.user_agent,
468 'user_agent': self.request.user_agent,
471 'archive_name': archive_name_key,
469 'archive_name': archive_name_key,
472 'archive_spec': fname,
470 'archive_spec': fname,
473 'archive_cached': use_cached_archive},
471 'archive_cached': use_cached_archive},
474 user=self._rhodecode_user,
472 user=self._rhodecode_user,
475 repo=self.db_repo,
473 repo=self.db_repo,
476 commit=True
474 commit=True
477 )
475 )
478
476
479 def _get_file_node(self, commit_id, f_path):
477 def _get_file_node(self, commit_id, f_path):
480 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
478 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
481 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
479 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
482 try:
480 try:
483 node = commit.get_node(f_path)
481 node = commit.get_node(f_path)
484 if node.is_dir():
482 if node.is_dir():
485 raise NodeError(f'{node} path is a {type(node)} not a file')
483 raise NodeError(f'{node} path is a {type(node)} not a file')
486 except NodeDoesNotExistError:
484 except NodeDoesNotExistError:
487 commit = EmptyCommit(
485 commit = EmptyCommit(
488 commit_id=commit_id,
486 commit_id=commit_id,
489 idx=commit.idx,
487 idx=commit.idx,
490 repo=commit.repository,
488 repo=commit.repository,
491 alias=commit.repository.alias,
489 alias=commit.repository.alias,
492 message=commit.message,
490 message=commit.message,
493 author=commit.author,
491 author=commit.author,
494 date=commit.date)
492 date=commit.date)
495 node = FileNode(safe_bytes(f_path), b'', commit=commit)
493 node = FileNode(safe_bytes(f_path), b'', commit=commit)
496 else:
494 else:
497 commit = EmptyCommit(
495 commit = EmptyCommit(
498 repo=self.rhodecode_vcs_repo,
496 repo=self.rhodecode_vcs_repo,
499 alias=self.rhodecode_vcs_repo.alias)
497 alias=self.rhodecode_vcs_repo.alias)
500 node = FileNode(safe_bytes(f_path), b'', commit=commit)
498 node = FileNode(safe_bytes(f_path), b'', commit=commit)
501 return node
499 return node
502
500
503 @LoginRequired()
501 @LoginRequired()
504 @HasRepoPermissionAnyDecorator(
502 @HasRepoPermissionAnyDecorator(
505 'repository.read', 'repository.write', 'repository.admin')
503 'repository.read', 'repository.write', 'repository.admin')
506 def repo_files_diff(self):
504 def repo_files_diff(self):
507 c = self.load_default_context()
505 c = self.load_default_context()
508 f_path = self._get_f_path(self.request.matchdict)
506 f_path = self._get_f_path(self.request.matchdict)
509 diff1 = self.request.GET.get('diff1', '')
507 diff1 = self.request.GET.get('diff1', '')
510 diff2 = self.request.GET.get('diff2', '')
508 diff2 = self.request.GET.get('diff2', '')
511
509
512 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
510 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
513
511
514 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
512 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
515 line_context = self.request.GET.get('context', 3)
513 line_context = self.request.GET.get('context', 3)
516
514
517 if not any((diff1, diff2)):
515 if not any((diff1, diff2)):
518 h.flash(
516 h.flash(
519 'Need query parameter "diff1" or "diff2" to generate a diff.',
517 'Need query parameter "diff1" or "diff2" to generate a diff.',
520 category='error')
518 category='error')
521 raise HTTPBadRequest()
519 raise HTTPBadRequest()
522
520
523 c.action = self.request.GET.get('diff')
521 c.action = self.request.GET.get('diff')
524 if c.action not in ['download', 'raw']:
522 if c.action not in ['download', 'raw']:
525 compare_url = h.route_path(
523 compare_url = h.route_path(
526 'repo_compare',
524 'repo_compare',
527 repo_name=self.db_repo_name,
525 repo_name=self.db_repo_name,
528 source_ref_type='rev',
526 source_ref_type='rev',
529 source_ref=diff1,
527 source_ref=diff1,
530 target_repo=self.db_repo_name,
528 target_repo=self.db_repo_name,
531 target_ref_type='rev',
529 target_ref_type='rev',
532 target_ref=diff2,
530 target_ref=diff2,
533 _query=dict(f_path=f_path))
531 _query=dict(f_path=f_path))
534 # redirect to new view if we render diff
532 # redirect to new view if we render diff
535 raise HTTPFound(compare_url)
533 raise HTTPFound(compare_url)
536
534
537 try:
535 try:
538 node1 = self._get_file_node(diff1, path1)
536 node1 = self._get_file_node(diff1, path1)
539 node2 = self._get_file_node(diff2, f_path)
537 node2 = self._get_file_node(diff2, f_path)
540 except (RepositoryError, NodeError):
538 except (RepositoryError, NodeError):
541 log.exception("Exception while trying to get node from repository")
539 log.exception("Exception while trying to get node from repository")
542 raise HTTPFound(
540 raise HTTPFound(
543 h.route_path('repo_files', repo_name=self.db_repo_name,
541 h.route_path('repo_files', repo_name=self.db_repo_name,
544 commit_id='tip', f_path=f_path))
542 commit_id='tip', f_path=f_path))
545
543
546 if all(isinstance(node.commit, EmptyCommit)
544 if all(isinstance(node.commit, EmptyCommit)
547 for node in (node1, node2)):
545 for node in (node1, node2)):
548 raise HTTPNotFound()
546 raise HTTPNotFound()
549
547
550 c.commit_1 = node1.commit
548 c.commit_1 = node1.commit
551 c.commit_2 = node2.commit
549 c.commit_2 = node2.commit
552
550
553 if c.action == 'download':
551 if c.action == 'download':
554 _diff = diffs.get_gitdiff(node1, node2,
552 _diff = diffs.get_gitdiff(node1, node2,
555 ignore_whitespace=ignore_whitespace,
553 ignore_whitespace=ignore_whitespace,
556 context=line_context)
554 context=line_context)
557 # NOTE: this was using diff_format='gitdiff'
555 # NOTE: this was using diff_format='gitdiff'
558 diff = diffs.DiffProcessor(_diff, diff_format='newdiff')
556 diff = diffs.DiffProcessor(_diff, diff_format='newdiff')
559
557
560 response = Response(self.path_filter.get_raw_patch(diff))
558 response = Response(self.path_filter.get_raw_patch(diff))
561 response.content_type = 'text/plain'
559 response.content_type = 'text/plain'
562 response.content_disposition = (
560 response.content_disposition = (
563 f'attachment; filename={f_path}_{diff1}_vs_{diff2}.diff'
561 f'attachment; filename={f_path}_{diff1}_vs_{diff2}.diff'
564 )
562 )
565 charset = self._get_default_encoding(c)
563 charset = self._get_default_encoding(c)
566 if charset:
564 if charset:
567 response.charset = charset
565 response.charset = charset
568 return response
566 return response
569
567
570 elif c.action == 'raw':
568 elif c.action == 'raw':
571 _diff = diffs.get_gitdiff(node1, node2,
569 _diff = diffs.get_gitdiff(node1, node2,
572 ignore_whitespace=ignore_whitespace,
570 ignore_whitespace=ignore_whitespace,
573 context=line_context)
571 context=line_context)
574 # NOTE: this was using diff_format='gitdiff'
572 # NOTE: this was using diff_format='gitdiff'
575 diff = diffs.DiffProcessor(_diff, diff_format='newdiff')
573 diff = diffs.DiffProcessor(_diff, diff_format='newdiff')
576
574
577 response = Response(self.path_filter.get_raw_patch(diff))
575 response = Response(self.path_filter.get_raw_patch(diff))
578 response.content_type = 'text/plain'
576 response.content_type = 'text/plain'
579 charset = self._get_default_encoding(c)
577 charset = self._get_default_encoding(c)
580 if charset:
578 if charset:
581 response.charset = charset
579 response.charset = charset
582 return response
580 return response
583
581
584 # in case we ever end up here
582 # in case we ever end up here
585 raise HTTPNotFound()
583 raise HTTPNotFound()
586
584
587 @LoginRequired()
585 @LoginRequired()
588 @HasRepoPermissionAnyDecorator(
586 @HasRepoPermissionAnyDecorator(
589 'repository.read', 'repository.write', 'repository.admin')
587 'repository.read', 'repository.write', 'repository.admin')
590 def repo_files_diff_2way_redirect(self):
588 def repo_files_diff_2way_redirect(self):
591 """
589 """
592 Kept only to make OLD links work
590 Kept only to make OLD links work
593 """
591 """
594 f_path = self._get_f_path_unchecked(self.request.matchdict)
592 f_path = self._get_f_path_unchecked(self.request.matchdict)
595 diff1 = self.request.GET.get('diff1', '')
593 diff1 = self.request.GET.get('diff1', '')
596 diff2 = self.request.GET.get('diff2', '')
594 diff2 = self.request.GET.get('diff2', '')
597
595
598 if not any((diff1, diff2)):
596 if not any((diff1, diff2)):
599 h.flash(
597 h.flash(
600 'Need query parameter "diff1" or "diff2" to generate a diff.',
598 'Need query parameter "diff1" or "diff2" to generate a diff.',
601 category='error')
599 category='error')
602 raise HTTPBadRequest()
600 raise HTTPBadRequest()
603
601
604 compare_url = h.route_path(
602 compare_url = h.route_path(
605 'repo_compare',
603 'repo_compare',
606 repo_name=self.db_repo_name,
604 repo_name=self.db_repo_name,
607 source_ref_type='rev',
605 source_ref_type='rev',
608 source_ref=diff1,
606 source_ref=diff1,
609 target_ref_type='rev',
607 target_ref_type='rev',
610 target_ref=diff2,
608 target_ref=diff2,
611 _query=dict(f_path=f_path, diffmode='sideside',
609 _query=dict(f_path=f_path, diffmode='sideside',
612 target_repo=self.db_repo_name,))
610 target_repo=self.db_repo_name,))
613 raise HTTPFound(compare_url)
611 raise HTTPFound(compare_url)
614
612
615 @LoginRequired()
613 @LoginRequired()
616 def repo_files_default_commit_redirect(self):
614 def repo_files_default_commit_redirect(self):
617 """
615 """
618 Special page that redirects to the landing page of files based on the default
616 Special page that redirects to the landing page of files based on the default
619 commit for repository
617 commit for repository
620 """
618 """
621 c = self.load_default_context()
619 c = self.load_default_context()
622 ref_name = c.rhodecode_db_repo.landing_ref_name
620 ref_name = c.rhodecode_db_repo.landing_ref_name
623 landing_url = h.repo_files_by_ref_url(
621 landing_url = h.repo_files_by_ref_url(
624 c.rhodecode_db_repo.repo_name,
622 c.rhodecode_db_repo.repo_name,
625 c.rhodecode_db_repo.repo_type,
623 c.rhodecode_db_repo.repo_type,
626 f_path='',
624 f_path='',
627 ref_name=ref_name,
625 ref_name=ref_name,
628 commit_id='tip',
626 commit_id='tip',
629 query=dict(at=ref_name)
627 query=dict(at=ref_name)
630 )
628 )
631
629
632 raise HTTPFound(landing_url)
630 raise HTTPFound(landing_url)
633
631
634 @LoginRequired()
632 @LoginRequired()
635 @HasRepoPermissionAnyDecorator(
633 @HasRepoPermissionAnyDecorator(
636 'repository.read', 'repository.write', 'repository.admin')
634 'repository.read', 'repository.write', 'repository.admin')
637 def repo_files(self):
635 def repo_files(self):
638 c = self.load_default_context()
636 c = self.load_default_context()
639
637
640 view_name = getattr(self.request.matched_route, 'name', None)
638 view_name = getattr(self.request.matched_route, 'name', None)
641
639
642 c.annotate = view_name == 'repo_files:annotated'
640 c.annotate = view_name == 'repo_files:annotated'
643 # default is false, but .rst/.md files later are auto rendered, we can
641 # default is false, but .rst/.md files later are auto rendered, we can
644 # overwrite auto rendering by setting this GET flag
642 # overwrite auto rendering by setting this GET flag
645 c.renderer = view_name == 'repo_files:rendered' or not self.request.GET.get('no-render', False)
643 c.renderer = view_name == 'repo_files:rendered' or not self.request.GET.get('no-render', False)
646
644
647 commit_id, f_path = self._get_commit_and_path()
645 commit_id, f_path = self._get_commit_and_path()
648
646
649 c.commit = self._get_commit_or_redirect(commit_id)
647 c.commit = self._get_commit_or_redirect(commit_id)
650 c.branch = self.request.GET.get('branch', None)
648 c.branch = self.request.GET.get('branch', None)
651 c.f_path = f_path
649 c.f_path = f_path
652 at_rev = self.request.GET.get('at')
650 at_rev = self.request.GET.get('at')
653
651
654 # prev link
652 # prev link
655 try:
653 try:
656 prev_commit = c.commit.prev(c.branch)
654 prev_commit = c.commit.prev(c.branch)
657 c.prev_commit = prev_commit
655 c.prev_commit = prev_commit
658 c.url_prev = h.route_path(
656 c.url_prev = h.route_path(
659 'repo_files', repo_name=self.db_repo_name,
657 'repo_files', repo_name=self.db_repo_name,
660 commit_id=prev_commit.raw_id, f_path=f_path)
658 commit_id=prev_commit.raw_id, f_path=f_path)
661 if c.branch:
659 if c.branch:
662 c.url_prev += '?branch=%s' % c.branch
660 c.url_prev += '?branch=%s' % c.branch
663 except (CommitDoesNotExistError, VCSError):
661 except (CommitDoesNotExistError, VCSError):
664 c.url_prev = '#'
662 c.url_prev = '#'
665 c.prev_commit = EmptyCommit()
663 c.prev_commit = EmptyCommit()
666
664
667 # next link
665 # next link
668 try:
666 try:
669 next_commit = c.commit.next(c.branch)
667 next_commit = c.commit.next(c.branch)
670 c.next_commit = next_commit
668 c.next_commit = next_commit
671 c.url_next = h.route_path(
669 c.url_next = h.route_path(
672 'repo_files', repo_name=self.db_repo_name,
670 'repo_files', repo_name=self.db_repo_name,
673 commit_id=next_commit.raw_id, f_path=f_path)
671 commit_id=next_commit.raw_id, f_path=f_path)
674 if c.branch:
672 if c.branch:
675 c.url_next += '?branch=%s' % c.branch
673 c.url_next += '?branch=%s' % c.branch
676 except (CommitDoesNotExistError, VCSError):
674 except (CommitDoesNotExistError, VCSError):
677 c.url_next = '#'
675 c.url_next = '#'
678 c.next_commit = EmptyCommit()
676 c.next_commit = EmptyCommit()
679
677
680 # files or dirs
678 # files or dirs
681 try:
679 try:
682 c.file = c.commit.get_node(f_path, pre_load=['is_binary', 'size', 'data'])
680 c.file = c.commit.get_node(f_path, pre_load=['is_binary', 'size', 'data'])
683
681
684 c.file_author = True
682 c.file_author = True
685 c.file_tree = ''
683 c.file_tree = ''
686
684
687 # load file content
685 # load file content
688 if c.file.is_file():
686 if c.file.is_file():
689 c.lf_node = {}
687 c.lf_node = {}
690
688
691 has_lf_enabled = self._is_lf_enabled(self.db_repo)
689 has_lf_enabled = self._is_lf_enabled(self.db_repo)
692 if has_lf_enabled:
690 if has_lf_enabled:
693 c.lf_node = c.file.get_largefile_node()
691 c.lf_node = c.file.get_largefile_node()
694
692
695 c.file_source_page = 'true'
693 c.file_source_page = 'true'
696 c.file_last_commit = c.file.last_commit
694 c.file_last_commit = c.file.last_commit
697
695
698 c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
696 c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
699
697
700 if not (c.file_size_too_big or c.file.is_binary):
698 if not (c.file_size_too_big or c.file.is_binary):
701 if c.annotate: # annotation has precedence over renderer
699 if c.annotate: # annotation has precedence over renderer
702 c.annotated_lines = filenode_as_annotated_lines_tokens(
700 c.annotated_lines = filenode_as_annotated_lines_tokens(
703 c.file
701 c.file
704 )
702 )
705 else:
703 else:
706 c.renderer = (
704 c.renderer = (
707 c.renderer and h.renderer_from_filename(c.file.path)
705 c.renderer and h.renderer_from_filename(c.file.path)
708 )
706 )
709 if not c.renderer:
707 if not c.renderer:
710 c.lines = filenode_as_lines_tokens(c.file)
708 c.lines = filenode_as_lines_tokens(c.file)
711
709
712 _branch_name, _sha_commit_id, is_head = \
710 _branch_name, _sha_commit_id, is_head = \
713 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
711 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
714 landing_ref=self.db_repo.landing_ref_name)
712 landing_ref=self.db_repo.landing_ref_name)
715 c.on_branch_head = is_head
713 c.on_branch_head = is_head
716
714
717 branch = c.commit.branch if (
715 branch = c.commit.branch if (
718 c.commit.branch and '/' not in c.commit.branch) else None
716 c.commit.branch and '/' not in c.commit.branch) else None
719 c.branch_or_raw_id = branch or c.commit.raw_id
717 c.branch_or_raw_id = branch or c.commit.raw_id
720 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
718 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
721
719
722 author = c.file_last_commit.author
720 author = c.file_last_commit.author
723 c.authors = [[
721 c.authors = [[
724 h.email(author),
722 h.email(author),
725 h.person(author, 'username_or_name_or_email'),
723 h.person(author, 'username_or_name_or_email'),
726 1
724 1
727 ]]
725 ]]
728
726
729 else: # load tree content at path
727 else: # load tree content at path
730 c.file_source_page = 'false'
728 c.file_source_page = 'false'
731 c.authors = []
729 c.authors = []
732 # this loads a simple tree without metadata to speed things up
730 # this loads a simple tree without metadata to speed things up
733 # later via ajax we call repo_nodetree_full and fetch whole
731 # later via ajax we call repo_nodetree_full and fetch whole
734 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path, at_rev=at_rev)
732 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path, at_rev=at_rev)
735
733
736 c.readme_data, c.readme_file = \
734 c.readme_data, c.readme_file = \
737 self._get_readme_data(self.db_repo, c.visual.default_renderer,
735 self._get_readme_data(self.db_repo, c.visual.default_renderer,
738 c.commit.raw_id, f_path)
736 c.commit.raw_id, f_path)
739
737
740 except RepositoryError as e:
738 except RepositoryError as e:
741 h.flash(h.escape(safe_str(e)), category='error')
739 h.flash(h.escape(safe_str(e)), category='error')
742 raise HTTPNotFound()
740 raise HTTPNotFound()
743
741
744 if self.request.environ.get('HTTP_X_PJAX'):
742 if self.request.environ.get('HTTP_X_PJAX'):
745 html = render('rhodecode:templates/files/files_pjax.mako',
743 html = render('rhodecode:templates/files/files_pjax.mako',
746 self._get_template_context(c), self.request)
744 self._get_template_context(c), self.request)
747 else:
745 else:
748 html = render('rhodecode:templates/files/files.mako',
746 html = render('rhodecode:templates/files/files.mako',
749 self._get_template_context(c), self.request)
747 self._get_template_context(c), self.request)
750 return Response(html)
748 return Response(html)
751
749
752 @HasRepoPermissionAnyDecorator(
750 @HasRepoPermissionAnyDecorator(
753 'repository.read', 'repository.write', 'repository.admin')
751 'repository.read', 'repository.write', 'repository.admin')
754 def repo_files_annotated_previous(self):
752 def repo_files_annotated_previous(self):
755 self.load_default_context()
753 self.load_default_context()
756
754
757 commit_id, f_path = self._get_commit_and_path()
755 commit_id, f_path = self._get_commit_and_path()
758 commit = self._get_commit_or_redirect(commit_id)
756 commit = self._get_commit_or_redirect(commit_id)
759 prev_commit_id = commit.raw_id
757 prev_commit_id = commit.raw_id
760 line_anchor = self.request.GET.get('line_anchor')
758 line_anchor = self.request.GET.get('line_anchor')
761 is_file = False
759 is_file = False
762 try:
760 try:
763 _file = commit.get_node(f_path)
761 _file = commit.get_node(f_path)
764 is_file = _file.is_file()
762 is_file = _file.is_file()
765 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
763 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
766 pass
764 pass
767
765
768 if is_file:
766 if is_file:
769 history = commit.get_path_history(f_path)
767 history = commit.get_path_history(f_path)
770 prev_commit_id = history[1].raw_id \
768 prev_commit_id = history[1].raw_id \
771 if len(history) > 1 else prev_commit_id
769 if len(history) > 1 else prev_commit_id
772 prev_url = h.route_path(
770 prev_url = h.route_path(
773 'repo_files:annotated', repo_name=self.db_repo_name,
771 'repo_files:annotated', repo_name=self.db_repo_name,
774 commit_id=prev_commit_id, f_path=f_path,
772 commit_id=prev_commit_id, f_path=f_path,
775 _anchor='L{}'.format(line_anchor))
773 _anchor=f'L{line_anchor}')
776
774
777 raise HTTPFound(prev_url)
775 raise HTTPFound(prev_url)
778
776
779 @LoginRequired()
777 @LoginRequired()
780 @HasRepoPermissionAnyDecorator(
778 @HasRepoPermissionAnyDecorator(
781 'repository.read', 'repository.write', 'repository.admin')
779 'repository.read', 'repository.write', 'repository.admin')
782 def repo_nodetree_full(self):
780 def repo_nodetree_full(self):
783 """
781 """
784 Returns rendered html of file tree that contains commit date,
782 Returns rendered html of file tree that contains commit date,
785 author, commit_id for the specified combination of
783 author, commit_id for the specified combination of
786 repo, commit_id and file path
784 repo, commit_id and file path
787 """
785 """
788 c = self.load_default_context()
786 c = self.load_default_context()
789
787
790 commit_id, f_path = self._get_commit_and_path()
788 commit_id, f_path = self._get_commit_and_path()
791 commit = self._get_commit_or_redirect(commit_id)
789 commit = self._get_commit_or_redirect(commit_id)
792 try:
790 try:
793 dir_node = commit.get_node(f_path)
791 dir_node = commit.get_node(f_path)
794 except RepositoryError as e:
792 except RepositoryError as e:
795 return Response('error: {}'.format(h.escape(safe_str(e))))
793 return Response(f'error: {h.escape(safe_str(e))}')
796
794
797 if dir_node.is_file():
795 if dir_node.is_file():
798 return Response('')
796 return Response('')
799
797
800 c.file = dir_node
798 c.file = dir_node
801 c.commit = commit
799 c.commit = commit
802 at_rev = self.request.GET.get('at')
800 at_rev = self.request.GET.get('at')
803
801
804 html = self._get_tree_at_commit(
802 html = self._get_tree_at_commit(
805 c, commit.raw_id, dir_node.path, full_load=True, at_rev=at_rev)
803 c, commit.raw_id, dir_node.path, full_load=True, at_rev=at_rev)
806
804
807 return Response(html)
805 return Response(html)
808
806
809 def _get_attachement_headers(self, f_path):
807 def _get_attachement_headers(self, f_path):
810 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
808 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
811 safe_path = f_name.replace('"', '\\"')
809 safe_path = f_name.replace('"', '\\"')
812 encoded_path = urllib.parse.quote(f_name)
810 encoded_path = urllib.parse.quote(f_name)
813
811
814 return "attachment; " \
812 return "attachment; " \
815 "filename=\"{}\"; " \
813 "filename=\"{}\"; " \
816 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
814 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
817
815
818 @LoginRequired()
816 @LoginRequired()
819 @HasRepoPermissionAnyDecorator(
817 @HasRepoPermissionAnyDecorator(
820 'repository.read', 'repository.write', 'repository.admin')
818 'repository.read', 'repository.write', 'repository.admin')
821 def repo_file_raw(self):
819 def repo_file_raw(self):
822 """
820 """
823 Action for show as raw, some mimetypes are "rendered",
821 Action for show as raw, some mimetypes are "rendered",
824 those include images, icons.
822 those include images, icons.
825 """
823 """
826 c = self.load_default_context()
824 c = self.load_default_context()
827
825
828 commit_id, f_path = self._get_commit_and_path()
826 commit_id, f_path = self._get_commit_and_path()
829 commit = self._get_commit_or_redirect(commit_id)
827 commit = self._get_commit_or_redirect(commit_id)
830 file_node = self._get_filenode_or_redirect(commit, f_path)
828 file_node = self._get_filenode_or_redirect(commit, f_path)
831
829
832 raw_mimetype_mapping = {
830 raw_mimetype_mapping = {
833 # map original mimetype to a mimetype used for "show as raw"
831 # map original mimetype to a mimetype used for "show as raw"
834 # you can also provide a content-disposition to override the
832 # you can also provide a content-disposition to override the
835 # default "attachment" disposition.
833 # default "attachment" disposition.
836 # orig_type: (new_type, new_dispo)
834 # orig_type: (new_type, new_dispo)
837
835
838 # show images inline:
836 # show images inline:
839 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
837 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
840 # for example render an SVG with javascript inside or even render
838 # for example render an SVG with javascript inside or even render
841 # HTML.
839 # HTML.
842 'image/x-icon': ('image/x-icon', 'inline'),
840 'image/x-icon': ('image/x-icon', 'inline'),
843 'image/png': ('image/png', 'inline'),
841 'image/png': ('image/png', 'inline'),
844 'image/gif': ('image/gif', 'inline'),
842 'image/gif': ('image/gif', 'inline'),
845 'image/jpeg': ('image/jpeg', 'inline'),
843 'image/jpeg': ('image/jpeg', 'inline'),
846 'application/pdf': ('application/pdf', 'inline'),
844 'application/pdf': ('application/pdf', 'inline'),
847 }
845 }
848
846
849 mimetype = file_node.mimetype
847 mimetype = file_node.mimetype
850 try:
848 try:
851 mimetype, disposition = raw_mimetype_mapping[mimetype]
849 mimetype, disposition = raw_mimetype_mapping[mimetype]
852 except KeyError:
850 except KeyError:
853 # we don't know anything special about this, handle it safely
851 # we don't know anything special about this, handle it safely
854 if file_node.is_binary:
852 if file_node.is_binary:
855 # do same as download raw for binary files
853 # do same as download raw for binary files
856 mimetype, disposition = 'application/octet-stream', 'attachment'
854 mimetype, disposition = 'application/octet-stream', 'attachment'
857 else:
855 else:
858 # do not just use the original mimetype, but force text/plain,
856 # do not just use the original mimetype, but force text/plain,
859 # otherwise it would serve text/html and that might be unsafe.
857 # otherwise it would serve text/html and that might be unsafe.
860 # Note: underlying vcs library fakes text/plain mimetype if the
858 # Note: underlying vcs library fakes text/plain mimetype if the
861 # mimetype can not be determined and it thinks it is not
859 # mimetype can not be determined and it thinks it is not
862 # binary.This might lead to erroneous text display in some
860 # binary.This might lead to erroneous text display in some
863 # cases, but helps in other cases, like with text files
861 # cases, but helps in other cases, like with text files
864 # without extension.
862 # without extension.
865 mimetype, disposition = 'text/plain', 'inline'
863 mimetype, disposition = 'text/plain', 'inline'
866
864
867 if disposition == 'attachment':
865 if disposition == 'attachment':
868 disposition = self._get_attachement_headers(f_path)
866 disposition = self._get_attachement_headers(f_path)
869
867
870 stream_content = file_node.stream_bytes()
868 stream_content = file_node.stream_bytes()
871
869
872 response = Response(app_iter=stream_content)
870 response = Response(app_iter=stream_content)
873 response.content_disposition = disposition
871 response.content_disposition = disposition
874 response.content_type = mimetype
872 response.content_type = mimetype
875
873
876 charset = self._get_default_encoding(c)
874 charset = self._get_default_encoding(c)
877 if charset:
875 if charset:
878 response.charset = charset
876 response.charset = charset
879
877
880 return response
878 return response
881
879
882 @LoginRequired()
880 @LoginRequired()
883 @HasRepoPermissionAnyDecorator(
881 @HasRepoPermissionAnyDecorator(
884 'repository.read', 'repository.write', 'repository.admin')
882 'repository.read', 'repository.write', 'repository.admin')
885 def repo_file_download(self):
883 def repo_file_download(self):
886 c = self.load_default_context()
884 c = self.load_default_context()
887
885
888 commit_id, f_path = self._get_commit_and_path()
886 commit_id, f_path = self._get_commit_and_path()
889 commit = self._get_commit_or_redirect(commit_id)
887 commit = self._get_commit_or_redirect(commit_id)
890 file_node = self._get_filenode_or_redirect(commit, f_path)
888 file_node = self._get_filenode_or_redirect(commit, f_path)
891
889
892 if self.request.GET.get('lf'):
890 if self.request.GET.get('lf'):
893 # only if lf get flag is passed, we download this file
891 # only if lf get flag is passed, we download this file
894 # as LFS/Largefile
892 # as LFS/Largefile
895 lf_node = file_node.get_largefile_node()
893 lf_node = file_node.get_largefile_node()
896 if lf_node:
894 if lf_node:
897 # overwrite our pointer with the REAL large-file
895 # overwrite our pointer with the REAL large-file
898 file_node = lf_node
896 file_node = lf_node
899
897
900 disposition = self._get_attachement_headers(f_path)
898 disposition = self._get_attachement_headers(f_path)
901
899
902 stream_content = file_node.stream_bytes()
900 stream_content = file_node.stream_bytes()
903
901
904 response = Response(app_iter=stream_content)
902 response = Response(app_iter=stream_content)
905 response.content_disposition = disposition
903 response.content_disposition = disposition
906 response.content_type = file_node.mimetype
904 response.content_type = file_node.mimetype
907
905
908 charset = self._get_default_encoding(c)
906 charset = self._get_default_encoding(c)
909 if charset:
907 if charset:
910 response.charset = charset
908 response.charset = charset
911
909
912 return response
910 return response
913
911
914 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
912 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
915
913
916 cache_seconds = safe_int(
914 cache_seconds = safe_int(
917 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
915 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
918 cache_on = cache_seconds > 0
916 cache_on = cache_seconds > 0
919 log.debug(
917 log.debug(
920 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
918 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
921 'with caching: %s[TTL: %ss]' % (
919 'with caching: %s[TTL: %ss]' % (
922 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
920 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
923
921
924 cache_namespace_uid = 'repo.{}'.format(repo_id)
922 cache_namespace_uid = f'repo.{repo_id}'
925 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
923 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
926
924
927 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
925 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
928 def compute_file_search(_name_hash, _repo_id, _commit_id, _f_path):
926 def compute_file_search(_name_hash, _repo_id, _commit_id, _f_path):
929 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
927 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
930 _repo_id, commit_id, f_path)
928 _repo_id, commit_id, f_path)
931 try:
929 try:
932 _d, _f = ScmModel().get_quick_filter_nodes(repo_name, _commit_id, _f_path)
930 _d, _f = ScmModel().get_quick_filter_nodes(repo_name, _commit_id, _f_path)
933 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
931 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
934 log.exception(safe_str(e))
932 log.exception(safe_str(e))
935 h.flash(h.escape(safe_str(e)), category='error')
933 h.flash(h.escape(safe_str(e)), category='error')
936 raise HTTPFound(h.route_path(
934 raise HTTPFound(h.route_path(
937 'repo_files', repo_name=self.db_repo_name,
935 'repo_files', repo_name=self.db_repo_name,
938 commit_id='tip', f_path='/'))
936 commit_id='tip', f_path='/'))
939
937
940 return _d + _f
938 return _d + _f
941
939
942 result = compute_file_search(self.db_repo.repo_name_hash, self.db_repo.repo_id,
940 result = compute_file_search(self.db_repo.repo_name_hash, self.db_repo.repo_id,
943 commit_id, f_path)
941 commit_id, f_path)
944 return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result)
942 return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result)
945
943
946 @LoginRequired()
944 @LoginRequired()
947 @HasRepoPermissionAnyDecorator(
945 @HasRepoPermissionAnyDecorator(
948 'repository.read', 'repository.write', 'repository.admin')
946 'repository.read', 'repository.write', 'repository.admin')
949 def repo_nodelist(self):
947 def repo_nodelist(self):
950 self.load_default_context()
948 self.load_default_context()
951
949
952 commit_id, f_path = self._get_commit_and_path()
950 commit_id, f_path = self._get_commit_and_path()
953 commit = self._get_commit_or_redirect(commit_id)
951 commit = self._get_commit_or_redirect(commit_id)
954
952
955 metadata = self._get_nodelist_at_commit(
953 metadata = self._get_nodelist_at_commit(
956 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
954 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
957 return {'nodes': [x for x in metadata]}
955 return {'nodes': [x for x in metadata]}
958
956
959 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
957 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
960 items = []
958 items = []
961 for name, commit_id in branches_or_tags.items():
959 for name, commit_id in branches_or_tags.items():
962 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
960 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
963 items.append((sym_ref, name, ref_type))
961 items.append((sym_ref, name, ref_type))
964 return items
962 return items
965
963
966 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
964 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
967 return commit_id
965 return commit_id
968
966
969 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
967 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
970 return commit_id
968 return commit_id
971
969
972 # NOTE(dan): old code we used in "diff" mode compare
970 # NOTE(dan): old code we used in "diff" mode compare
973 new_f_path = vcspath.join(name, f_path)
971 new_f_path = vcspath.join(name, f_path)
974 return f'{new_f_path}@{commit_id}'
972 return f'{new_f_path}@{commit_id}'
975
973
976 def _get_node_history(self, commit_obj, f_path, commits=None):
974 def _get_node_history(self, commit_obj, f_path, commits=None):
977 """
975 """
978 get commit history for given node
976 get commit history for given node
979
977
980 :param commit_obj: commit to calculate history
978 :param commit_obj: commit to calculate history
981 :param f_path: path for node to calculate history for
979 :param f_path: path for node to calculate history for
982 :param commits: if passed don't calculate history and take
980 :param commits: if passed don't calculate history and take
983 commits defined in this list
981 commits defined in this list
984 """
982 """
985 _ = self.request.translate
983 _ = self.request.translate
986
984
987 # calculate history based on tip
985 # calculate history based on tip
988 tip = self.rhodecode_vcs_repo.get_commit()
986 tip = self.rhodecode_vcs_repo.get_commit()
989 if commits is None:
987 if commits is None:
990 pre_load = ["author", "branch"]
988 pre_load = ["author", "branch"]
991 try:
989 try:
992 commits = tip.get_path_history(f_path, pre_load=pre_load)
990 commits = tip.get_path_history(f_path, pre_load=pre_load)
993 except (NodeDoesNotExistError, CommitError):
991 except (NodeDoesNotExistError, CommitError):
994 # this node is not present at tip!
992 # this node is not present at tip!
995 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
993 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
996
994
997 history = []
995 history = []
998 commits_group = ([], _("Changesets"))
996 commits_group = ([], _("Changesets"))
999 for commit in commits:
997 for commit in commits:
1000 branch = ' (%s)' % commit.branch if commit.branch else ''
998 branch = ' (%s)' % commit.branch if commit.branch else ''
1001 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
999 n_desc = 'r{}:{}{}'.format(commit.idx, commit.short_id, branch)
1002 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
1000 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
1003 history.append(commits_group)
1001 history.append(commits_group)
1004
1002
1005 symbolic_reference = self._symbolic_reference
1003 symbolic_reference = self._symbolic_reference
1006
1004
1007 if self.rhodecode_vcs_repo.alias == 'svn':
1005 if self.rhodecode_vcs_repo.alias == 'svn':
1008 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
1006 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
1009 f_path, self.rhodecode_vcs_repo)
1007 f_path, self.rhodecode_vcs_repo)
1010 if adjusted_f_path != f_path:
1008 if adjusted_f_path != f_path:
1011 log.debug(
1009 log.debug(
1012 'Recognized svn tag or branch in file "%s", using svn '
1010 'Recognized svn tag or branch in file "%s", using svn '
1013 'specific symbolic references', f_path)
1011 'specific symbolic references', f_path)
1014 f_path = adjusted_f_path
1012 f_path = adjusted_f_path
1015 symbolic_reference = self._symbolic_reference_svn
1013 symbolic_reference = self._symbolic_reference_svn
1016
1014
1017 branches = self._create_references(
1015 branches = self._create_references(
1018 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1016 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1019 branches_group = (branches, _("Branches"))
1017 branches_group = (branches, _("Branches"))
1020
1018
1021 tags = self._create_references(
1019 tags = self._create_references(
1022 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1020 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1023 tags_group = (tags, _("Tags"))
1021 tags_group = (tags, _("Tags"))
1024
1022
1025 history.append(branches_group)
1023 history.append(branches_group)
1026 history.append(tags_group)
1024 history.append(tags_group)
1027
1025
1028 return history, commits
1026 return history, commits
1029
1027
1030 @LoginRequired()
1028 @LoginRequired()
1031 @HasRepoPermissionAnyDecorator(
1029 @HasRepoPermissionAnyDecorator(
1032 'repository.read', 'repository.write', 'repository.admin')
1030 'repository.read', 'repository.write', 'repository.admin')
1033 def repo_file_history(self):
1031 def repo_file_history(self):
1034 self.load_default_context()
1032 self.load_default_context()
1035
1033
1036 commit_id, f_path = self._get_commit_and_path()
1034 commit_id, f_path = self._get_commit_and_path()
1037 commit = self._get_commit_or_redirect(commit_id)
1035 commit = self._get_commit_or_redirect(commit_id)
1038 file_node = self._get_filenode_or_redirect(commit, f_path)
1036 file_node = self._get_filenode_or_redirect(commit, f_path)
1039
1037
1040 if file_node.is_file():
1038 if file_node.is_file():
1041 file_history, _hist = self._get_node_history(commit, f_path)
1039 file_history, _hist = self._get_node_history(commit, f_path)
1042
1040
1043 res = []
1041 res = []
1044 for section_items, section in file_history:
1042 for section_items, section in file_history:
1045 items = []
1043 items = []
1046 for obj_id, obj_text, obj_type in section_items:
1044 for obj_id, obj_text, obj_type in section_items:
1047 at_rev = ''
1045 at_rev = ''
1048 if obj_type in ['branch', 'bookmark', 'tag']:
1046 if obj_type in ['branch', 'bookmark', 'tag']:
1049 at_rev = obj_text
1047 at_rev = obj_text
1050 entry = {
1048 entry = {
1051 'id': obj_id,
1049 'id': obj_id,
1052 'text': obj_text,
1050 'text': obj_text,
1053 'type': obj_type,
1051 'type': obj_type,
1054 'at_rev': at_rev
1052 'at_rev': at_rev
1055 }
1053 }
1056
1054
1057 items.append(entry)
1055 items.append(entry)
1058
1056
1059 res.append({
1057 res.append({
1060 'text': section,
1058 'text': section,
1061 'children': items
1059 'children': items
1062 })
1060 })
1063
1061
1064 data = {
1062 data = {
1065 'more': False,
1063 'more': False,
1066 'results': res
1064 'results': res
1067 }
1065 }
1068 return data
1066 return data
1069
1067
1070 log.warning('Cannot fetch history for directory')
1068 log.warning('Cannot fetch history for directory')
1071 raise HTTPBadRequest()
1069 raise HTTPBadRequest()
1072
1070
1073 @LoginRequired()
1071 @LoginRequired()
1074 @HasRepoPermissionAnyDecorator(
1072 @HasRepoPermissionAnyDecorator(
1075 'repository.read', 'repository.write', 'repository.admin')
1073 'repository.read', 'repository.write', 'repository.admin')
1076 def repo_file_authors(self):
1074 def repo_file_authors(self):
1077 c = self.load_default_context()
1075 c = self.load_default_context()
1078
1076
1079 commit_id, f_path = self._get_commit_and_path()
1077 commit_id, f_path = self._get_commit_and_path()
1080 commit = self._get_commit_or_redirect(commit_id)
1078 commit = self._get_commit_or_redirect(commit_id)
1081 file_node = self._get_filenode_or_redirect(commit, f_path)
1079 file_node = self._get_filenode_or_redirect(commit, f_path)
1082
1080
1083 if not file_node.is_file():
1081 if not file_node.is_file():
1084 raise HTTPBadRequest()
1082 raise HTTPBadRequest()
1085
1083
1086 c.file_last_commit = file_node.last_commit
1084 c.file_last_commit = file_node.last_commit
1087 if self.request.GET.get('annotate') == '1':
1085 if self.request.GET.get('annotate') == '1':
1088 # use _hist from annotation if annotation mode is on
1086 # use _hist from annotation if annotation mode is on
1089 commit_ids = set(x[1] for x in file_node.annotate)
1087 commit_ids = {x[1] for x in file_node.annotate}
1090 _hist = (
1088 _hist = (
1091 self.rhodecode_vcs_repo.get_commit(commit_id)
1089 self.rhodecode_vcs_repo.get_commit(commit_id)
1092 for commit_id in commit_ids)
1090 for commit_id in commit_ids)
1093 else:
1091 else:
1094 _f_history, _hist = self._get_node_history(commit, f_path)
1092 _f_history, _hist = self._get_node_history(commit, f_path)
1095 c.file_author = False
1093 c.file_author = False
1096
1094
1097 unique = collections.OrderedDict()
1095 unique = collections.OrderedDict()
1098 for commit in _hist:
1096 for commit in _hist:
1099 author = commit.author
1097 author = commit.author
1100 if author not in unique:
1098 if author not in unique:
1101 unique[commit.author] = [
1099 unique[commit.author] = [
1102 h.email(author),
1100 h.email(author),
1103 h.person(author, 'username_or_name_or_email'),
1101 h.person(author, 'username_or_name_or_email'),
1104 1 # counter
1102 1 # counter
1105 ]
1103 ]
1106
1104
1107 else:
1105 else:
1108 # increase counter
1106 # increase counter
1109 unique[commit.author][2] += 1
1107 unique[commit.author][2] += 1
1110
1108
1111 c.authors = [val for val in unique.values()]
1109 c.authors = [val for val in unique.values()]
1112
1110
1113 return self._get_template_context(c)
1111 return self._get_template_context(c)
1114
1112
1115 @LoginRequired()
1113 @LoginRequired()
1116 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1114 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1117 def repo_files_check_head(self):
1115 def repo_files_check_head(self):
1118 self.load_default_context()
1116 self.load_default_context()
1119
1117
1120 commit_id, f_path = self._get_commit_and_path()
1118 commit_id, f_path = self._get_commit_and_path()
1121 _branch_name, _sha_commit_id, is_head = \
1119 _branch_name, _sha_commit_id, is_head = \
1122 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1120 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1123 landing_ref=self.db_repo.landing_ref_name)
1121 landing_ref=self.db_repo.landing_ref_name)
1124
1122
1125 new_path = self.request.POST.get('path')
1123 new_path = self.request.POST.get('path')
1126 operation = self.request.POST.get('operation')
1124 operation = self.request.POST.get('operation')
1127 path_exist = ''
1125 path_exist = ''
1128
1126
1129 if new_path and operation in ['create', 'upload']:
1127 if new_path and operation in ['create', 'upload']:
1130 new_f_path = os.path.join(f_path.lstrip('/'), new_path)
1128 new_f_path = os.path.join(f_path.lstrip('/'), new_path)
1131 try:
1129 try:
1132 commit_obj = self.rhodecode_vcs_repo.get_commit(commit_id)
1130 commit_obj = self.rhodecode_vcs_repo.get_commit(commit_id)
1133 # NOTE(dan): construct whole path without leading /
1131 # NOTE(dan): construct whole path without leading /
1134 file_node = commit_obj.get_node(new_f_path)
1132 file_node = commit_obj.get_node(new_f_path)
1135 if file_node is not None:
1133 if file_node is not None:
1136 path_exist = new_f_path
1134 path_exist = new_f_path
1137 except EmptyRepositoryError:
1135 except EmptyRepositoryError:
1138 pass
1136 pass
1139 except Exception:
1137 except Exception:
1140 pass
1138 pass
1141
1139
1142 return {
1140 return {
1143 'branch': _branch_name,
1141 'branch': _branch_name,
1144 'sha': _sha_commit_id,
1142 'sha': _sha_commit_id,
1145 'is_head': is_head,
1143 'is_head': is_head,
1146 'path_exists': path_exist
1144 'path_exists': path_exist
1147 }
1145 }
1148
1146
1149 @LoginRequired()
1147 @LoginRequired()
1150 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1148 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1151 def repo_files_remove_file(self):
1149 def repo_files_remove_file(self):
1152 _ = self.request.translate
1150 _ = self.request.translate
1153 c = self.load_default_context()
1151 c = self.load_default_context()
1154 commit_id, f_path = self._get_commit_and_path()
1152 commit_id, f_path = self._get_commit_and_path()
1155
1153
1156 self._ensure_not_locked()
1154 self._ensure_not_locked()
1157 _branch_name, _sha_commit_id, is_head = \
1155 _branch_name, _sha_commit_id, is_head = \
1158 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1156 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1159 landing_ref=self.db_repo.landing_ref_name)
1157 landing_ref=self.db_repo.landing_ref_name)
1160
1158
1161 self.forbid_non_head(is_head, f_path)
1159 self.forbid_non_head(is_head, f_path)
1162 self.check_branch_permission(_branch_name)
1160 self.check_branch_permission(_branch_name)
1163
1161
1164 c.commit = self._get_commit_or_redirect(commit_id)
1162 c.commit = self._get_commit_or_redirect(commit_id)
1165 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1163 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1166
1164
1167 c.default_message = _(
1165 c.default_message = _(
1168 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1166 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1169 c.f_path = f_path
1167 c.f_path = f_path
1170
1168
1171 return self._get_template_context(c)
1169 return self._get_template_context(c)
1172
1170
1173 @LoginRequired()
1171 @LoginRequired()
1174 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1172 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1175 @CSRFRequired()
1173 @CSRFRequired()
1176 def repo_files_delete_file(self):
1174 def repo_files_delete_file(self):
1177 _ = self.request.translate
1175 _ = self.request.translate
1178
1176
1179 c = self.load_default_context()
1177 c = self.load_default_context()
1180 commit_id, f_path = self._get_commit_and_path()
1178 commit_id, f_path = self._get_commit_and_path()
1181
1179
1182 self._ensure_not_locked()
1180 self._ensure_not_locked()
1183 _branch_name, _sha_commit_id, is_head = \
1181 _branch_name, _sha_commit_id, is_head = \
1184 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1182 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1185 landing_ref=self.db_repo.landing_ref_name)
1183 landing_ref=self.db_repo.landing_ref_name)
1186
1184
1187 self.forbid_non_head(is_head, f_path)
1185 self.forbid_non_head(is_head, f_path)
1188 self.check_branch_permission(_branch_name)
1186 self.check_branch_permission(_branch_name)
1189
1187
1190 c.commit = self._get_commit_or_redirect(commit_id)
1188 c.commit = self._get_commit_or_redirect(commit_id)
1191 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1189 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1192
1190
1193 c.default_message = _(
1191 c.default_message = _(
1194 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1192 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1195 c.f_path = f_path
1193 c.f_path = f_path
1196 node_path = f_path
1194 node_path = f_path
1197 author = self._rhodecode_db_user.full_contact
1195 author = self._rhodecode_db_user.full_contact
1198 message = self.request.POST.get('message') or c.default_message
1196 message = self.request.POST.get('message') or c.default_message
1199 try:
1197 try:
1200 nodes = {
1198 nodes = {
1201 safe_bytes(node_path): {
1199 safe_bytes(node_path): {
1202 'content': b''
1200 'content': b''
1203 }
1201 }
1204 }
1202 }
1205 ScmModel().delete_nodes(
1203 ScmModel().delete_nodes(
1206 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1204 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1207 message=message,
1205 message=message,
1208 nodes=nodes,
1206 nodes=nodes,
1209 parent_commit=c.commit,
1207 parent_commit=c.commit,
1210 author=author,
1208 author=author,
1211 )
1209 )
1212
1210
1213 h.flash(
1211 h.flash(
1214 _('Successfully deleted file `{}`').format(
1212 _('Successfully deleted file `{}`').format(
1215 h.escape(f_path)), category='success')
1213 h.escape(f_path)), category='success')
1216 except Exception:
1214 except Exception:
1217 log.exception('Error during commit operation')
1215 log.exception('Error during commit operation')
1218 h.flash(_('Error occurred during commit'), category='error')
1216 h.flash(_('Error occurred during commit'), category='error')
1219 raise HTTPFound(
1217 raise HTTPFound(
1220 h.route_path('repo_commit', repo_name=self.db_repo_name,
1218 h.route_path('repo_commit', repo_name=self.db_repo_name,
1221 commit_id='tip'))
1219 commit_id='tip'))
1222
1220
1223 @LoginRequired()
1221 @LoginRequired()
1224 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1222 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1225 def repo_files_edit_file(self):
1223 def repo_files_edit_file(self):
1226 _ = self.request.translate
1224 _ = self.request.translate
1227 c = self.load_default_context()
1225 c = self.load_default_context()
1228 commit_id, f_path = self._get_commit_and_path()
1226 commit_id, f_path = self._get_commit_and_path()
1229
1227
1230 self._ensure_not_locked()
1228 self._ensure_not_locked()
1231 _branch_name, _sha_commit_id, is_head = \
1229 _branch_name, _sha_commit_id, is_head = \
1232 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1230 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1233 landing_ref=self.db_repo.landing_ref_name)
1231 landing_ref=self.db_repo.landing_ref_name)
1234
1232
1235 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1233 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1236 self.check_branch_permission(_branch_name, commit_id=commit_id)
1234 self.check_branch_permission(_branch_name, commit_id=commit_id)
1237
1235
1238 c.commit = self._get_commit_or_redirect(commit_id)
1236 c.commit = self._get_commit_or_redirect(commit_id)
1239 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1237 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1240
1238
1241 if c.file.is_binary:
1239 if c.file.is_binary:
1242 files_url = h.route_path(
1240 files_url = h.route_path(
1243 'repo_files',
1241 'repo_files',
1244 repo_name=self.db_repo_name,
1242 repo_name=self.db_repo_name,
1245 commit_id=c.commit.raw_id, f_path=f_path)
1243 commit_id=c.commit.raw_id, f_path=f_path)
1246 raise HTTPFound(files_url)
1244 raise HTTPFound(files_url)
1247
1245
1248 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1246 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1249 c.f_path = f_path
1247 c.f_path = f_path
1250
1248
1251 return self._get_template_context(c)
1249 return self._get_template_context(c)
1252
1250
1253 @LoginRequired()
1251 @LoginRequired()
1254 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1252 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1255 @CSRFRequired()
1253 @CSRFRequired()
1256 def repo_files_update_file(self):
1254 def repo_files_update_file(self):
1257 _ = self.request.translate
1255 _ = self.request.translate
1258 c = self.load_default_context()
1256 c = self.load_default_context()
1259 commit_id, f_path = self._get_commit_and_path()
1257 commit_id, f_path = self._get_commit_and_path()
1260
1258
1261 self._ensure_not_locked()
1259 self._ensure_not_locked()
1262
1260
1263 c.commit = self._get_commit_or_redirect(commit_id)
1261 c.commit = self._get_commit_or_redirect(commit_id)
1264 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1262 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1265
1263
1266 if c.file.is_binary:
1264 if c.file.is_binary:
1267 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1265 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1268 commit_id=c.commit.raw_id, f_path=f_path))
1266 commit_id=c.commit.raw_id, f_path=f_path))
1269
1267
1270 _branch_name, _sha_commit_id, is_head = \
1268 _branch_name, _sha_commit_id, is_head = \
1271 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1269 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1272 landing_ref=self.db_repo.landing_ref_name)
1270 landing_ref=self.db_repo.landing_ref_name)
1273
1271
1274 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1272 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1275 self.check_branch_permission(_branch_name, commit_id=commit_id)
1273 self.check_branch_permission(_branch_name, commit_id=commit_id)
1276
1274
1277 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1275 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1278 c.f_path = f_path
1276 c.f_path = f_path
1279
1277
1280 old_content = c.file.str_content
1278 old_content = c.file.str_content
1281 sl = old_content.splitlines(1)
1279 sl = old_content.splitlines(1)
1282 first_line = sl[0] if sl else ''
1280 first_line = sl[0] if sl else ''
1283
1281
1284 r_post = self.request.POST
1282 r_post = self.request.POST
1285 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1283 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1286 line_ending_mode = detect_mode(first_line, 0)
1284 line_ending_mode = detect_mode(first_line, 0)
1287 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1285 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1288
1286
1289 message = r_post.get('message') or c.default_message
1287 message = r_post.get('message') or c.default_message
1290
1288
1291 org_node_path = c.file.str_path
1289 org_node_path = c.file.str_path
1292 filename = r_post['filename']
1290 filename = r_post['filename']
1293
1291
1294 root_path = c.file.dir_path
1292 root_path = c.file.dir_path
1295 pure_path = self.create_pure_path(root_path, filename)
1293 pure_path = self.create_pure_path(root_path, filename)
1296 node_path = pure_path.as_posix()
1294 node_path = pure_path.as_posix()
1297
1295
1298 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1296 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1299 commit_id=commit_id)
1297 commit_id=commit_id)
1300 if content == old_content and node_path == org_node_path:
1298 if content == old_content and node_path == org_node_path:
1301 h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
1299 h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
1302 category='warning')
1300 category='warning')
1303 raise HTTPFound(default_redirect_url)
1301 raise HTTPFound(default_redirect_url)
1304
1302
1305 try:
1303 try:
1306 mapping = {
1304 mapping = {
1307 c.file.bytes_path: {
1305 c.file.bytes_path: {
1308 'org_filename': org_node_path,
1306 'org_filename': org_node_path,
1309 'filename': safe_bytes(node_path),
1307 'filename': safe_bytes(node_path),
1310 'content': safe_bytes(content),
1308 'content': safe_bytes(content),
1311 'lexer': '',
1309 'lexer': '',
1312 'op': 'mod',
1310 'op': 'mod',
1313 'mode': c.file.mode
1311 'mode': c.file.mode
1314 }
1312 }
1315 }
1313 }
1316
1314
1317 commit = ScmModel().update_nodes(
1315 commit = ScmModel().update_nodes(
1318 user=self._rhodecode_db_user.user_id,
1316 user=self._rhodecode_db_user.user_id,
1319 repo=self.db_repo,
1317 repo=self.db_repo,
1320 message=message,
1318 message=message,
1321 nodes=mapping,
1319 nodes=mapping,
1322 parent_commit=c.commit,
1320 parent_commit=c.commit,
1323 )
1321 )
1324
1322
1325 h.flash(_('Successfully committed changes to file `{}`').format(
1323 h.flash(_('Successfully committed changes to file `{}`').format(
1326 h.escape(f_path)), category='success')
1324 h.escape(f_path)), category='success')
1327 default_redirect_url = h.route_path(
1325 default_redirect_url = h.route_path(
1328 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1326 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1329
1327
1330 except Exception:
1328 except Exception:
1331 log.exception('Error occurred during commit')
1329 log.exception('Error occurred during commit')
1332 h.flash(_('Error occurred during commit'), category='error')
1330 h.flash(_('Error occurred during commit'), category='error')
1333
1331
1334 raise HTTPFound(default_redirect_url)
1332 raise HTTPFound(default_redirect_url)
1335
1333
1336 @LoginRequired()
1334 @LoginRequired()
1337 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1335 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1338 def repo_files_add_file(self):
1336 def repo_files_add_file(self):
1339 _ = self.request.translate
1337 _ = self.request.translate
1340 c = self.load_default_context()
1338 c = self.load_default_context()
1341 commit_id, f_path = self._get_commit_and_path()
1339 commit_id, f_path = self._get_commit_and_path()
1342
1340
1343 self._ensure_not_locked()
1341 self._ensure_not_locked()
1344
1342
1345 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1343 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1346 if c.commit is None:
1344 if c.commit is None:
1347 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1345 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1348
1346
1349 if self.rhodecode_vcs_repo.is_empty():
1347 if self.rhodecode_vcs_repo.is_empty():
1350 # for empty repository we cannot check for current branch, we rely on
1348 # for empty repository we cannot check for current branch, we rely on
1351 # c.commit.branch instead
1349 # c.commit.branch instead
1352 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1350 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1353 else:
1351 else:
1354 _branch_name, _sha_commit_id, is_head = \
1352 _branch_name, _sha_commit_id, is_head = \
1355 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1353 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1356 landing_ref=self.db_repo.landing_ref_name)
1354 landing_ref=self.db_repo.landing_ref_name)
1357
1355
1358 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1356 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1359 self.check_branch_permission(_branch_name, commit_id=commit_id)
1357 self.check_branch_permission(_branch_name, commit_id=commit_id)
1360
1358
1361 c.default_message = (_('Added file via RhodeCode Enterprise'))
1359 c.default_message = (_('Added file via RhodeCode Enterprise'))
1362 c.f_path = f_path.lstrip('/') # ensure not relative path
1360 c.f_path = f_path.lstrip('/') # ensure not relative path
1363
1361
1364 return self._get_template_context(c)
1362 return self._get_template_context(c)
1365
1363
1366 @LoginRequired()
1364 @LoginRequired()
1367 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1365 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1368 @CSRFRequired()
1366 @CSRFRequired()
1369 def repo_files_create_file(self):
1367 def repo_files_create_file(self):
1370 _ = self.request.translate
1368 _ = self.request.translate
1371 c = self.load_default_context()
1369 c = self.load_default_context()
1372 commit_id, f_path = self._get_commit_and_path()
1370 commit_id, f_path = self._get_commit_and_path()
1373
1371
1374 self._ensure_not_locked()
1372 self._ensure_not_locked()
1375
1373
1376 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1374 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1377 if c.commit is None:
1375 if c.commit is None:
1378 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1376 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1379
1377
1380 # calculate redirect URL
1378 # calculate redirect URL
1381 if self.rhodecode_vcs_repo.is_empty():
1379 if self.rhodecode_vcs_repo.is_empty():
1382 default_redirect_url = h.route_path(
1380 default_redirect_url = h.route_path(
1383 'repo_summary', repo_name=self.db_repo_name)
1381 'repo_summary', repo_name=self.db_repo_name)
1384 else:
1382 else:
1385 default_redirect_url = h.route_path(
1383 default_redirect_url = h.route_path(
1386 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1384 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1387
1385
1388 if self.rhodecode_vcs_repo.is_empty():
1386 if self.rhodecode_vcs_repo.is_empty():
1389 # for empty repository we cannot check for current branch, we rely on
1387 # for empty repository we cannot check for current branch, we rely on
1390 # c.commit.branch instead
1388 # c.commit.branch instead
1391 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1389 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1392 else:
1390 else:
1393 _branch_name, _sha_commit_id, is_head = \
1391 _branch_name, _sha_commit_id, is_head = \
1394 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1392 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1395 landing_ref=self.db_repo.landing_ref_name)
1393 landing_ref=self.db_repo.landing_ref_name)
1396
1394
1397 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1395 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1398 self.check_branch_permission(_branch_name, commit_id=commit_id)
1396 self.check_branch_permission(_branch_name, commit_id=commit_id)
1399
1397
1400 c.default_message = (_('Added file via RhodeCode Enterprise'))
1398 c.default_message = (_('Added file via RhodeCode Enterprise'))
1401 c.f_path = f_path
1399 c.f_path = f_path
1402
1400
1403 r_post = self.request.POST
1401 r_post = self.request.POST
1404 message = r_post.get('message') or c.default_message
1402 message = r_post.get('message') or c.default_message
1405 filename = r_post.get('filename')
1403 filename = r_post.get('filename')
1406 unix_mode = 0
1404 unix_mode = 0
1407
1405
1408 if not filename:
1406 if not filename:
1409 # If there's no commit, redirect to repo summary
1407 # If there's no commit, redirect to repo summary
1410 if type(c.commit) is EmptyCommit:
1408 if type(c.commit) is EmptyCommit:
1411 redirect_url = h.route_path(
1409 redirect_url = h.route_path(
1412 'repo_summary', repo_name=self.db_repo_name)
1410 'repo_summary', repo_name=self.db_repo_name)
1413 else:
1411 else:
1414 redirect_url = default_redirect_url
1412 redirect_url = default_redirect_url
1415 h.flash(_('No filename specified'), category='warning')
1413 h.flash(_('No filename specified'), category='warning')
1416 raise HTTPFound(redirect_url)
1414 raise HTTPFound(redirect_url)
1417
1415
1418 root_path = f_path
1416 root_path = f_path
1419 pure_path = self.create_pure_path(root_path, filename)
1417 pure_path = self.create_pure_path(root_path, filename)
1420 node_path = pure_path.as_posix().lstrip('/')
1418 node_path = pure_path.as_posix().lstrip('/')
1421
1419
1422 author = self._rhodecode_db_user.full_contact
1420 author = self._rhodecode_db_user.full_contact
1423 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1421 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1424 nodes = {
1422 nodes = {
1425 safe_bytes(node_path): {
1423 safe_bytes(node_path): {
1426 'content': safe_bytes(content)
1424 'content': safe_bytes(content)
1427 }
1425 }
1428 }
1426 }
1429
1427
1430 try:
1428 try:
1431
1429
1432 commit = ScmModel().create_nodes(
1430 commit = ScmModel().create_nodes(
1433 user=self._rhodecode_db_user.user_id,
1431 user=self._rhodecode_db_user.user_id,
1434 repo=self.db_repo,
1432 repo=self.db_repo,
1435 message=message,
1433 message=message,
1436 nodes=nodes,
1434 nodes=nodes,
1437 parent_commit=c.commit,
1435 parent_commit=c.commit,
1438 author=author,
1436 author=author,
1439 )
1437 )
1440
1438
1441 h.flash(_('Successfully committed new file `{}`').format(
1439 h.flash(_('Successfully committed new file `{}`').format(
1442 h.escape(node_path)), category='success')
1440 h.escape(node_path)), category='success')
1443
1441
1444 default_redirect_url = h.route_path(
1442 default_redirect_url = h.route_path(
1445 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1443 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1446
1444
1447 except NonRelativePathError:
1445 except NonRelativePathError:
1448 log.exception('Non Relative path found')
1446 log.exception('Non Relative path found')
1449 h.flash(_('The location specified must be a relative path and must not '
1447 h.flash(_('The location specified must be a relative path and must not '
1450 'contain .. in the path'), category='warning')
1448 'contain .. in the path'), category='warning')
1451 raise HTTPFound(default_redirect_url)
1449 raise HTTPFound(default_redirect_url)
1452 except (NodeError, NodeAlreadyExistsError) as e:
1450 except (NodeError, NodeAlreadyExistsError) as e:
1453 h.flash(h.escape(safe_str(e)), category='error')
1451 h.flash(h.escape(safe_str(e)), category='error')
1454 except Exception:
1452 except Exception:
1455 log.exception('Error occurred during commit')
1453 log.exception('Error occurred during commit')
1456 h.flash(_('Error occurred during commit'), category='error')
1454 h.flash(_('Error occurred during commit'), category='error')
1457
1455
1458 raise HTTPFound(default_redirect_url)
1456 raise HTTPFound(default_redirect_url)
1459
1457
1460 @LoginRequired()
1458 @LoginRequired()
1461 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1459 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1462 @CSRFRequired()
1460 @CSRFRequired()
1463 def repo_files_upload_file(self):
1461 def repo_files_upload_file(self):
1464 _ = self.request.translate
1462 _ = self.request.translate
1465 c = self.load_default_context()
1463 c = self.load_default_context()
1466 commit_id, f_path = self._get_commit_and_path()
1464 commit_id, f_path = self._get_commit_and_path()
1467
1465
1468 self._ensure_not_locked()
1466 self._ensure_not_locked()
1469
1467
1470 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1468 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1471 if c.commit is None:
1469 if c.commit is None:
1472 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1470 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1473
1471
1474 # calculate redirect URL
1472 # calculate redirect URL
1475 if self.rhodecode_vcs_repo.is_empty():
1473 if self.rhodecode_vcs_repo.is_empty():
1476 default_redirect_url = h.route_path(
1474 default_redirect_url = h.route_path(
1477 'repo_summary', repo_name=self.db_repo_name)
1475 'repo_summary', repo_name=self.db_repo_name)
1478 else:
1476 else:
1479 default_redirect_url = h.route_path(
1477 default_redirect_url = h.route_path(
1480 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1478 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1481
1479
1482 if self.rhodecode_vcs_repo.is_empty():
1480 if self.rhodecode_vcs_repo.is_empty():
1483 # for empty repository we cannot check for current branch, we rely on
1481 # for empty repository we cannot check for current branch, we rely on
1484 # c.commit.branch instead
1482 # c.commit.branch instead
1485 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1483 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1486 else:
1484 else:
1487 _branch_name, _sha_commit_id, is_head = \
1485 _branch_name, _sha_commit_id, is_head = \
1488 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1486 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1489 landing_ref=self.db_repo.landing_ref_name)
1487 landing_ref=self.db_repo.landing_ref_name)
1490
1488
1491 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1489 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1492 if error:
1490 if error:
1493 return {
1491 return {
1494 'error': error,
1492 'error': error,
1495 'redirect_url': default_redirect_url
1493 'redirect_url': default_redirect_url
1496 }
1494 }
1497 error = self.check_branch_permission(_branch_name, json_mode=True)
1495 error = self.check_branch_permission(_branch_name, json_mode=True)
1498 if error:
1496 if error:
1499 return {
1497 return {
1500 'error': error,
1498 'error': error,
1501 'redirect_url': default_redirect_url
1499 'redirect_url': default_redirect_url
1502 }
1500 }
1503
1501
1504 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1502 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1505 c.f_path = f_path
1503 c.f_path = f_path
1506
1504
1507 r_post = self.request.POST
1505 r_post = self.request.POST
1508
1506
1509 message = c.default_message
1507 message = c.default_message
1510 user_message = r_post.getall('message')
1508 user_message = r_post.getall('message')
1511 if isinstance(user_message, list) and user_message:
1509 if isinstance(user_message, list) and user_message:
1512 # we take the first from duplicated results if it's not empty
1510 # we take the first from duplicated results if it's not empty
1513 message = user_message[0] if user_message[0] else message
1511 message = user_message[0] if user_message[0] else message
1514
1512
1515 nodes = {}
1513 nodes = {}
1516
1514
1517 for file_obj in r_post.getall('files_upload') or []:
1515 for file_obj in r_post.getall('files_upload') or []:
1518 content = file_obj.file
1516 content = file_obj.file
1519 filename = file_obj.filename
1517 filename = file_obj.filename
1520
1518
1521 root_path = f_path
1519 root_path = f_path
1522 pure_path = self.create_pure_path(root_path, filename)
1520 pure_path = self.create_pure_path(root_path, filename)
1523 node_path = pure_path.as_posix().lstrip('/')
1521 node_path = pure_path.as_posix().lstrip('/')
1524
1522
1525 nodes[safe_bytes(node_path)] = {
1523 nodes[safe_bytes(node_path)] = {
1526 'content': content
1524 'content': content
1527 }
1525 }
1528
1526
1529 if not nodes:
1527 if not nodes:
1530 error = 'missing files'
1528 error = 'missing files'
1531 return {
1529 return {
1532 'error': error,
1530 'error': error,
1533 'redirect_url': default_redirect_url
1531 'redirect_url': default_redirect_url
1534 }
1532 }
1535
1533
1536 author = self._rhodecode_db_user.full_contact
1534 author = self._rhodecode_db_user.full_contact
1537
1535
1538 try:
1536 try:
1539 commit = ScmModel().create_nodes(
1537 commit = ScmModel().create_nodes(
1540 user=self._rhodecode_db_user.user_id,
1538 user=self._rhodecode_db_user.user_id,
1541 repo=self.db_repo,
1539 repo=self.db_repo,
1542 message=message,
1540 message=message,
1543 nodes=nodes,
1541 nodes=nodes,
1544 parent_commit=c.commit,
1542 parent_commit=c.commit,
1545 author=author,
1543 author=author,
1546 )
1544 )
1547 if len(nodes) == 1:
1545 if len(nodes) == 1:
1548 flash_message = _('Successfully committed {} new files').format(len(nodes))
1546 flash_message = _('Successfully committed {} new files').format(len(nodes))
1549 else:
1547 else:
1550 flash_message = _('Successfully committed 1 new file')
1548 flash_message = _('Successfully committed 1 new file')
1551
1549
1552 h.flash(flash_message, category='success')
1550 h.flash(flash_message, category='success')
1553
1551
1554 default_redirect_url = h.route_path(
1552 default_redirect_url = h.route_path(
1555 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1553 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1556
1554
1557 except NonRelativePathError:
1555 except NonRelativePathError:
1558 log.exception('Non Relative path found')
1556 log.exception('Non Relative path found')
1559 error = _('The location specified must be a relative path and must not '
1557 error = _('The location specified must be a relative path and must not '
1560 'contain .. in the path')
1558 'contain .. in the path')
1561 h.flash(error, category='warning')
1559 h.flash(error, category='warning')
1562
1560
1563 return {
1561 return {
1564 'error': error,
1562 'error': error,
1565 'redirect_url': default_redirect_url
1563 'redirect_url': default_redirect_url
1566 }
1564 }
1567 except (NodeError, NodeAlreadyExistsError) as e:
1565 except (NodeError, NodeAlreadyExistsError) as e:
1568 error = h.escape(e)
1566 error = h.escape(e)
1569 h.flash(error, category='error')
1567 h.flash(error, category='error')
1570
1568
1571 return {
1569 return {
1572 'error': error,
1570 'error': error,
1573 'redirect_url': default_redirect_url
1571 'redirect_url': default_redirect_url
1574 }
1572 }
1575 except Exception:
1573 except Exception:
1576 log.exception('Error occurred during commit')
1574 log.exception('Error occurred during commit')
1577 error = _('Error occurred during commit')
1575 error = _('Error occurred during commit')
1578 h.flash(error, category='error')
1576 h.flash(error, category='error')
1579 return {
1577 return {
1580 'error': error,
1578 'error': error,
1581 'redirect_url': default_redirect_url
1579 'redirect_url': default_redirect_url
1582 }
1580 }
1583
1581
1584 return {
1582 return {
1585 'error': None,
1583 'error': None,
1586 'redirect_url': default_redirect_url
1584 'redirect_url': default_redirect_url
1587 }
1585 }
@@ -1,254 +1,252 b''
1
2
3 # Copyright (C) 2011-2023 RhodeCode GmbH
1 # Copyright (C) 2011-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 import logging
19 import logging
22 import datetime
20 import datetime
23 import formencode
21 import formencode
24 import formencode.htmlfill
22 import formencode.htmlfill
25
23
26 from pyramid.httpexceptions import HTTPFound
24 from pyramid.httpexceptions import HTTPFound
27
25
28 from pyramid.renderers import render
26 from pyramid.renderers import render
29 from pyramid.response import Response
27 from pyramid.response import Response
30
28
31 from rhodecode import events
29 from rhodecode import events
32 from rhodecode.apps._base import RepoAppView, DataGridAppView
30 from rhodecode.apps._base import RepoAppView, DataGridAppView
33 from rhodecode.lib.auth import (
31 from rhodecode.lib.auth import (
34 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous,
32 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous,
35 HasRepoPermissionAny, HasPermissionAnyDecorator, CSRFRequired)
33 HasRepoPermissionAny, HasPermissionAnyDecorator, CSRFRequired)
36 import rhodecode.lib.helpers as h
34 import rhodecode.lib.helpers as h
37 from rhodecode.lib.str_utils import safe_str
35 from rhodecode.lib.str_utils import safe_str
38 from rhodecode.lib.celerylib.utils import get_task_id
36 from rhodecode.lib.celerylib.utils import get_task_id
39 from rhodecode.model.db import coalesce, or_, Repository, RepoGroup
37 from rhodecode.model.db import coalesce, or_, Repository, RepoGroup
40 from rhodecode.model.permission import PermissionModel
38 from rhodecode.model.permission import PermissionModel
41 from rhodecode.model.repo import RepoModel
39 from rhodecode.model.repo import RepoModel
42 from rhodecode.model.forms import RepoForkForm
40 from rhodecode.model.forms import RepoForkForm
43 from rhodecode.model.scm import ScmModel, RepoGroupList
41 from rhodecode.model.scm import ScmModel, RepoGroupList
44
42
45 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
46
44
47
45
48 class RepoForksView(RepoAppView, DataGridAppView):
46 class RepoForksView(RepoAppView, DataGridAppView):
49
47
50 def load_default_context(self):
48 def load_default_context(self):
51 c = self._get_local_tmpl_context(include_app_defaults=True)
49 c = self._get_local_tmpl_context(include_app_defaults=True)
52 c.rhodecode_repo = self.rhodecode_vcs_repo
50 c.rhodecode_repo = self.rhodecode_vcs_repo
53
51
54 acl_groups = RepoGroupList(
52 acl_groups = RepoGroupList(
55 RepoGroup.query().all(),
53 RepoGroup.query().all(),
56 perm_set=['group.write', 'group.admin'])
54 perm_set=['group.write', 'group.admin'])
57 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
55 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
58 c.repo_groups_choices = list(map(lambda k: safe_str(k[0]), c.repo_groups))
56 c.repo_groups_choices = list(map(lambda k: safe_str(k[0]), c.repo_groups))
59
57
60 c.personal_repo_group = c.rhodecode_user.personal_repo_group
58 c.personal_repo_group = c.rhodecode_user.personal_repo_group
61
59
62 return c
60 return c
63
61
64 @LoginRequired()
62 @LoginRequired()
65 @HasRepoPermissionAnyDecorator(
63 @HasRepoPermissionAnyDecorator(
66 'repository.read', 'repository.write', 'repository.admin')
64 'repository.read', 'repository.write', 'repository.admin')
67 def repo_forks_show_all(self):
65 def repo_forks_show_all(self):
68 c = self.load_default_context()
66 c = self.load_default_context()
69 return self._get_template_context(c)
67 return self._get_template_context(c)
70
68
71 @LoginRequired()
69 @LoginRequired()
72 @HasRepoPermissionAnyDecorator(
70 @HasRepoPermissionAnyDecorator(
73 'repository.read', 'repository.write', 'repository.admin')
71 'repository.read', 'repository.write', 'repository.admin')
74 def repo_forks_data(self):
72 def repo_forks_data(self):
75 _ = self.request.translate
73 _ = self.request.translate
76 self.load_default_context()
74 self.load_default_context()
77 column_map = {
75 column_map = {
78 'fork_name': 'repo_name',
76 'fork_name': 'repo_name',
79 'fork_date': 'created_on',
77 'fork_date': 'created_on',
80 'last_activity': 'updated_on'
78 'last_activity': 'updated_on'
81 }
79 }
82 draw, start, limit = self._extract_chunk(self.request)
80 draw, start, limit = self._extract_chunk(self.request)
83 search_q, order_by, order_dir = self._extract_ordering(
81 search_q, order_by, order_dir = self._extract_ordering(
84 self.request, column_map=column_map)
82 self.request, column_map=column_map)
85
83
86 acl_check = HasRepoPermissionAny(
84 acl_check = HasRepoPermissionAny(
87 'repository.read', 'repository.write', 'repository.admin')
85 'repository.read', 'repository.write', 'repository.admin')
88 repo_id = self.db_repo.repo_id
86 repo_id = self.db_repo.repo_id
89 allowed_ids = [-1]
87 allowed_ids = [-1]
90 for f in Repository.query().filter(Repository.fork_id == repo_id):
88 for f in Repository.query().filter(Repository.fork_id == repo_id):
91 if acl_check(f.repo_name, 'get forks check'):
89 if acl_check(f.repo_name, 'get forks check'):
92 allowed_ids.append(f.repo_id)
90 allowed_ids.append(f.repo_id)
93
91
94 forks_data_total_count = Repository.query()\
92 forks_data_total_count = Repository.query()\
95 .filter(Repository.fork_id == repo_id)\
93 .filter(Repository.fork_id == repo_id)\
96 .filter(Repository.repo_id.in_(allowed_ids))\
94 .filter(Repository.repo_id.in_(allowed_ids))\
97 .count()
95 .count()
98
96
99 # json generate
97 # json generate
100 base_q = Repository.query()\
98 base_q = Repository.query()\
101 .filter(Repository.fork_id == repo_id)\
99 .filter(Repository.fork_id == repo_id)\
102 .filter(Repository.repo_id.in_(allowed_ids))\
100 .filter(Repository.repo_id.in_(allowed_ids))\
103
101
104 if search_q:
102 if search_q:
105 like_expression = u'%{}%'.format(safe_str(search_q))
103 like_expression = f'%{safe_str(search_q)}%'
106 base_q = base_q.filter(or_(
104 base_q = base_q.filter(or_(
107 Repository.repo_name.ilike(like_expression),
105 Repository.repo_name.ilike(like_expression),
108 Repository.description.ilike(like_expression),
106 Repository.description.ilike(like_expression),
109 ))
107 ))
110
108
111 forks_data_total_filtered_count = base_q.count()
109 forks_data_total_filtered_count = base_q.count()
112
110
113 sort_col = getattr(Repository, order_by, None)
111 sort_col = getattr(Repository, order_by, None)
114 if sort_col:
112 if sort_col:
115 if order_dir == 'asc':
113 if order_dir == 'asc':
116 # handle null values properly to order by NULL last
114 # handle null values properly to order by NULL last
117 if order_by in ['last_activity']:
115 if order_by in ['last_activity']:
118 sort_col = coalesce(sort_col, datetime.date.max)
116 sort_col = coalesce(sort_col, datetime.date.max)
119 sort_col = sort_col.asc()
117 sort_col = sort_col.asc()
120 else:
118 else:
121 # handle null values properly to order by NULL last
119 # handle null values properly to order by NULL last
122 if order_by in ['last_activity']:
120 if order_by in ['last_activity']:
123 sort_col = coalesce(sort_col, datetime.date.min)
121 sort_col = coalesce(sort_col, datetime.date.min)
124 sort_col = sort_col.desc()
122 sort_col = sort_col.desc()
125
123
126 base_q = base_q.order_by(sort_col)
124 base_q = base_q.order_by(sort_col)
127 base_q = base_q.offset(start).limit(limit)
125 base_q = base_q.offset(start).limit(limit)
128
126
129 fork_list = base_q.all()
127 fork_list = base_q.all()
130
128
131 def fork_actions(fork):
129 def fork_actions(fork):
132 url_link = h.route_path(
130 url_link = h.route_path(
133 'repo_compare',
131 'repo_compare',
134 repo_name=fork.repo_name,
132 repo_name=fork.repo_name,
135 source_ref_type=self.db_repo.landing_ref_type,
133 source_ref_type=self.db_repo.landing_ref_type,
136 source_ref=self.db_repo.landing_ref_name,
134 source_ref=self.db_repo.landing_ref_name,
137 target_ref_type=self.db_repo.landing_ref_type,
135 target_ref_type=self.db_repo.landing_ref_type,
138 target_ref=self.db_repo.landing_ref_name,
136 target_ref=self.db_repo.landing_ref_name,
139 _query=dict(merge=1, target_repo=f.repo_name))
137 _query=dict(merge=1, target_repo=f.repo_name))
140 return h.link_to(_('Compare fork'), url_link, class_='btn-link')
138 return h.link_to(_('Compare fork'), url_link, class_='btn-link')
141
139
142 def fork_name(fork):
140 def fork_name(fork):
143 return h.link_to(fork.repo_name,
141 return h.link_to(fork.repo_name,
144 h.route_path('repo_summary', repo_name=fork.repo_name))
142 h.route_path('repo_summary', repo_name=fork.repo_name))
145
143
146 forks_data = []
144 forks_data = []
147 for fork in fork_list:
145 for fork in fork_list:
148 forks_data.append({
146 forks_data.append({
149 "username": h.gravatar_with_user(self.request, fork.user.username),
147 "username": h.gravatar_with_user(self.request, fork.user.username),
150 "fork_name": fork_name(fork),
148 "fork_name": fork_name(fork),
151 "description": fork.description_safe,
149 "description": fork.description_safe,
152 "fork_date": h.age_component(fork.created_on, time_is_local=True),
150 "fork_date": h.age_component(fork.created_on, time_is_local=True),
153 "last_activity": h.format_date(fork.updated_on),
151 "last_activity": h.format_date(fork.updated_on),
154 "action": fork_actions(fork),
152 "action": fork_actions(fork),
155 })
153 })
156
154
157 data = ({
155 data = ({
158 'draw': draw,
156 'draw': draw,
159 'data': forks_data,
157 'data': forks_data,
160 'recordsTotal': forks_data_total_count,
158 'recordsTotal': forks_data_total_count,
161 'recordsFiltered': forks_data_total_filtered_count,
159 'recordsFiltered': forks_data_total_filtered_count,
162 })
160 })
163
161
164 return data
162 return data
165
163
166 @LoginRequired()
164 @LoginRequired()
167 @NotAnonymous()
165 @NotAnonymous()
168 @HasPermissionAnyDecorator('hg.admin', PermissionModel.FORKING_ENABLED)
166 @HasPermissionAnyDecorator('hg.admin', PermissionModel.FORKING_ENABLED)
169 @HasRepoPermissionAnyDecorator(
167 @HasRepoPermissionAnyDecorator(
170 'repository.read', 'repository.write', 'repository.admin')
168 'repository.read', 'repository.write', 'repository.admin')
171 def repo_fork_new(self):
169 def repo_fork_new(self):
172 c = self.load_default_context()
170 c = self.load_default_context()
173
171
174 defaults = RepoModel()._get_defaults(self.db_repo_name)
172 defaults = RepoModel()._get_defaults(self.db_repo_name)
175 # alter the description to indicate a fork
173 # alter the description to indicate a fork
176 defaults['description'] = (
174 defaults['description'] = (
177 'fork of repository: %s \n%s' % (
175 'fork of repository: {} \n{}'.format(
178 defaults['repo_name'], defaults['description']))
176 defaults['repo_name'], defaults['description']))
179 # add suffix to fork
177 # add suffix to fork
180 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
178 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
181
179
182 data = render('rhodecode:templates/forks/fork.mako',
180 data = render('rhodecode:templates/forks/fork.mako',
183 self._get_template_context(c), self.request)
181 self._get_template_context(c), self.request)
184 html = formencode.htmlfill.render(
182 html = formencode.htmlfill.render(
185 data,
183 data,
186 defaults=defaults,
184 defaults=defaults,
187 encoding="UTF-8",
185 encoding="UTF-8",
188 force_defaults=False
186 force_defaults=False
189 )
187 )
190 return Response(html)
188 return Response(html)
191
189
192 @LoginRequired()
190 @LoginRequired()
193 @NotAnonymous()
191 @NotAnonymous()
194 @HasPermissionAnyDecorator('hg.admin', PermissionModel.FORKING_ENABLED)
192 @HasPermissionAnyDecorator('hg.admin', PermissionModel.FORKING_ENABLED)
195 @HasRepoPermissionAnyDecorator(
193 @HasRepoPermissionAnyDecorator(
196 'repository.read', 'repository.write', 'repository.admin')
194 'repository.read', 'repository.write', 'repository.admin')
197 @CSRFRequired()
195 @CSRFRequired()
198 def repo_fork_create(self):
196 def repo_fork_create(self):
199 _ = self.request.translate
197 _ = self.request.translate
200 c = self.load_default_context()
198 c = self.load_default_context()
201
199
202 _form = RepoForkForm(self.request.translate,
200 _form = RepoForkForm(self.request.translate,
203 old_data={'repo_type': self.db_repo.repo_type},
201 old_data={'repo_type': self.db_repo.repo_type},
204 repo_groups=c.repo_groups_choices)()
202 repo_groups=c.repo_groups_choices)()
205 post_data = dict(self.request.POST)
203 post_data = dict(self.request.POST)
206
204
207 # forbid injecting other repo by forging a request
205 # forbid injecting other repo by forging a request
208 post_data['fork_parent_id'] = self.db_repo.repo_id
206 post_data['fork_parent_id'] = self.db_repo.repo_id
209 post_data['landing_rev'] = self.db_repo._landing_revision
207 post_data['landing_rev'] = self.db_repo._landing_revision
210
208
211 form_result = {}
209 form_result = {}
212 task_id = None
210 task_id = None
213 try:
211 try:
214 form_result = _form.to_python(post_data)
212 form_result = _form.to_python(post_data)
215 copy_permissions = form_result.get('copy_permissions')
213 copy_permissions = form_result.get('copy_permissions')
216 # create fork is done sometimes async on celery, db transaction
214 # create fork is done sometimes async on celery, db transaction
217 # management is handled there.
215 # management is handled there.
218 task = RepoModel().create_fork(
216 task = RepoModel().create_fork(
219 form_result, c.rhodecode_user.user_id)
217 form_result, c.rhodecode_user.user_id)
220
218
221 task_id = get_task_id(task)
219 task_id = get_task_id(task)
222 except formencode.Invalid as errors:
220 except formencode.Invalid as errors:
223 c.rhodecode_db_repo = self.db_repo
221 c.rhodecode_db_repo = self.db_repo
224
222
225 data = render('rhodecode:templates/forks/fork.mako',
223 data = render('rhodecode:templates/forks/fork.mako',
226 self._get_template_context(c), self.request)
224 self._get_template_context(c), self.request)
227 html = formencode.htmlfill.render(
225 html = formencode.htmlfill.render(
228 data,
226 data,
229 defaults=errors.value,
227 defaults=errors.value,
230 errors=errors.error_dict or {},
228 errors=errors.error_dict or {},
231 prefix_error=False,
229 prefix_error=False,
232 encoding="UTF-8",
230 encoding="UTF-8",
233 force_defaults=False
231 force_defaults=False
234 )
232 )
235 return Response(html)
233 return Response(html)
236 except Exception:
234 except Exception:
237 log.exception(
235 log.exception(
238 u'Exception while trying to fork the repository %s', self.db_repo_name)
236 'Exception while trying to fork the repository %s', self.db_repo_name)
239 msg = _('An error occurred during repository forking %s') % (self.db_repo_name, )
237 msg = _('An error occurred during repository forking %s') % (self.db_repo_name, )
240 h.flash(msg, category='error')
238 h.flash(msg, category='error')
241 raise HTTPFound(h.route_path('home'))
239 raise HTTPFound(h.route_path('home'))
242
240
243 repo_name = form_result.get('repo_name_full', self.db_repo_name)
241 repo_name = form_result.get('repo_name_full', self.db_repo_name)
244
242
245 affected_user_ids = [self._rhodecode_user.user_id]
243 affected_user_ids = [self._rhodecode_user.user_id]
246 if copy_permissions:
244 if copy_permissions:
247 # permission flush is done in repo creating
245 # permission flush is done in repo creating
248 pass
246 pass
249
247
250 PermissionModel().trigger_permission_flush(affected_user_ids)
248 PermissionModel().trigger_permission_flush(affected_user_ids)
251
249
252 raise HTTPFound(
250 raise HTTPFound(
253 h.route_path('repo_creating', repo_name=repo_name,
251 h.route_path('repo_creating', repo_name=repo_name,
254 _query=dict(task_id=task_id)))
252 _query=dict(task_id=task_id)))
@@ -1,56 +1,54 b''
1
2
3 # Copyright (C) 2011-2023 RhodeCode GmbH
1 # Copyright (C) 2011-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 import logging
19 import logging
22
20
23
21
24
22
25 from rhodecode.apps._base import RepoAppView
23 from rhodecode.apps._base import RepoAppView
26 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
24 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
27 from rhodecode.lib import repo_maintenance
25 from rhodecode.lib import repo_maintenance
28
26
29 log = logging.getLogger(__name__)
27 log = logging.getLogger(__name__)
30
28
31
29
32 class RepoMaintenanceView(RepoAppView):
30 class RepoMaintenanceView(RepoAppView):
33 def load_default_context(self):
31 def load_default_context(self):
34 c = self._get_local_tmpl_context()
32 c = self._get_local_tmpl_context()
35 return c
33 return c
36
34
37 @LoginRequired()
35 @LoginRequired()
38 @HasRepoPermissionAnyDecorator('repository.admin')
36 @HasRepoPermissionAnyDecorator('repository.admin')
39 def repo_maintenance(self):
37 def repo_maintenance(self):
40 c = self.load_default_context()
38 c = self.load_default_context()
41 c.active = 'maintenance'
39 c.active = 'maintenance'
42 maintenance = repo_maintenance.RepoMaintenance()
40 maintenance = repo_maintenance.RepoMaintenance()
43 c.executable_tasks = maintenance.get_tasks_for_repo(self.db_repo)
41 c.executable_tasks = maintenance.get_tasks_for_repo(self.db_repo)
44 return self._get_template_context(c)
42 return self._get_template_context(c)
45
43
46 @LoginRequired()
44 @LoginRequired()
47 @HasRepoPermissionAnyDecorator('repository.admin')
45 @HasRepoPermissionAnyDecorator('repository.admin')
48 def repo_maintenance_execute(self):
46 def repo_maintenance_execute(self):
49 c = self.load_default_context()
47 c = self.load_default_context()
50 c.active = 'maintenance'
48 c.active = 'maintenance'
51 _ = self.request.translate
49 _ = self.request.translate
52
50
53 maintenance = repo_maintenance.RepoMaintenance()
51 maintenance = repo_maintenance.RepoMaintenance()
54 executed_types = maintenance.execute(self.db_repo)
52 executed_types = maintenance.execute(self.db_repo)
55
53
56 return executed_types
54 return executed_types
@@ -1,130 +1,128 b''
1
2
3 # Copyright (C) 2011-2023 RhodeCode GmbH
1 # Copyright (C) 2011-2023 RhodeCode GmbH
4 #
2 #
5 # This program is free software: you can redistribute it and/or modify
3 # 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
4 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
8 #
6 #
9 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
10 # GNU General Public License for more details.
13 #
11 #
14 # You should have received a copy of the GNU Affero General Public License
12 # 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/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
14 #
17 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
18
21 import logging
19 import logging
22
20
23 from pyramid.httpexceptions import HTTPFound
21 from pyramid.httpexceptions import HTTPFound
24
22
25 from rhodecode.apps._base import RepoAppView
23 from rhodecode.apps._base import RepoAppView
26 from rhodecode.lib import helpers as h
24 from rhodecode.lib import helpers as h
27 from rhodecode.lib import audit_logger
25 from rhodecode.lib import audit_logger
28 from rhodecode.lib.auth import (
26 from rhodecode.lib.auth import (
29 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
27 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
30 from rhodecode.lib.utils2 import str2bool
28 from rhodecode.lib.utils2 import str2bool
31 from rhodecode.model.db import User
29 from rhodecode.model.db import User
32 from rhodecode.model.forms import RepoPermsForm
30 from rhodecode.model.forms import RepoPermsForm
33 from rhodecode.model.meta import Session
31 from rhodecode.model.meta import Session
34 from rhodecode.model.permission import PermissionModel
32 from rhodecode.model.permission import PermissionModel
35 from rhodecode.model.repo import RepoModel
33 from rhodecode.model.repo import RepoModel
36
34
37 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
38
36
39
37
40 class RepoSettingsPermissionsView(RepoAppView):
38 class RepoSettingsPermissionsView(RepoAppView):
41
39
42 def load_default_context(self):
40 def load_default_context(self):
43 c = self._get_local_tmpl_context()
41 c = self._get_local_tmpl_context()
44 return c
42 return c
45
43
46 @LoginRequired()
44 @LoginRequired()
47 @HasRepoPermissionAnyDecorator('repository.admin')
45 @HasRepoPermissionAnyDecorator('repository.admin')
48 def edit_permissions(self):
46 def edit_permissions(self):
49 _ = self.request.translate
47 _ = self.request.translate
50 c = self.load_default_context()
48 c = self.load_default_context()
51 c.active = 'permissions'
49 c.active = 'permissions'
52 if self.request.GET.get('branch_permissions'):
50 if self.request.GET.get('branch_permissions'):
53 h.flash(_('Explicitly add user or user group with write or higher '
51 h.flash(_('Explicitly add user or user group with write or higher '
54 'permission to modify their branch permissions.'),
52 'permission to modify their branch permissions.'),
55 category='notice')
53 category='notice')
56 return self._get_template_context(c)
54 return self._get_template_context(c)
57
55
58 @LoginRequired()
56 @LoginRequired()
59 @HasRepoPermissionAnyDecorator('repository.admin')
57 @HasRepoPermissionAnyDecorator('repository.admin')
60 @CSRFRequired()
58 @CSRFRequired()
61 def edit_permissions_update(self):
59 def edit_permissions_update(self):
62 _ = self.request.translate
60 _ = self.request.translate
63 c = self.load_default_context()
61 c = self.load_default_context()
64 c.active = 'permissions'
62 c.active = 'permissions'
65 data = self.request.POST
63 data = self.request.POST
66 # store private flag outside of HTML to verify if we can modify
64 # store private flag outside of HTML to verify if we can modify
67 # default user permissions, prevents submission of FAKE post data
65 # default user permissions, prevents submission of FAKE post data
68 # into the form for private repos
66 # into the form for private repos
69 data['repo_private'] = self.db_repo.private
67 data['repo_private'] = self.db_repo.private
70 form = RepoPermsForm(self.request.translate)().to_python(data)
68 form = RepoPermsForm(self.request.translate)().to_python(data)
71 changes = RepoModel().update_permissions(
69 changes = RepoModel().update_permissions(
72 self.db_repo_name, form['perm_additions'], form['perm_updates'],
70 self.db_repo_name, form['perm_additions'], form['perm_updates'],
73 form['perm_deletions'])
71 form['perm_deletions'])
74
72
75 action_data = {
73 action_data = {
76 'added': changes['added'],
74 'added': changes['added'],
77 'updated': changes['updated'],
75 'updated': changes['updated'],
78 'deleted': changes['deleted'],
76 'deleted': changes['deleted'],
79 }
77 }
80 audit_logger.store_web(
78 audit_logger.store_web(
81 'repo.edit.permissions', action_data=action_data,
79 'repo.edit.permissions', action_data=action_data,
82 user=self._rhodecode_user, repo=self.db_repo)
80 user=self._rhodecode_user, repo=self.db_repo)
83
81
84 Session().commit()
82 Session().commit()
85 h.flash(_('Repository access permissions updated'), category='success')
83 h.flash(_('Repository access permissions updated'), category='success')
86
84
87 affected_user_ids = None
85 affected_user_ids = None
88 if changes.get('default_user_changed', False):
86 if changes.get('default_user_changed', False):
89 # if we change the default user, we need to flush everyone permissions
87 # if we change the default user, we need to flush everyone permissions
90 affected_user_ids = User.get_all_user_ids()
88 affected_user_ids = User.get_all_user_ids()
91 PermissionModel().flush_user_permission_caches(
89 PermissionModel().flush_user_permission_caches(
92 changes, affected_user_ids=affected_user_ids)
90 changes, affected_user_ids=affected_user_ids)
93
91
94 raise HTTPFound(
92 raise HTTPFound(
95 h.route_path('edit_repo_perms', repo_name=self.db_repo_name))
93 h.route_path('edit_repo_perms', repo_name=self.db_repo_name))
96
94
97 @LoginRequired()
95 @LoginRequired()
98 @HasRepoPermissionAnyDecorator('repository.admin')
96 @HasRepoPermissionAnyDecorator('repository.admin')
99 @CSRFRequired()
97 @CSRFRequired()
100 def edit_permissions_set_private_repo(self):
98 def edit_permissions_set_private_repo(self):
101 _ = self.request.translate
99 _ = self.request.translate
102 self.load_default_context()
100 self.load_default_context()
103
101
104 private_flag = str2bool(self.request.POST.get('private'))
102 private_flag = str2bool(self.request.POST.get('private'))
105
103
106 try:
104 try:
107 repo = RepoModel().get(self.db_repo.repo_id)
105 repo = RepoModel().get(self.db_repo.repo_id)
108 repo.private = private_flag
106 repo.private = private_flag
109 Session().add(repo)
107 Session().add(repo)
110 RepoModel().grant_user_permission(
108 RepoModel().grant_user_permission(
111 repo=self.db_repo, user=User.DEFAULT_USER, perm='repository.none'
109 repo=self.db_repo, user=User.DEFAULT_USER, perm='repository.none'
112 )
110 )
113
111
114 Session().commit()
112 Session().commit()
115
113
116 h.flash(_('Repository `{}` private mode set successfully').format(self.db_repo_name),
114 h.flash(_('Repository `{}` private mode set successfully').format(self.db_repo_name),
117 category='success')
115 category='success')
118 # NOTE(dan): we change repo private mode we need to notify all USERS
116 # NOTE(dan): we change repo private mode we need to notify all USERS
119 affected_user_ids = User.get_all_user_ids()
117 affected_user_ids = User.get_all_user_ids()
120 PermissionModel().trigger_permission_flush(affected_user_ids)
118 PermissionModel().trigger_permission_flush(affected_user_ids)
121
119
122 except Exception:
120 except Exception:
123 log.exception("Exception during update of repository")
121 log.exception("Exception during update of repository")
124 h.flash(_('Error occurred during update of repository {}').format(
122 h.flash(_('Error occurred during update of repository {}').format(
125 self.db_repo_name), category='error')
123 self.db_repo_name), category='error')
126
124
127 return {
125 return {
128 'redirect_url': h.route_path('edit_repo_perms', repo_name=self.db_repo_name),
126 'redirect_url': h.route_path('edit_repo_perms', repo_name=self.db_repo_name),
129 'private': private_flag
127 'private': private_flag
130 }
128 }
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now